# Axiom documentation
---
# Changelog
Source: https://axiom.co/docs/changelog
Axiom ships continuously. Read the latest release notes on the [Axiom changelog](https://axiom.co/changelog).
This section will become the first-party home for product updates as the changelog publishing workflow moves into the documentation platform.
---
# Quickstart
Source: https://axiom.co/docs/getting-started
This guide takes you from foundational knowledge to advanced proficiency with Axiom. The sections are structured to build upon one another but can also serve as standalone references. By the end, you can independently query data, diagnose performance, derive insights, and set up proactive alerting.
## Axiom fundamentals [#axiom-fundamentals]
Axiom is the modern machine data platform. Machine data is any record a system produces: logs, distributed traces, metrics, and events like product analytics, marketing attribution, or security audit trails. Axiom stores and queries all of it through one engine.
**Datasets** store related machine data, similar to a table in a traditional database. For example, the Axiom Playground includes datasets like `sample-http-logs` for HTTP request logs and `github-push-event` for GitHub activity.
**Fields** are named pieces of data on each record, like columns in a spreadsheet. Fields have a name (for example, `status`, `resp_body_size_bytes`, `geo.city`) and a value. Axiom supports various data types including strings, numbers, booleans, and complex JSON objects.
* **Single dataset:** Use one dataset when events share many common fields and you often query them together.
* **Multiple datasets:** Split into multiple datasets when the data is structurally different or serves entirely different use cases. This prevents a single dataset from accumulating fields that are only relevant to a small subset of its events.
## Send data to Axiom [#send-data-to-axiom]
You can send your first event with a single HTTP request:
```shell wrap
curl -X 'POST' 'https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/x-ndjson' \
-d '{ "http": { "request": { "method": "GET", "duration_ms": 231 }, "response": { "body": { "size": 3012 } } }, "url": { "path": "/download" } }'
```
To send events continuously, Axiom supports a wide range of tools and libraries:
* **[OpenTelemetry](/send-data/opentelemetry)** for industry-standard instrumentation of traces, logs, and metrics.
* **[Axiom API](/restapi/ingest)** for direct HTTP ingestion from any language or platform.
* **Language libraries** for [JavaScript](/guides/javascript), [Python](/guides/python), [Go](/guides/go), [Rust](/guides/rust), [.NET](/guides/send-logs-from-dotnet), and more.
* **Log shippers** like [Vector](/send-data/vector), [Fluent Bit](/send-data/fluent-bit), and [Logstash](/send-data/logstash).
* **Platform integrations** for [AWS](/send-data/cloudwatch), [Kubernetes](/send-data/kubernetes), [Vercel](/apps/vercel), and [Cloudflare Logpush](/apps/cloudflare-logpush).
For the full list, see [Methods for sending data](/send-data/methods).
## Explore your data [#explore-your-data]
Start by identifying the correct dataset in the [Datasets tab](https://app.axiom.co/datasets). You can explore the fields within a dataset to find relevant information. For example, in `sample-http-logs` you might look at `resp_header_size_bytes` (response size), `status` (HTTP status code), and `geo.country` (geographic origin).
Pay attention to field data types. A field stored as a number behaves differently than one stored as a string. The schema view in the Datasets tab shows the type for each field.
## Build your first queries [#build-your-first-queries]
You typically interact with this data by creating queries using one of the following interfaces:
* **[Builder](/query-data/explore):** A point-and-click interface that helps you build filters and aggregations without writing code. It's excellent for simple, quick-look analyses.
* **[Editor](/query-data/query-editor)**: An interface where you can write queries using powerful, text-based query languages for sophisticated analysis. Use [APL (Axiom Processing Language)](/apl/introduction) for logs, traces, and events, and [MPL (Metrics Processing Language)](/mpl/introduction) for metrics.
* **[Axiom API](/restapi/query)**: Query your data programmatically.
* **[AI-assisted query building](/query-data/query-editor#generate-query-using-natural-language)**: Generate APL queries from natural language descriptions after pressing Cmd+K (macOS) or Ctrl+K (Windows/Linux) in the Query tab.
* **[MCP Server](/console/intelligence/mcp-server) and [Skills](/console/intelligence/skills)**: Enable AI agents to query your data. Agents without an existing organization can [provision their own temporary organization](/console/intelligence/agent-created-orgs).
An APL query starts with a data source, followed by operators connected by the pipe `|` character. Each pipe takes the output of the previous line and uses it as input for the next, allowing you to chain operations. A common pattern is dataset, filter (`where`), transform (`extend`), analyze (`summarize`).
This example counts distinct users per day, grouped by HTTP method:
```kusto
['sample-http-logs']
| summarize dcount(id) by bin(_time, 1d), method
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20summarize%20dcount\(id\)%20by%20bin\(_time%2C%201d\)%2C%20method%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
* `['sample-http-logs']` selects the dataset.
* `|` passes the data to the next operator.
* `summarize` groups rows that share values in the `by` clause.
* `dcount(id)` calculates the distinct count of the `id` field. This is a probabilistic function that provides a highly accurate approximation and runs faster than an exact count.
* `by bin(_time, 1d), method` groups by time binned into 1-day intervals and by HTTP method.
* Start with Builder to understand the basic structure of a query, then switch to Editor to refine it.
* Use the AI assistant as a learning tool. Generate a query and then study its structure to understand the APL.
* Use the `where` operator early to narrow down events. This is important for performance.
* Review your team's saved queries in Axiom to learn from real-world examples.
* The full list of functions and operators is available in the [Query reference](/apl/overview).
## Set up monitors [#set-up-monitors]
[Monitors](/monitor-data/monitors) run queries on a schedule and trigger notifications when conditions are met. This moves you from reactive investigation to proactive awareness.
* **[Threshold monitors](/monitor-data/threshold-monitors)** trigger when an aggregated value crosses a threshold (for example, error counts above 100 in 5 minutes). This is the most common type.
* **[Match monitors](/monitor-data/match-monitors)** trigger for each individual event that matches a specific pattern (for example, a critical error message). Use these sparingly for high-volume events.
* **[Anomaly monitors](/monitor-data/anomaly-monitors)** use machine learning to detect unexpected deviations from a historical baseline, without a static threshold.
This example creates a threshold monitor to get a notification if the number of server errors exceeds a threshold.
1. Write and test the query:
```kusto
['sample-http-logs']
| where status >= 500
| summarize error_count = count()
```
2. Go to **Monitors** tab and create a new threshold monitor.
3. Paste your query and set the trigger condition, for example, `When error_count is above 50`.
4. Set the schedule, for example, evaluate every 5 minutes.
5. Select or create a [notifier](/monitor-data/notifiers-overview) (Slack, PagerDuty, email, and more).
* Customize the notifier description to include important context such as a link to a relevant dashboard.
* Avoid creating match monitors on high-volume logs without specific filters. This can lead to excessive notifications.
## Build dashboards [#build-dashboards]
[Dashboards](/dashboards/create) are collections of saved queries visualized as charts, tables, and other elements. They provide a single view for monitoring a service, tracking an experiment, or sharing key metrics.
This example creates a dashboard to monitor HTTP request performance.
**Element 1: P75 and P95 latency (time series)**
This query calculates the 75th and 95th percentiles of request duration and displays them as a time series.
```kusto
['sample-http-logs']
| summarize
P75 = percentile(req_duration_ms, 75),
P95 = percentile(req_duration_ms, 95)
by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20summarize%5Cn%20%20%20%20P75%20%3D%20percentile\(req_duration_ms%2C%2075\)%2C%5Cn%20%20%20%20P95%20%3D%20percentile\(req_duration_ms%2C%2095\)%5Cn%20%20by%20bin_auto\(_time\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Element 2: Requests by country (table)**
This query counts the number of requests by country and displays them as a table.
```kusto
['sample-http-logs']
| summarize request_count = count() by ['geo.country']
| sort by request_count desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20%5B'geo.country'%5D%5Cn%7C%20sort%20by%20request_count%20desc%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Element 3: Total distinct users (statistic)**
This query counts the number of distinct users and displays them as a statistic.
```kusto
['sample-http-logs']
| summarize dcount(id)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20summarize%20dcount\(id\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
* Give dashboards and their elements clear, descriptive names.
* Use dashboard filters to allow viewers to slice data by dimensions like region, status code, or service.
* Avoid cluttering a single dashboard with unrelated metrics. Create separate dashboards for different use cases.
## What's next [#whats-next]
* [APL introduction](/apl/introduction) for the full query language reference.
* [Sample queries](/apl/tutorial) for real-world APL examples you can run in the Playground.
* [Send data to Axiom](/send-data/methods) to connect your own data sources.
* [Monitor examples](/monitor-data/monitor-examples) for more alerting patterns.
---
# What is Axiom?
Source: https://axiom.co/docs/introduction
Axiom is a data platform designed to efficiently collect, store, and analyze event and telemetry data at massive scale. At its core, Axiom combines a high-performance, proprietary data store with an intelligent Console, helping teams reach actionable insights from their data faster.
Trusted by 30,000+ organizations, from high-growth startups to global enterprises.
## Components [#components]
Axiom consists of two purpose-built data stores supported by a unified console experience:
### EventDB [#eventdb]
Robust, cost-effective, and scalable datastore specifically optimized for timestamped event data. Built from the ground up to handle the vast volumes and high velocity of event ingestion, EventDB ensures:
* **Scalable data loading:** Events are ingested seamlessly without complex middleware, scaling linearly with no single points of failure.
* **Extreme compression:** Tuned storage format compresses data 25-50x, significantly reducing storage costs and ensuring data remains queryable at any time.
* **Serverless querying:** Axiom spins up ephemeral, serverless runtimes on-demand to execute queries efficiently, minimizing idle compute resources and costs.
### MetricsDB [#metricsdb]
Dedicated metrics datastore engineered specifically for high-cardinality time-series data. Unlike traditional metrics solutions that penalize you for dimensional complexity, MetricsDB embraces high-cardinality tags as a design principle:
* **High-cardinality native:** Store metrics with high-cardinality dimensional tags without performance degradation or cost penalties.
* **Optimized storage:** Purpose-built storage format designed for time-series workloads delivers efficient compression and fast aggregations across millions of unique tag combinations.
* **Thoughtful constraints:** Design choices prioritize the most common metrics use cases while maintaining exceptional performance.
For more information, see [Axiom’s architecture](/platform-overview/architecture).
### Console [#console]
Intuitive web app built for exploration, visualization, and monitoring of your data.
* **Real-time exploration:** Effortlessly query and visualize data streams in real-time, providing instant clarity on operational and business conditions.
* **Dynamic visualizations:** Generate insightful visualizations, from straightforward counts to sophisticated aggregations, tailored specifically to your needs.
* **Robust monitoring:** Set up threshold-based and anomaly driven alerts, ensuring proactive visibility into potential issues.
## Why choose Axiom? [#why-choose-axiom]
* **Cost-efficiency:** Axiom dramatically lowers data ingestion and storage costs compared to traditional observability and logging solutions.
* **Flexible insights:** Real-time query capabilities and an increasingly intelligent UI help pinpoint issues and opportunities without sampling.
* **One platform, many use cases:** Use Axiom for observability (logs, metrics, traces), LLM observability, product analytics, and any other machine data workload.
## Getting started [#getting-started]
* [Learn more about Axiom’s features](/platform-overview/features).
* [Explore the interactive demo playground](https://play.axiom.co/).
* [Create your own organization](https://app.axiom.co/register).
* Building with AI agents? Agents can [provision their own temporary organization](/console/intelligence/agent-created-orgs) with a single API request.
---
# API limits
Source: https://axiom.co/docs/restapi/api-limits
Axiom limits the number of calls a user (and their organization) can make over a certain period
of time to ensure fair usage and to maintain the quality of service for everyone.
Axiom systems closely monitor API usage and if a user exceeds any thresholds, Axiom
temporarily halts further processing of requests from that user (and/or organization).
This is to prevent any single user or app from overloading the system,
which could potentially impact other users' experience.
## Rate Limits [#rate-limits]
Rate limits vary and are specified by the following header in all responses:
| Header | Description |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `X-RateLimit-Scope` | Indicates if the limits counts against the organisation or personal rate limit. |
| `X-RateLimit-Limit` | The maximum number of requests a user is permitted to make per minute. |
| `X-RateLimit-Remaining` | The number of requests remaining in the current rate limit window. |
| `X-RateLimit-Reset` | The time at which the current rate limit window resets in UTC [epoch seconds](https://en.wikipedia.org/wiki/Unix_time). |
**Possible values for X-RateLimit-Scope :**
* `user`
* `organization`
**When the rate limit is exceeded, an error is returned with the status "429 Too Many Requests"**:
```json
{
"message": "rate limit exceeded",
}
```
## Query Limits [#query-limits]
| Header | Description |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| `X-QueryLimit-Limit` | The query cost limit of your plan in Gigabyte Milliseconds (GB\*ms). |
| `X-QueryLimit-Remaining` | The remaining query Gigabyte Milliseconds. |
| `X-QueryLimit-Reset` | The time at which the current rate limit window resets in UTC [epoch seconds](https://en.wikipedia.org/wiki/Unix_time). |
## Ingest Limits [#ingest-limits]
| Header | Description |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `X-IngestLimit-Limit` | The maximum bytes ingested a user is permitted to make per month. |
| `X-IngestLimit-Remaining` | The bytes ingested remaining in the current rate limit window. |
| `X-IngestLimit-Reset` | The time at which the current rate limit window resets in UTC [epoch seconds](https://en.wikipedia.org/wiki/Unix_time). |
Alongside data volume limits, Axiom also monitors the rate of ingest requests.
If an organization consistently sends an excessive number of requests per second,
far exceeding normal usage patterns, Axiom reserves the right to suspend their ingest
to maintain system stability and ensure fair resource allocation for all users.
To prevent exceeding these rate limits, it’s highly recommended to use batching clients,
which can efficiently manage the number of requests by aggregating data before sending.
## Limits on ingested data [#limits-on-ingested-data]
For more information on limits and requirements imposed by Axiom, see [Limits](/reference/limits).
---
# Send data to Axiom via API
Source: https://axiom.co/docs/restapi/ingest
The Axiom REST API accepts the following data formats:
* [JSON](#send-data-in-json-format)
* [NDJSON](#send-data-in-ndjson-format)
* [CSV](#send-data-in-csv-format)
This page explains how to send data to Axiom via cURL commands in each of these formats, and how to send data with the [Axiom Node.js library](#send-data-with-axiom-node-js).
For more information on choosing an ingest format, see [Optimize data loading](/reference/optimize-usage#optimize-data-loading).
For more information on other ingest options, see [Send data](/send-data/methods).
For an introduction to the basics of the Axiom API and to the authentication options, see [Introduction to Axiom API](/restapi/introduction).
The API requests on this page use the ingest data endpoint. For more information, see the [API reference](/restapi/endpoints/ingestToDataset).
The [Ingest data endpoint](/restapi/endpoints/ingestToDataset) only supports API tokens. Personal access tokens (PATs) aren't supported. For more information, see [Tokens](/reference/tokens).
## Send data in JSON format [#send-data-in-json-format]
To send data to Axiom in JSON format:
1. Encode the events as JSON objects.
2. Enter the array of JSON objects into the body of the API request.
3. Optional: In the body of the request, set optional parameters such as `timestamp-field` and `timestamp-format`. For more information, see the [ingest data API reference](/restapi/endpoints/ingestToDataset).
4. Set the `Content-Type` header to `application/json`.
5. Set the `Authorization` header to `Bearer API_TOKEN`.
6. Send the POST request to `https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME`.
### Example with grouped events [#example-with-grouped-events]
The following example request contains grouped events. The structure of the JSON payload has the scheme of `[ { "labels": { "key1": "value1", "key2": "value2" } }, ]` where the array contains one or more JSON objects describing events.
**Example request**
```bash
curl -X 'POST' 'https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
-d '[
{
"time":"2025-01-12T00:00:00.000Z",
"data":{"key1":"value1","key2":"value2"}
},
{
"data":{"key3":"value3"},
"labels":{"key4":"value4"}
}
]'
```
**Example response**
```json
{
"ingested": 2,
"failed": 0,
"failures": [],
"processedBytes": 219,
"blocksCreated": 0,
"walLength": 2
}
```
### Example with nested arrays [#example-with-nested-arrays]
**Example request**
```bash
curl -X 'POST' 'https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
-d '[
{
"axiom": [{
"logging":[{
"observability":[{
"location":[{
"credentials":[{
"datasets":[{
"first_name":"axiom",
"last_name":"logging",
"location":"global"
}],
"work":[{
"details":"https://app.axiom.co/",
"tutorials":"https://www.axiom.co/blog",
"changelog":"https://www.axiom.co/changelog",
"documentation": "https://www.axiom.co/docs"
}]
}],
"social_media":[{
"details":[{
"twitter":"https://twitter.com/AxiomFM",
"linkedin":"https://linkedin.com/company/axiomhq",
"github":"https://github.com/axiomhq"
}],
"features":[{
"datasets":"view logs",
"stream":"live_tail",
"explorer":"queries"
}]
}]
}]
}],
"logs":[{
"apl": "functions"
}]
}],
"storage":[{}]
}]}
]'
```
**Example response**
```json
{
"ingested":1,
"failed":0,
"failures":[],
"processedBytes":1587,
"blocksCreated":0,
"walLength":3
}
```
### Example with objects, strings, and arrays [#example-with-objects-strings-and-arrays]
**Example request**
```bash
curl -X 'POST' 'https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
-d '[{ "axiom": {
"logging": {
"observability": [
{ "apl": 23, "function": "tostring" },
{ "apl": 24, "operator": "summarize" }
],
"axiom": [
{ "stream": "livetail", "datasets": [4, 0, 16], "logging": "observability", "metrics": 8, "dashboard": 10, "alerting": "kubernetes" }
]
},
"apl": {
"reference":
[[80, 12], [30, 40]]
}
}
}]'
```
**Example response**
```json
{
"ingested":1,
"failed":0,
"failures":[],
"processedBytes":432,
"blocksCreated":0,
"walLength":4
}
```
## Send data in NDJSON format [#send-data-in-ndjson-format]
To send data to Axiom in NDJSON format:
1. Encode the events as JSON objects.
2. Enter each JSON object in a separate line into the body of the API request.
3. Optional: In the body of the request, set optional parameters such as `timestamp-field` and `timestamp-format`. For more information, see the [ingest data API reference](/restapi/endpoints/ingestToDataset).
4. Set the `Content-Type` header to either `application/json` or `application/x-ndjson`.
5. Set the `Authorization` header to `Bearer API_TOKEN`. Replace `API_TOKEN` with the Axiom API token you have generated.
6. Send the POST request to `https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME`. Replace `DATASET_NAME` with the name of the Axiom dataset where you want to send data.
**Example request**
```bash
curl -X 'POST' 'https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/x-ndjson' \
-d '{"id":1,"name":"machala"}
{"id":2,"name":"axiom"}
{"id":3,"name":"apl"}
{"index": {"_index": "products"}}
{"timestamp": "2016-06-06T12:00:00+02:00", "attributes": {"key1": "value1","key2": "value2"}}
{"queryString": "count()"}'
```
**Example response**
```json
{
"ingested": 6,
"failed": 0,
"failures": [],
"processedBytes": 266,
"blocksCreated": 0,
"walLength": 6
}
```
## Send data in CSV format [#send-data-in-csv-format]
To send data to Axiom in JSON format:
1. Encode the events in CSV format. The first line specifies the field names separated by commas. Subsequent new lines specify the values separated by commas.
2. Enter the CSV representation in the body of the API request.
3. Optional: In the body of the request, set optional parameters such as `timestamp-field` and `timestamp-format`. For more information, see the [ingest data API reference](/restapi/endpoints/ingestToDataset).
4. Set the `Content-Type` header to `text/csv`.
5. Set the `Authorization` header to `Bearer API_TOKEN`. Replace `API_TOKEN` with the Axiom API token you have generated.
6. Send the POST request to `https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME`. Replace `DATASET_NAME` with the name of the Axiom dataset where you want to send data.
**Example request**
```bash
curl -X 'POST' 'https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: text/csv' \
-d 'user, name
foo, bar'
```
**Example response**
```json
{
"ingested": 1,
"failed": 0,
"failures": [],
"processedBytes": 28,
"blocksCreated": 0,
"walLength": 2
}
```
## Send data with Axiom Node.js [#send-data-with-axiom-nodejs]
1. [Install and configure](/guides/javascript#use-axiomhq-js) the Axiom Node.js library.
2. Encode the events as JSON objects.
3. Pass the dataset name and the array of JSON objects to the `axiom.ingest` function.
```ts
axiom.ingest('DATASET_NAME', [{ foo: 'bar' }]);
await axiom.flush();
```
For more information on other libraries you can use to send data, see [Send data](/send-data/methods).
## What’s next [#whats-next]
After ingesting data to Axiom, you can [query it via API](/restapi/query) or the [Axiom Console](/query-data/explore).
---
# Get started with Axiom API
Source: https://axiom.co/docs/restapi/introduction
You can use the Axiom API (Application Programming Interface) to send data to Axiom, query data, and manage resources programmatically. This page covers the basics for interacting with the Axiom API.
## API basics [#api-basics]
Axiom API follows the REST architectural style and uses JSON for serialization. You can send API requests to Axiom with curl or API tools such as [Postman](https://www.postman.com/).
For example, the following curl command ingests data to an Axiom dataset:
```bash
curl -X 'POST' 'https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
-d '[
{
"axiom": "logs"
}
]'
```
For more information, see [Send data to Axiom via API](/restapi/ingest) and [Ingest data endpoint](/restapi/endpoints/ingestToDataset).
## Base domain [#base-domain]
The base domain of an API request depends on the following:
* To ingest data, use the [Ingest data endpoint](/restapi/endpoints/ingestToDataset) with the base domain of your edge deployment.
For more information on edge deployments, see [Edge deployments](/reference/edge-deployments).
* For all other API endpoints, use the base domain `https://api.axiom.co`.
## Content type [#content-type]
Encode the body of API requests as JSON objects and set the `Content-Type` header to `application/json`. Unless otherwise specified, Axiom encodes all responses (including errors) as JSON objects.
## Authentication [#authentication]
To prove that API requests come from you, you must include forms of authentication called tokens in your API requests. Axiom offers two types of tokens:
* [API tokens](/reference/tokens#api-tokens) let you control the actions that can be performed with the token. For example, you can specify that requests authenticated with a certain API token can only query data from a particular dataset.
* [Personal access tokens (PATs)](/reference/tokens#personal-access-tokens-pat) provide full control over your Axiom account. Requests authenticated with a PAT can perform every action you can perform in Axiom. When possible, use API tokens instead of PATs.
If you use an API token for authentication, include the API token in the `Authorization` header.
```bash
Authorization: Bearer API_TOKEN
```
If you use a PAT for authentication, include the PAT in the `Authorization` header and the org ID in the `x-axiom-org-id` header. For more information, see [Determine org ID](/reference/tokens#determine-org-id).
```bash
Authorization: Bearer API_TOKEN
x-axiom-org-id: ORG_ID
```
If authentication is unsuccessful for a request, Axiom returns the error status code `403`.
## Data types [#data-types]
Below is a list of the types of data used within the Axiom API:
| Name | Definition | Example |
| ----------- | ----------------------------------------------------------------- | ------------------------ |
| **ID** | A unique value used to identify resources. | "io12h34io1h24i" |
| **String** | A sequence of characters used to represent text. | "string value" |
| **Boolean** | A type of two possible values representing true or false. | true |
| **Integer** | A number without decimals. | 4567 |
| **Float** | A number with decimals. | 15.67 |
| **Map** | A data structure with a list of values assigned to a unique key. | \{ "key": "value" } |
| **List** | A data structure with only a list of values separated by a comma. | `["value", 4567, 45.67]` |
## What’s next [#whats-next]
* [Ingest data via API](/restapi/ingest)
* [Query data via API](/restapi/query)
---
# Pagination in Axiom API
Source: https://axiom.co/docs/restapi/pagination
Pagination allows you to retrieve responses in manageable chunks.
You can use pagination for the following endpoints:
* [Run Query](/restapi/endpoints/queryApl)
* [Run Query (Legacy)](/restapi/endpoints/queryDataset)
## Pagination mechanisms [#pagination-mechanisms]
You can use one of the following pagination mechanisms:
* [Pagination based on timestamp](#timestamp-based-pagination) (stable)
* [Pagination based on cursor](#cursor-based-pagination) (public preview)
Axiom recommends timestamp-based pagination. Cursor-based pagination is in public preview and may return unexpected query results.
## Timestamp-based pagination [#timestamp-based-pagination]
The parameters and mechanisms differ between the current and legacy endpoints.
### Run Query [#run-query]
To use timestamp-based pagination with the Run Query endpoint:
* Include the [limit operator](/apl/tabular-operators/limit-operator) in the APL query of your API request. The argument of this operator determines the number of events to display per page.
* Use `sort by _time asc` or `sort by _time desc` in the APL query. This returns the results in ascending or descending chronological order. For more information, see [sort operator](/apl/tabular-operators/sort-operator).
* Specify `startTime` and `endTime` in the body of your API request.
### Run Query (Legacy) [#run-query-legacy]
To use timestamp-based pagination with the legacy Run Query endpoint:
* Add the `limit` parameter to the body of your API request. The value of this parameter determines the number of events to display per page.
* Add the `order` parameter to the body of your API request. In the value of this parameter, order the results by time in either ascending or descending chronological order. For example, `[{ "field": "_time", "desc": true }]`. For more information, see [order operator](/apl/tabular-operators/order-operator).
* Specify `startTime` and `endTime` in the body of your API request.
## Page through the result set [#page-through-the-result-set]
Use the timestamps as boundaries to page through the result set.
### Queries with descending order [#queries-with-descending-order]
To go to the next page of the result set for queries with descending order (`_time desc`):
1. Determine the timestamp of last item on the current page. This is the least recent event.
2. Optional: Subtract 1 nanosecond from the timestamp.
3. In your next request, change the value `endTime` parameter in the body of your API request to the timestamp of the last item (optionally, minus 1 nanosecond).
Repeat this process until the result set is empty.
### Queries with ascending order [#queries-with-ascending-order]
To go to the next page of the result set for queries with ascending order (`_time asc`):
1. Determine the timestamp of last item on the current page. This is the most recent event.
2. Optional: Add 1 nanosecond to the timestamp.
3. In your next request, change the value `startTime` parameter in the body of your API request to the timestamp of the last item (optionally, plus 1 nanosecond).
Repeat this process until the result set is empty.
### Deduplication mechanism [#deduplication-mechanism]
In the procedures above, the steps about incrementing the timestamp are optional. If you increment the timestamp, there is a risk of duplication. If you don’t increment the timestamp, there is a risk of overlap. Duplicated data is possible for many reasons, such as backfill or natural duplication from external data sources. For these reasons, regardless of the method you choose (increment or not increment the timestamp, sort by descending or ascending order), Axiom recommends you implement some form of deduplication mechanism in your pagination script.
### Limits [#limits]
Both the Run Query and the Run Query (Legacy) endpoints allow request-based limit configuration. This means that the limit they use is the lowest of the following: the query limit, the request limit, and Axiom’s server-side internal limit. Without a query or request limit, Axiom currently defaults to the limit of 1,000 events per page. For the pagination of datasets that are greater than 1,000 events, Axioms recommends specifying the same limit in the request and the APL query to avoid the default value and contradictory limits.
### Examples [#examples]
#### Example request Run Query [#example-request-run-query]
```bash
curl -X 'POST' 'https://api.axiom.co/v1/datasets/_apl?format=tabular' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"apl": "DATASET_NAME | sort by _time desc | limit 100",
"startTime": "2024-11-30T00:00:00.000Z",
"endTime": "2024-11-30T23:59:59.999Z"
}'
```
#### Example request Run Query (Legacy) [#example-request-run-query-legacy]
```bash
curl -X 'POST' 'https://api.axiom.co/v1/datasets/DATASET_NAME/query' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"startTime": "2024-11-30T00:00:00.000Z",
"endTime": "2024-11-30T23:59:59.999Z",
"limit": 100,
"order": [{ "field": "_time", "desc": true }]
}'
```
#### Example request to page through the result set [#example-request-to-page-through-the-result-set]
Example request to go to next page for Run Query:
```bash
curl -X 'POST' 'https://api.axiom.co/v1/datasets/_apl?format=tabular' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"apl": "DATASET_NAME | sort by _time desc | limit 100",
"startTime": "2024-11-30T00:00:00.000Z",
"endTime": "2024-11-30T22:59:59.999Z"
}'
```
Example request to go to next page for Run Query (Legacy):
```bash
curl -X 'POST' 'https://api.axiom.co/v1/datasets/DATASET_NAME/query' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"startTime": "2024-11-30T00:00:00.000Z",
"endTime": "2024-11-30T22:59:59.999Z",
"limit": 100,
"order": [{ "field": "_time", "desc": true }]
}'
```
## Cursor-based pagination [#cursor-based-pagination]
Cursor-based pagination is in public preview and may return unexpected query results. Axiom recommends timestamp-based pagination.
The parameters and mechanisms differ between the current and legacy endpoints.
### Run Query [#run-query-1]
To use cursor-based pagination with the Run Query endpoint:
* Include the [`limit` operator](/apl/tabular-operators/limit-operator) in the APL query of your API request. The argument of this operator determines the number of events to display per page.
* Use `sort by _time asc` or `sort by _time desc` in the APL query. This returns the results in ascending or descending chronological order. For more information, see [sort operator](/apl/tabular-operators/sort-operator).
* Specify `startTime` and `endTime` in the body of your API request.
### Run Query (Legacy) [#run-query-legacy-1]
To use cursor-based pagination with the legacy Run Query endpoint:
* Add the `limit` parameter to the body of your API request. The value of this parameter determines the number of events to display per page.
* Add the `order` parameter to the body of your API request. In the value of this parameter, order the results by time in either ascending or descending chronological order. For example, `[{ "field": "_time", "desc": true }]`. For more information, see [order operator](/apl/tabular-operators/order-operator).
* Specify `startTime` and `endTime` in the body of your API request.
### Response format [#response-format]
Contains metadata about the response including pagination information.
Cursor for the first item in the current page.
Cursor for the last item in the current page.
Total number of rows matching the query.
Contains the list of returned objects.
## Page through the result set [#page-through-the-result-set-1]
To page through the result set, add the `cursor` parameter to the body of your API request.
Optional. A cursor for use in pagination. Use the cursor string returned in previous responses to fetch the next or previous page of results.
The `minCursor` and `maxCursor` fields in the response are boundaries that help you page through the result set.
For queries with descending order (`_time desc`), use `minCursor` from the response as the `cursor` in your next request to go to the next page. You reach the end when your provided `cursor` matches the `minCursor` in the response.
For queries with ascending order (`_time asc`), use `maxCursor` from the response as the `cursor` in your next request to go to the next page. You reach the end when your provided `cursor` matches the `maxCursor` in the response.
If the query returns fewer results than the specified limit, paging can stop.
### Examples [#examples-1]
#### Example request Run Query [#example-request-run-query-1]
```bash
curl -X 'POST' 'https://api.axiom.co/v1/datasets/_apl?format=tabular' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"apl": "DATASET_NAME | sort by _time desc | limit 100",
"startTime": "2024-01-01T00:00:00.000Z",
"endTime": "2024-01-31T23:59:59.999Z"
}'
```
#### Example request Run Query (Legacy) [#example-request-run-query-legacy-1]
```bash
curl -X 'POST' 'https://api.axiom.co/v1/datasets/DATASET_NAME/query' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"startTime": "2024-01-01T00:00:00.000Z",
"endTime": "2024-01-31T23:59:59.999Z",
"limit": 100,
"order": [{ "field": "_time", "desc": true }]
}'
```
#### Example response [#example-response]
```json
{
"status": {
"rowsMatched": 2500,
"minCursor": "0d3wo7v7e1oii-075a8c41710018b9-0000ecc5",
"maxCursor": "0d3wo7v7e1oii-075a8c41710018b9-0000faa3"
},
"matches": [
// ... events ...
]
}
```
#### Example request to page through the result set [#example-request-to-page-through-the-result-set-1]
To page through the result set, use the appropriate cursor value in your next request. For more information, see [Page through the result set](#page-through-the-result-set).
Example request to go to next page for Run Query:
```bash
curl -X 'POST' 'https://api.axiom.co/v1/datasets/_apl?format=tabular' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"apl": "DATASET_NAME | sort by _time desc | limit 100",
"startTime": "2024-01-01T00:00:00.000Z",
"endTime": "2024-01-31T23:59:59.999Z",
"cursor": "0d3wo7v7e1oii-075a8c41710018b9-0000ecc5"
}'
```
Example request to go to next page for Run Query (Legacy):
```bash
curl -X 'POST' 'https://api.axiom.co/v1/datasets/DATASET_NAME/query' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"startTime": "2024-01-01T00:00:00.000Z",
"endTime": "2024-01-31T23:59:59.999Z",
"limit": 100,
"order": [{ "field": "_time", "desc": true }],
"cursor": "0d3wo7v7e1oii-075a8c41710018b9-0000ecc5"
}'
```
---
# Query data via Axiom API
Source: https://axiom.co/docs/restapi/query
This page explains how to query data via the Axiom API using the following:
* [cURL](#query-data-with-curl)
* [Axiom JavaScript library (`@axiomhq/js`)](#query-data-with-the-axiom-javascript-library)
For an introduction to the basics of the Axiom API and to the authentication options, see [Introduction to Axiom API](/restapi/introduction).
The API requests on this page use the query data endpoint. For more information, see the [API reference](/restapi/endpoints/queryApl).
## Query data with cURL [#query-data-with-curl]
To query data with cURL:
1. Build the APL query. For more information, see [Introduction to APL](/apl/introduction).
2. Encode the APL query as a JSON object and enter it into the body of the API request.
3. Optional: In the body of the request, set optional parameters such as `startTime` and `endTime`. For more information, see the [query data API reference](/restapi/endpoints/queryApl).
4. Set the `Content-Type` header to `application/json`.
5. Set the `Authorization` header to `Bearer API_TOKEN`.
6. Send the POST request to one of the following:
* For tabular output, use `https://AXIOM_DOMAIN/v1/query/_apl?format=tabular`.
* For legacy output, use `https://AXIOM_DOMAIN/v1/query/_apl?format=legacy`.
### Example [#example]
```bash
curl --request POST \
--url 'https://AXIOM_DOMAIN/v1/query/_apl?format=tabular' \
--header 'Authorization: Bearer API_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"apl": "DATASET_NAME | limit 10",
"startTime": "string",
"endTime": "string"
}'
```
**Example response**
```json [expandable]
{
"format": "tabular",
"status": {
"elapsedTime": 260650,
"minCursor": "0d8q6stroluyo-07c3957e7400015c-0000c875",
"maxCursor": "0d8q6stroluyo-07c3957e7400015c-0000c877",
"blocksExamined": 4,
"blocksCached": 0,
"blocksMatched": 0,
"rowsExamined": 197604,
"rowsMatched": 197604,
"numGroups": 0,
"isPartial": false,
"cacheStatus": 1,
"minBlockTime": "2025-03-26T12:03:14Z",
"maxBlockTime": "2025-03-26T12:12:42Z"
},
"tables": [
{
"name": "0",
"sources": [
{
"name": "DATASET_NAME"
}
],
"fields": [
{
"name": "_sysTime",
"type": "datetime"
},
{
"name": "_time",
"type": "datetime"
},
{
"name": "content_type",
"type": "string"
},
{
"name": "geo.city",
"type": "string"
},
{
"name": "geo.country",
"type": "string"
},
{
"name": "id",
"type": "string"
},
{
"name": "is_tls",
"type": "boolean"
},
{
"name": "message",
"type": "string"
},
{
"name": "method",
"type": "string"
},
{
"name": "req_duration_ms",
"type": "float"
},
{
"name": "resp_body_size_bytes",
"type": "integer"
},
{
"name": "resp_header_size_bytes",
"type": "integer"
},
{
"name": "server_datacenter",
"type": "string"
},
{
"name": "status",
"type": "string"
},
{
"name": "uri",
"type": "string"
},
{
"name": "user_agent",
"type": "string"
},
{
"name": "is_ok_2 ",
"type": "boolean"
},
{
"name": "city_str_len",
"type": "integer"
}
],
"order": [
{
"field": "_time",
"desc": true
}
],
"groups": [],
"range": {
"field": "_time",
"start": "1970-01-01T00:00:00Z",
"end": "2025-03-26T12:12:43Z"
},
"columns": [
[
"2025-03-26T12:12:42.68112905Z",
"2025-03-26T12:12:42.68112905Z",
"2025-03-26T12:12:42.68112905Z"
],
[
"2025-03-26T12:12:42Z",
"2025-03-26T12:12:42Z",
"2025-03-26T12:12:42Z"
],
[
"text/html",
"text/plain-charset=utf-8",
"image/jpeg"
],
[
"Ojinaga",
"Humboldt",
"Nevers"
],
[
"Mexico",
"United States",
"France"
],
[
"8af366cf-6f25-42e6-bbb4-d860ab535a60",
"032e7f68-b0ab-47c0-a24a-35af566359e5",
"4d2c7baa-ff28-4b1f-9db9-8e6c0ed5a9c9"
],
[
false,
false,
true
],
[
"QCD permutations were not solvable in linear time, expected compressed time",
"QCD permutations were not solvable in linear time, expected compressed time",
"Expected a new layer of particle physics but got a Higgs Boson"
],
[
"GET",
"GET",
"GET"
],
[
1.396373193863436,
0.16252390534308514,
0.4093416175186162
],
[
3448,
2533,
1906
],
[
84,
31,
29
],
[
"DCA",
"GRU",
"FRA"
],
[
"201",
"200",
"200"
],
[
"/api/v1/buy/commit/id/go",
"/api/v1/textdata/cnfigs",
"/api/v1/bank/warn"
],
[
"Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))"
],
[
true,
true,
true
],
[
7,
8,
6
]
]
}
],
"datasetNames": [
"DATASET_NAME"
],
"fieldsMetaMap": {
"DATASET_NAME": [
{
"name": "status",
"type": "",
"unit": "",
"hidden": false,
"description": "HTTP status code"
},
{
"name": "resp_header_size_bytes",
"type": "integer",
"unit": "none",
"hidden": false,
"description": ""
},
{
"name": "geo.city",
"type": "string",
"unit": "",
"hidden": false,
"description": "the city"
},
{
"name": "resp_body_size_bytes",
"type": "integer",
"unit": "decbytes",
"hidden": false,
"description": ""
},
{
"name": "content_type",
"type": "string",
"unit": "",
"hidden": false,
"description": ""
},
{
"name": "geo.country",
"type": "string",
"unit": "",
"hidden": false,
"description": ""
},
{
"name": "req_duration_ms",
"type": "float",
"unit": "ms",
"hidden": false,
"description": "Request duration"
}
]
}
}
```
## Query data with the Axiom JavaScript library [#query-data-with-the-axiom-javascript-library]
1. [Install and configure](/guides/javascript#use-axiomhq-js) the Axiom JavaScript library (`@axiomhq/js`).
2. Build the APL query. For more information, see [Introduction to APL](/apl/introduction).
3. Pass the APL query as a string to the `axiom.query` function.
```ts
const res = await axiom.query(`['DATASET_NAME'] | where foo == 'bar' | limit 100`);
console.log(res);
```
To run queries within a specific edge deployment, set the `edge` option when you create the client. The client then routes queries to the [edge query endpoint](/restapi/endpoints/queryEdge) automatically. For more information, see [Configure region](/guides/javascript#configure-region).
For more examples, see the [examples in GitHub](https://github.com/axiomhq/axiom-js/tree/main/examples).
---
# Enrich Axiom experience with AWS PrivateLink
Source: https://axiom.co/docs/apps/aws-privatelink
[AWS PrivateLink](https://aws.amazon.com/privatelink/) is a networking service provided by Amazon Web Services (AWS) that allows you to securely access services hosted on the AWS cloud over a private network connection. With AWS PrivateLink, you can access Axiom directly from your AWS without an internet gateway or NAT device, simplifying your network setup.
## Cross-region support [#cross-region-support]
Axiom supports native cross-region PrivateLink for the following regions:
| Axiom edge deployment | Supported PrivateLink regions |
| :-------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `US East 1 (AWS)` | `us-east-1` `us-east-2` `us-west-1` `us-west-2` `eu-west-1` `eu-west-2` `eu-west-3` `eu-central-1` `ca-central-1` |
| `EU Central 1 (AWS)` | `eu-central-1` `eu-north-1` |
To connect to a region that isn't listed above, [contact Axiom](https://axiom.co/contact).
For more information, see [Edge deployments](/reference/edge-deployments).
## Setup [#setup]
Use the service details for your edge deployment to set up the VPC endpoint.
| Axiom edge deployment | PrivateLink service name | PrivateLink service region |
| :-------------------- | :----------------------------------------------------------- | :------------------------- |
| `US East 1 (AWS)` | `com.amazonaws.vpce.us-east-1.vpce-svc-0daeb6c5a00337061` | `us-east-1` |
| `EU Central 1 (AWS)` | `com.amazonaws.vpce.eu-central-1.vpce-svc-00e8d47e8c60784f7` | `eu-central-1` |
1. In your VPC Console, go to **PrivateLink and Lattice > Endpoints**, and then click **Create endpoint**.
2. Select **PrivateLink ready partner services**, and then enter the service name for your edge deployment.
3. Under **Service Region**, turn on **Cross-region endpoint**, and then select the service region for your edge deployment. This is the region where the Axiom service is hosted, and it's independent of your VPC's region.
4. Click **Verify service**.
5. Select the VPC and subnets that you want to connect to the Axiom VPC service endpoint. Ensure that **Enable DNS name** is turned on and the security group accepts inbound traffic on TCP port `443`.
6. Finish the setup and wait for the VPC endpoint to become available. This usually takes 10 minutes.
---
# Connect Axiom with Cloudflare Logpush
Source: https://axiom.co/docs/apps/cloudflare-logpush
Cloudflare Logpush is a feature that allows you to push HTTP request logs and other Cloudflare-generated logs directly to your desired storage, analytics, and monitoring solutions like Axiom. The integration with Axiom aims to provide real-time insights into web traffic, and operational issues, thereby helping to monitor and troubleshoot effectively.
The Cloudflare Logpush integration supports all [edge deployments](/reference/edge-deployments). The integration sends data to your organization’s default edge deployment automatically, without any additional configuration.
## What’s Cloudflare Logpush? [#whats-cloudflare-logpush]
Cloudflare Logpush enables Cloudflare users to automatically export their logs in JSON format to a variety of endpoints. This feature is incredibly useful for analytics, auditing, debugging, and monitoring the performance and security of websites. Types of logs you can export include HTTP request logs, firewall events, and more.
## Installing Cloudflare Logpush app [#installing-cloudflare-logpush-app]
### Prerequisites [#prerequisites]
* An active Cloudflare Enterprise account
* API token or global API key
You can create a token that has access to a single zone, single account or a mix of all these, depending on your needs. For account access, the token must
have these permissions:
* Logs: Edit
* Account settings: Read
For the zones, only edit permission is required for logs.
**Zero Trust datasets require an additional permission.** To create Logpush jobs for Cloudflare Zero Trust datasets (Access, Gateway, and DEX) — for example **Access requests**, **Gateway DNS**, **Gateway HTTP**, **Gateway Network**, and **Zero Trust Network Session Logs** — your token must also have the **Zero Trust: PII Read** permission in addition to **Logs: Edit**. These datasets can contain personally identifiable information (such as private IP addresses, user names, and device names), and Cloudflare blocks the job with a `missing required permissions` error when this permission isn't granted.
The standard **Administrator** role includes **Logs: Edit** but not Zero Trust PII access, so an admin or owner account alone isn't enough. A **Super Administrator** must assign the **Cloudflare Zero Trust PII** role to the user (or grant the token the **Zero Trust: PII Read** permission) before these jobs can be created. If you don't use Cloudflare Zero Trust, Gateway, or Access, you can leave these datasets unselected.
## Steps [#steps]
* Log in to Cloudflare, go to your Cloudflare dashboard, and then select the Enterprise zone (domain) you want to enable Logpush for.
* Optionally, set filters and fields. You can filter logs by field (like Client IP, User Agent, etc.) and set the type of logs you want (for example, HTTP requests, firewall events).
* In Axiom, click **Settings**, select **Apps**, and install the Cloudflare Logpush app with the token you created from the profile settings in Cloudflare.
* You see your available accounts and zones. Select the Cloudflare datasets you want to subscribe to.
* The installation uses the Cloudflare API to create Logpush jobs for each selected dataset.
* After the installation completes, you can find the installed Logpush jobs at Cloudflare.
For zone-scoped Logpush jobs:
For account-scoped Logpush jobs:
* In the Axiom, you can see your Cloudflare Logpush dashboard.
Using Axiom with Cloudflare Logpush offers a powerful solution for real-time monitoring, observability, and analytics. Axiom can help you gain deep insights into your app’s performance, errors, and app bottlenecks.
### Benefits of using the Axiom Cloudflare Logpush Dashboard [#benefits-of-using-the-axiom-cloudflare-logpush-dashboard]
* Real-time visibility into web performance: One of the most crucial features is the ability to see how your website or app is performing in real-time. The dashboard can show everything from page load times to error rates, giving you immediate insights that can help in timely decision-making.
* Actionable insights for troubleshooting: The dashboard doesn’t just provide raw data; it provides insights. Whether it’s an error that needs immediate fixing or performance metrics that show an error from your app, having this information readily available makes it easier to identify problems and resolve them swiftly.
* DNS metrics: Understanding the DNS requests, DNS queries, and DNS cache hit from your app is vital to track if there’s a request spike or get the total number of queries in your system.
* Centralized logging and error tracing: With logs coming in from various parts of your app stack, centralizing them within Axiom makes it easier to correlate events across different layers of your infrastructure. This is crucial for troubleshooting complex issues that may span multiple services or components.
## Supported Cloudflare Logpush Datasets [#supported-cloudflare-logpush-datasets]
Axiom supports all the Cloudflare account-scoped datasets.
Zone-scoped
* DNS logs
* Firewall events
* HTTP requests
* NEL reports
* Spectrum events
Account-scoped
* Access requests
* Audit logs
* CASB Findings
* Device posture results
* DNS Firewall Logs
* Gateway DNS
* Gateway HTTP
* Gateway Network
* Magic IDS Detections
* Network Analytics Logs
* Workers Trace Events
* Zero Trust Network Session Logs
The Zero Trust, Gateway, and Access datasets (**Access requests**, **Gateway DNS**, **Gateway HTTP**, **Gateway Network**, and **Zero Trust Network Session Logs**) require the **Zero Trust: PII Read** token permission in addition to **Logs: Edit**. See [Prerequisites](#prerequisites) above. Without it, Cloudflare returns a `missing required permissions` error for these datasets.
---
# Connect Axiom with Cloudflare Workers
Source: https://axiom.co/docs/apps/cloudflare-workers
The Axiom Cloudflare Workers app provides granular detail about the traffic coming in from your monitored sites. This includes edge requests, static resources, client auth, response duration, and status. Axiom gives you an all-at-once view of key Cloudflare Workers metrics and logs, out of the box, with the dynamic Cloudflare Workers dashboard.
The data obtained with the Axiom dashboard gives you better insights into the state of your Cloudflare Workers so you can easily monitor bad requests, popular URLs, cumulative execution time, successful requests, and more. The app is part of Axiom’s unified logging and observability platform, so you can easily track Cloudflare Workers edge requests alongside a comprehensive view of other resources in your Cloudflare Worker environments.
Axiom Cloudflare Workers is an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/axiom-cloudflare-workers).
## What’s Cloudflare Workers [#whats-cloudflare-workers]
[Cloudflare Workers](https://developers.cloudflare.com/workers/) is a serverless computing platform developed by Cloudflare. The Workers platform allows developers to deploy and run JavaScript code directly at the network edge in more than 200 data centers worldwide. This serverless architecture enables high performance, low latency, and efficient scaling for web apps and APIs.
## Send Cloudflare Worker logs to Axiom [#send-cloudflare-worker-logs-to-axiom]
1. In Cloudflare, create a new worker. For more information, see the [Cloudflare documentation](https://developers.cloudflare.com/workers/get-started/guide/).
2. Copy the contents of the [src/worker.js](https://github.com/axiomhq/axiom-cloudflare-workers/blob/main/src/worker.js) file into the worker you have created.
3. Update the authentication variables:
```bash
const axiomDataset = "DATASET_NAME"
const axiomToken = "API_TOKEN"
```
4. Add triggers for the worker. For example, add a route trigger using the [Cloudflare documentation](https://developers.cloudflare.com/workers/configuration/routing/routes/#set-up-a-route-in-the-dashboard).
When the routes receive requests, the worker is triggered and the logs are sent to your Axiom dataset.
---
# Connect Axiom with Convex
Source: https://axiom.co/docs/apps/convex
Connect Axiom with [Convex](https://convex.dev) to get comprehensive observability into your backend functions and app events. Stream function executions, console logs, and metadata from your Convex deployment to Axiom for powerful querying, data visualization, and monitoring.
This page explains how to connecting with Convex can enhance your Axiom experience. For instructions on streaming data from Convex to Axiom, see [Send data from Convex](/send-data/convex).
## Convex and log streams [#convex-and-log-streams]
[Convex](https://convex.dev) is the backend platform that keeps your app in sync. It’s the open source, reactive database where queries are TypeScript code running right in the database. Just like React components react to state changes, Convex queries react to database changes.
Convex provides a database, a place to write your server functions, and client libraries. It makes it easy to build and scale dynamic live-updating apps.
Log streams enable streaming of events such as function executions and `console.log`s from your Convex deployment to supported destinations like Axiom, Datadog, or custom webhooks.
Log streams require a Convex Professional plan. Learn more about [Convex pricing plans](https://www.convex.dev/pricing) or upgrade your account.
## Benefits of connecting Axiom with Convex [#benefits-of-connecting-axiom-with-convex]
Convex’s built-in features allow you to see the most recent logs produced by your deployment. Additionally, log streaming to Axiom provides comprehensive observability for your backend operations.
* **Historical log storage** beyond the recent logs view.
* **Powerful querying** with Axiom Processing Language (APL).
* **Advanced data visualization** and custom dashboards.
* **Integration with monitoring tools** like PagerDuty, Slack, and more.
* **Real-time alerting** based on function performance and errors.
## Analyze function performance [#analyze-function-performance]
Convex log streams send structured data to Axiom that includes the following key fields:
* **`['data.topic']`**: Event type
* **`['data.status']`**: Execution status
* **`['data.function.path']`**: Full function path
* **`['data.function.type']`**: Function type
* **`['data.function.component_path']`**: Component path
* **`['data.execution_time_ms']`**: Function execution time in milliseconds
* **`['data.usage.database_read_documents']`**: Number of documents read
* **`['data.usage.database_read_bytes']`**: Bytes of data read
* **`['data.function.cached']`**: Cache hit indicator for queries
* **`['convex.project_slug']`**: Convex project identifier
* **`['convex.deployment_name']`**: Deployment name
* **`['convex.deployment_type']`**: Deployment type
After sending Convex log streams to Axiom, explore your Convex data and analyze your function performance using Axiom’s powerful [query capabilities](/query-data/query-editor).
Identify slow-performing functions by analyzing execution times:
```kusto
['convex']
| where ['data.topic'] == "function_execution"
| summarize
avg_time_ms = avg(['data.execution_time_ms']),
max_time_ms = max(['data.execution_time_ms']),
total_calls = count()
by ['data.function.path']
| order by avg_time_ms desc
```
Track functions that are experiencing failures:
```kusto
['convex']
| where ['data.topic'] == "function_execution" and ['data.status'] == "failure"
| summarize count() by ['data.function.path'], ['data.function.type']
| order by count_ desc
```
Compare performance across different function types (queries, mutations, actions):
```kusto
['convex']
| where ['data.topic'] == "function_execution"
| summarize
total_calls = count(),
avg_duration_ms = avg(['data.execution_time_ms'])
by ['data.function.type']
```
Track database read patterns and identify resource-intensive functions:
```kusto
['convex']
| where ['data.topic'] == "function_execution"
| summarize
avg_docs_read = avg(['data.usage.database_read_documents']),
avg_bytes_read = avg(['data.usage.database_read_bytes'])
by ['data.function.path']
| order by avg_bytes_read desc
```
Track query cache hit rates for optimization:
```kusto
['convex']
| where ['data.topic'] == "function_execution"
and ['data.function.type'] == "query"
and isnotempty(['data.function.cached'])
| summarize
cache_hits = countif(['data.function.cached'] == true),
total_queries = count()
| extend cache_hit_rate = (cache_hits * 100) / total_queries
```
Track scheduled job lag and performance:
```kusto
['convex']
| where ['data.topic'] == "scheduled_job_lag"
| summarize
max_lag_seconds = max(['data.lag_seconds']),
avg_lag_seconds = avg(['data.lag_seconds'])
by ['convex.project_slug'], ['convex.deployment_name']
```
## Set up monitoring and alerting [#set-up-monitoring-and-alerting]
[Create monitors](/monitor-data/monitors) to get notified about issues in your Convex deployment.
Monitor functions with high error rates:
```kusto
['convex']
| where ['data.topic'] == "function_execution"
| summarize
total_calls = count(),
failures = countif(['data.status'] == "failure")
by ['data.function.path']
| extend error_rate = (failures * 100) / total_calls
| where error_rate > 5 // Alert if error rate exceeds 5%
```
Set up alerts for functions exceeding execution time thresholds:
```kusto
['convex']
| where ['data.topic'] == "function_execution" and ['data.execution_time_ms'] > 5000
| summarize count() by ['data.function.path']
```
## Prebuilt dashboard [#prebuilt-dashboard]
When you configure a Convex dataset in Axiom, a dashboard is automatically created in the **Integrations** section of the Dashboards tab. This prebuilt dashboard provides immediate insights into your Convex function performance and errors.
The dashboard includes:
* **Function execution metrics**: Success rates, error counts, and performance trends.
* **Database operation insights**: Query patterns, mutation frequencies, and subscription activity.
* **Error analysis**: Error types, frequency, and affected functions.
* **Performance monitoring**: Response times, throughput, and resource utilization.
You can customize this dashboard or create additional dashboards tailored to your specific monitoring needs and business requirements. For more information, see [Create dashboards](/dashboards/create) and [Configure dashboards](/dashboards/configure).
## Next steps [#next-steps]
* [Explore your Convex data](/query-data/query-editor) in Axiom and [learn APL](/apl/introduction) to query your Convex logs.
* [Create custom dashboards](/dashboards/create) for your team.
* [Set up monitors](/monitor-data/monitors) to get alerted about issues.
---
# Connect Axiom with Grafana
Source: https://axiom.co/docs/apps/grafana
## What’s a Grafana data source plugin? [#whats-a-grafana-data-source-plugin]
Grafana is an open-source tool for time-series analytics, visualization, and alerting. It’s frequently used in DevOps and IT Operations roles to provide real-time information on system health and performance.
Data sources in Grafana are the actual databases or services where the data is stored. Grafana has a variety of data source plugins that connect Grafana to different types of databases or services. This enables Grafana to query those sources from display that data on its dashboards. The data sources can be anything from traditional SQL databases to time-series databases or metrics, and logs from Axiom.
A Grafana data source plugin extends the functionality of Grafana by allowing it to interact with a specific type of data source. These plugins enable users to extract data from a variety of different sources, not just those that come supported by default in Grafana.
## Prerequisites [#prerequisites]
* [Create an Axiom account](https://app.axiom.co/register).
* [Create a dataset in Axiom](/reference/datasets#create-dataset) where you send your data.
* [Create an advanced API token in Axiom](/reference/tokens#create-advanced-api-token) with permissions to query data from the dataset you have created. Basic API tokens only allow data ingestion and won't work with Grafana.
## Install the Axiom Grafana data source plugin on Grafana Cloud [#install-the-axiom-grafana-data-source-plugin-on-grafana-cloud]
* In Grafana, click Administration > Plugins in the side navigation menu to view installed plugins.
* In the filter bar, search for the Axiom plugin
* Click on the plugin logo.
* Click Install.
When the update is complete, a confirmation message is displayed, indicating that the installation was successful.
* The Axiom Grafana Plugin is also installable from the [Grafana Plugins page](https://grafana.com/grafana/plugins/axiomhq-axiom-datasource/)
## Install the Axiom Grafana data source plugin on local Grafana [#install-the-axiom-grafana-data-source-plugin-on-local-grafana]
The Axiom data source plugin for Grafana is [open source on GitHub](https://github.com/axiomhq/axiom-grafana). It can be installed via the Grafana CLI, or via Docker.
### Install the Axiom Grafana Plugin using Grafana CLI [#install-the-axiom-grafana-plugin-using-grafana-cli]
```bash
grafana-cli plugins install axiomhq-axiom-datasource
```
### Install Via Docker [#install-via-docker]
* Add the plugin to your `docker-compose.yml` or `Dockerfile`
* Set the environment variable `GF_INSTALL_PLUGINS` to include the plugin
Example:
`GF_INSTALL_PLUGINS="axiomhq-axiom-datasource"`
## Configuration [#configuration]
* Add a new data source in Grafana
* Select the Axiom data source type.
* Enter the previously generated API token.
* Save and test the data source.
## Build Queries with Query Editor [#build-queries-with-query-editor]
The Axiom data source Plugin provides a custom query editor to build and visualize your Axiom event data. After configuring the Axiom data source, start building visualizations from metrics and logs stored in Axiom.
* Create a new panel in Grafana by clicking on Add visualization
* Select the Axiom data source.
* Use the query editor to choose the desired metrics, dimensions, and filters.
## Benefits of the Axiom Grafana data source plugin [#benefits-of-the-axiom-grafana-data-source-plugin]
The Axiom Grafana data source plugin allows users to display and interact with their Axiom data directly from within Grafana. By doing so, it provides several advantages:
1. **Unified visualization:** The Axiom Grafana data source plugin allows users to utilize Grafana’s powerful visualization tools with Axiom’s data. This enables users to create, explore, and share dashboards which visually represent their Axiom logs and metrics.
2. **Rich Querying Capability:** Grafana has a powerful and flexible interface for building data queries. With the Axiom plugin, and leverage this capability to build complex queries against your Axiom data.
3. **Customizable Alerting:** Grafana’s alerting feature allows you to set alerts based on your queries' results, and set up custom alerts based on specific conditions in your Axiom log data.
4. **Sharing and Collaboration:** Grafana’s features for sharing and collaboration can help teams work together more effectively. Share Axiom data visualizations with others, collaborate on dashboards, and discuss insights directly in Grafana.
---
# Map location data with Axiom and Hex
Source: https://axiom.co/docs/apps/hex
Hex is a powerful collaborative data platform that allows you to create notebooks with Python/SQL code and interactive visualizations.
This page explains how to integrate Hex with Axiom to visualize geospatial data from your logs. You ingest location data into Axiom, query it using APL, and create interactive map visualizations in Hex.
* [Create a Hex account](https://app.hex.tech/).
## Send geospatial data to Axiom [#send-geospatial-data-to-axiom]
Send your sample location data to Axiom using the API endpoint. For example, the following HTTP request sends sample robot location data with latitude, longitude, status, and satellite information.
```bash
curl -X 'POST' 'https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
-d '[
{
"data": {
"robot_id": "robot-001",
"latitude": 37.7749,
"longitude": -122.4194,
"num_satellites": 8,
"status": "active"
}
}
]'
```
Verify that your data has been ingested correctly by running an APL query in the Axiom UI.
## Set up your Hex project [#set-up-your-hex-project]
1. Create a new Hex project. For more information, see the [Hex documentation](https://learn.hex.tech/docs/getting-started/create-your-first-project).
2. Save your Axiom API token as a secret in Hex. This example uses the secret name `AXIOM_TOKEN`. For more information, see the [Hex documentation](https://learn.hex.tech/docs/explore-data/projects/environment-configuration/environment-views#secrets).
## Query data from Axiom [#query-data-from-axiom]
Write the Python code in your Hex notebook that retrieves data from Axiom. For example, customize the code below:
```python
import requests
import pandas as pd
from datetime import datetime, timedelta
import os
# Retrieve the API token from Hex secrets
axiom_token = os.environ.get("AXIOM_TOKEN")
# Define Axiom API endpoint and headers
base_url = "https://AXIOM_DOMAIN/v1/query/_apl"
headers = {
'Authorization': f'Bearer {axiom_token}',
'Content-Type': 'application/json',
'Accept': 'application/json',
'Accept-Encoding': 'gzip'
}
# Define the time range for your query
end_time = datetime.utcnow()
start_time = end_time - timedelta(days=3) # Get data from the last 3 days
# Construct the APL query
query = {
"apl": """DATASET_NAME
| project ['data.latitude'], ['data.longitude'], ['data.num_satellites'], ['data.robot_id'], ['data.status']""",
"startTime": start_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
"endTime": end_time.strftime("%Y-%m-%dT%H:%M:%SZ")
}
try:
# Send the request to Axiom API
response = requests.post(
f"{base_url}?format=tabular",
headers=headers,
json=query,
timeout=10
)
# Print request details for debugging
print("Request Details:")
print(f"URL: {base_url}?format=tabular")
print(f"Query: {query['apl']}")
print(f"Response Status: {response.status_code}")
if response.status_code == 200:
data = response.json()
if 'tables' in data:
table = data['tables'][0]
if table.get('columns') and len(table['columns']) > 0:
columns = [field['name'] for field in table['fields']]
rows = table['columns']
# Create DataFrame with proper column orientation
df = pd.DataFrame(list(zip(*rows)), columns=columns)
# Ensure data types are appropriate for mapping
df['data.latitude'] = pd.to_numeric(df['data.latitude'])
df['data.longitude'] = pd.to_numeric(df['data.longitude'])
df['data.num_satellites'] = pd.to_numeric(df['data.num_satellites'])
# Display the first few rows to verify our data
print("\nDataFrame Preview:")
display(df.head())
# Store the DataFrame for visualization
robot_locations = df
else:
print("\nNo data found in the specified time range.")
else:
print("\nNo tables found in response")
print("Response structure:", data.keys())
except Exception as e:
print(f"\nError: {str(e)}")
```
## Create map visualisation [#create-map-visualisation]
Create an interactive map visualization in Hex and customize it. For more information, see the [Hex documentation](https://learn.hex.tech/docs/explore-data/cells/visualization-cells/map-cells).
---
# Extensions
Source: https://axiom.co/docs/apps/introduction
This section walks you through a catalog of dedicated extensions that enrich your experience in Axiom’s Console.
To use standard APIs and other data shippers like the Elasticsearch Bulk API, Fluent Bit log processor or Fluentd log collector, go to [Send data](/send-data/methods) instead.
A pre-configured dashboard for logs sent from AWS Lambda.
A configuration path for sending data to Axiom via AWS PrivateLink.
A pre-configured dashboard for logs sent from Cloudflare Workers.
An integrated workflow for sending data from Cloudflare Logpush to Axiom.
Stream function executions and console logs from your Convex deployment to Axiom.
A data source plugin for visualizing data stored in Axiom in Grafana.
A recommended pathway to visualize data stored in Axiom in Hex.
An integreated workflow for sending data from Netlify to Axiom.
A setup that allows you to stream your Supabase project's raw log events directly to Axiom.
A pre-configured dashboard for audit and network flow logs sent from Tailscale.
A walkthrough on configuring resources in Axiom using Terraform.
A pre-configured dashboard for logs sent from Vercel.
---
# Enrich Axiom experience with AWS Lambda
Source: https://axiom.co/docs/apps/lambda
Use the Axiom Lambda Extension to enrich your Axiom organization with quick filters and a dashboard.
For information on how to send logs and platform events of your Lambda function to Axiom, see [Send data from AWS Lambda](/send-data/aws-lambda).
## What’s the Axiom Lambda Extension [#whats-the-axiom-lambda-extension]
AWS Lambda is a compute service that allows you to build applications and run your code at scale without provisioning or maintaining any servers.
Use the AWS Lambda Extension to collect Lambda logs, performance metrics, platform events, and memory usage from your Lambda functions. With the Axiom Lambda Extension, you can monitor Lambda performance and aggregate system-level metrics for your serverless applications and optimize lambda functions through easy-to-use automatic dashboards.
With the Axiom Lambda extension, you can:
* Monitor your Lambda functions and invocations.
* Get full visibility into your AWS Lambda events in minutes.
* Collect metrics and logs from your Lambda-based Serverless Applications.
* Track and view enhanced memory usage by versions, durations, and cold start.
* Detect and get alerts on Lambda event errors, Lambda request timeout, and low execution time.
## Comprehensive AWS Lambda dashboards [#comprehensive-aws-lambda-dashboards]
The Axiom AWS Lambda integration comes with a pre-built dashboard where you can see and group your functions with the versions and AWS resource that triggers them, making this the ideal starting point for getting an advanced view of the performance and health of your AWS Lambda serverless services and Lambda function events. The AWS Lambda dashboards automatically show up in Axiom through schema detection after installing the Axiom Lambda Extension.
These new zero-config dashboards help you spot and troubleshoot Lambda function errors. For example, if there’s high memory usage on your functions, you can spot the unusual delay from the max execution dashboard and filter your errors by functions, durations, invocations, and versions. With your Lambda version name, you can gain and expand your views on what’s happening in your Lambda event source mapping and invocation type.
## Monitor Lambda functions and usage in Axiom [#monitor-lambda-functions-and-usage-in-axiom]
Having real-time visibility into your function logs is important because any duration between sending your lambda request and the execution time can cause a delay and adds to customer-facing latency. You need to be able to measure and track your Lambda invocations, maximum execution time, minimum execution time, and all invocations by function.
The Axiom Lambda Extension gives you full visibility into the most important metrics and logs coming from your Lambda function out of the box without any further configuration required.
## Track cold start on your Lambda function [#track-cold-start-on-your-lambda-function]
A cold start occurs when there’s a delay between your invocation and runtime created during the initialization process. During this period, there’s no available function instance to respond to an invocation. With the Axiom built-in Serverless AWS Lambda dashboard, you can track and see the effect of cold start on your Lambda functions and its impact on every Lambda function. This data lets you know when to take actionable steps, such as using provisioned concurrency or reducing function dependencies.
## Optimize slow-performing Lambda queries [#optimize-slow-performing-lambda-queries]
Grouping logs with Lambda invocations and execution time by function provides insights into your events request and response pattern. You can extend your query to view when an invocation request is rejected and configure alerts to be notified on Serverless log patterns and Lambda function payloads. With the invocation request dashboard, you can monitor request function logs and see how your Lambda serverless functions process your events and Lambda queues over time.
## Detect timeout on your Lambda function [#detect-timeout-on-your-lambda-function]
Axiom Lambda function monitors let you identify the different points of invocation failures, cold-start delays, and AWS Lambda errors on your Lambda functions. With standard function logs like invocations by function, and Lambda cold start, monitoring the rate of your execution time can alert you to be aware of a significant spike whenever an error occurs in your Lambda function.
## Smart filters [#smart-filters]
Axiom Lambda Serverless Smart Filters lets you easily filter down to specific AWS Lambda functions or Serverless projects and use saved queries to get deep insights on how functions are performing with a single click.
---
# Connect Axiom with Netlify
Source: https://axiom.co/docs/apps/netlify
Netlify is a platform for building and deploying highly performant websites, e-commerce stores, and web apps. Netlify automatically builds your site and deploys it across its global edge network.
Integrating Axiom with Netlify allows you to stream traffic, function, and deployment logs directly to Axiom, giving you comprehensive observability over your Netlify projects. With real-time, unsampled data, you can monitor site performance, detect errors quickly, and make informed decisions about your Jamstack apps.
The Netlify integration supports all [edge deployments](/reference/edge-deployments). The integration sends data to your organization's default edge deployment automatically.
This integration is only available for Netlify customers on enterprise-level plans where Log Drains are supported. For more information, see [Netlify documentation](https://docs.netlify.com/manage/monitoring/log-drains/).
* [Create a Netlify account](https://app.netlify.com/signup).
## Setup [#setup]
1. In Axiom, click **Settings > Apps > Netlify > Install now**.
2. Click **Authorize**, and then copy the integration token.
3. In Netlify, select the site you want to integrate with Axiom. Go to **Logs & Metrics > Log Drains**, and then click **Enable a log drain**.
4. In **Log drain service**, select **Axiom**.
5. Select the log types to send to Axiom.
6. In **Region**, select the default edge deployment of your organization.
The edge deployment you select must match the default edge deployment of your organization. To determine your organization's default edge deployment, see [Edge deployments](/reference/edge-deployments).
7. In **Integration token**, paste the integration token from Axiom.
8. Click **Connect**.
For more information on Log Drains, see the [Netlify documentation](https://docs.netlify.com/manage/monitoring/log-drains/).
## Netlify dashboard [#netlify-dashboard]
Axiom displays the data it receives in a pre-built Netlify dashboard that delivers immediate, actionable insights into your sites' activity and performance.
This comprehensive overview includes:
* **Traffic logs**: Monitor traffic volume, request patterns, and site configurations.
* **Function logs**: Track serverless function executions, errors, and resource usage.
* **Live log streaming**: Stream your site and app logs live with filtering for important information.
With these insights, you can:
* Quickly detect site errors and unusual traffic patterns.
* Monitor serverless function performance and resource usage.
* Analyze your build and deployment processes.
* Drill down into specific events using APL queries.
* Fork the dashboard and build your own custom site monitors.
---
# Connect Axiom with Supabase
Source: https://axiom.co/docs/apps/supabase
Supabase is an open-source Firebase alternative that provides a Postgres database, authentication, instant APIs, edge functions, real-time subscriptions, storage, and vector embeddings.
Integrating Axiom with Supabase allows you to stream your project's raw log events directly to Axiom using Supabase's log drain feature. With your logs in Axiom, you can query, visualize, and monitor your Supabase projects in real time, giving you full observability over your database operations, authentication events, and edge functions.
* [Create a Supabase account](https://supabase.com/dashboard) and upgrade to a Pro, Team, or Enterprise plan.
## Setup [#setup]
1. In Supabase, go to your project's dashboard and navigate to **Project Settings > Log Drains**.
2. Click **Axiom**, and then create a new destination with the following configuration:
* **Dataset name**: Enter the name of the Axiom dataset you created.
* **API token**: Enter the Axiom API token you created.
3. Click **Save destination**.
Supabase sends logs to Axiom as JSON raw log events with timestamps formatted for Axiom's ingestion endpoint.
For more information on log drains, see the [Supabase documentation](https://supabase.com/docs/guides/telemetry/log-drains).
## Explore your Supabase data [#explore-your-supabase-data]
After setting up the log drain, your Supabase logs appear in the dataset you specified. Open the [Stream tab](/query-data/stream) in Axiom to watch events arrive in real time.
With your Supabase data in Axiom, you can:
* Track authentication events and detect unusual sign-in patterns.
* Observe edge function executions and errors.
* Set up [monitors](/monitor-data/monitors) to get alerted about issues in your Supabase project.
* Build [custom dashboards](/dashboards/create) tailored to your Supabase workload.
---
# Connect Axiom with Tailscale
Source: https://axiom.co/docs/apps/tailscale
Tailscale is a secure networking solution that allows you to create and manage a private network (tailnet), securely connecting all your devices.
Integrating Axiom with Tailscale allows you to stream your audit and network flow logs directly to Axiom seamlessly, unlocking powerful insights and analysis. Whether you’re conducting a security audit, optimizing performance, or ensuring compliance, Axiom’s Tailscale dashboard equips you with the tools to maintain a secure and efficient network, respond quickly to potential issues, and make informed decisions about your network configuration and usage.
* [Create a Tailscale account](https://login.tailscale.com/start).
## Setup [#setup]
1. In Tailscale, go to the [configuration logs page](https://login.tailscale.com/admin/logs) of the admin console.
2. Add Axiom as a configuration log streaming destination in Tailscale. For more information, see the [Tailscale documentation](https://tailscale.com/kb/1255/log-streaming?q=stream#add-a-configuration-log-streaming-destination).
## Tailscale dashboard [#tailscale-dashboard]
Axiom displays the data it receives in a pre-built Tailscale dashboard that delivers immediate, actionable insights into your tailnet’s activity and health.
This comprehensive overview includes:
* **Log type distribution**: Understand the balance between configuration audit logs and network flow logs over time.
* **Top actions and hosts**: Identify the most common network actions and most active devices.
* **Traffic visualization**: View physical, virtual, and exit traffic patterns for both sources and destinations.
* **User activity tracking**: Monitor actions by user display name, email, and ID for security audits and compliance.
* **Configuration log stream**: Access a detailed audit trail of all configuration changes.
With these insights, you can:
* Quickly identify unusual network activity or traffic patterns.
* Track configuration changes and user actions.
* Monitor overall network health and performance.
* Investigate specific events or users as needed.
* Understand traffic distribution across your tailnet.
---
# Connect Axiom with Terraform
Source: https://axiom.co/docs/apps/terraform
Axiom Terraform Provider lets you provision and manage Axiom resources (datasets, notifiers, monitors, and users) with Terraform. This means that you can programmatically create resources, access existing ones, and perform further infrastructure automation tasks.
Install the Axiom Terraform Provider from the [Terraform Registry](https://registry.terraform.io/providers/axiomhq/axiom/latest). To see the provider in action, check out the [example](https://github.com/axiomhq/terraform-provider-axiom/blob/main/example/main.tf).
This guide explains how to install the provider and perform some common procedures such as creating new resources and accessing existing ones. For the full API reference, see the [documentation in the Terraform Registry](https://registry.terraform.io/providers/axiomhq/axiom/latest/docs).
## Prerequisites [#prerequisites]
* [Sign up for a free Axiom account](https://app.axiom.co/register). All you need is an email address.
* [Create an advanced API token in Axiom](/reference/tokens#create-advanced-api-token) with the permissions to perform the actions you want to use Terraform for. For example, to use Terraform to create and update datasets, create the advanced API token with these permissions.
* [Create a Terraform account](https://app.terraform.io/signup/account).
* [Install the Terraform CLI](https://developer.hashicorp.com/terraform/cli).
## Install the provider [#install-the-provider]
To install the Axiom Terraform Provider from the [Terraform Registry](https://registry.terraform.io/providers/axiomhq/axiom/latest), follow these steps:
1. Add the following code to your Terraform configuration file. Replace `API_TOKEN` with the Axiom API token you have generated. For added security, store the API token in an environment variable.
```hcl
terraform {
required_providers {
axiom = {
source = "axiomhq/axiom"
}
}
}
provider "axiom" {
api_token = "API_TOKEN"
}
```
2. In your terminal, go to the folder of your main Terraform configuration file, and then run the command `terraform init`.
## Create new resources [#create-new-resources]
### Create dataset [#create-dataset]
To create a dataset in Axiom using the provider, add the following code to your Terraform configuration file. Customize the `name` and `description` fields.
```hcl
resource "axiom_dataset" "test_dataset" {
name = "test_dataset"
description = "This is a test dataset created by Terraform."
}
```
### Create notifier [#create-notifier]
To create a Slack notifier in Axiom using the provider, add the following code to your Terraform configuration file. Replace `SLACK_URL` with the webhook URL from your Slack instance. For more information on obtaining this URL, see the [Slack documentation](https://api.slack.com/messaging/webhooks).
```hcl
resource "axiom_notifier" "test_slack_notifier" {
name = "test_slack_notifier"
properties = {
slack = {
slack_url = "SLACK_URL"
}
}
}
```
To create a Discord notifier in Axiom using the provider, add the following code to your Terraform configuration file.
* Replace `DISCORD_CHANNEL` with the webhook URL from your Discord instance. For more information on obtaining this URL, see the [Discord documentation](https://docs.discord.com/developers/platform/webhooks#webhook-events).
* Replace `DISCORD_TOKEN` with your Discord API token. For more information on obtaining this token, see the [Discord documentation](https://docs.discord.com/developers/topics/oauth2#oauth2).
```hcl
resource "axiom_notifier" "test_discord_notifier" {
name = "test_discord_notifier"
properties = {
discord = {
discord_channel = "DISCORD_CHANNEL"
discord_token = "DISCORD_TOKEN"
}
}
}
```
To create an email notifier in Axiom using the provider, add the following code to your Terraform configuration file. Replace `EMAIL1` and `EMAIL2` with the email addresses you want to notify.
```hcl
resource "axiom_notifier" "test_email_notifier" {
name = "test_email_notifier"
properties = {
email= {
emails = ["EMAIL1","EMAIL2"]
}
}
}
```
For more information on the types of notifier you can create, see the [documentation in the Terraform Registry](https://registry.terraform.io/providers/axiomhq/axiom/latest/docs/resources/notifier).
### Create monitor [#create-monitor]
To create a monitor in Axiom using the provider, add the following code to your Terraform configuration file and customize it:
```hcl
resource "axiom_monitor" "test_monitor" {
depends_on = [axiom_dataset.test_dataset, axiom_notifier.test_slack_notifier]
# `type` can be one of the following:
# - "Threshold": For numeric values against thresholds. It requires `operator` and `threshold`.
# - "MatchEvent": For detecting specific events. It doesn’t require `operator` and `threshold`.
# - "AnomalyDetection": For detecting anomalies. It requires `compare_days` and `tolerance, operator`.
type = "Threshold"
name = "test_monitor"
description = "This is a test monitor created by Terraform."
apl_query = "['test_dataset'] | summarize count() by bin_auto(_time)"
interval_minutes = 5
# `operator` is required for threshold and anomaly detection monitors.
# Valid values are "Above", "AboveOrEqual", "Below", "BelowOrEqual".
operator = "Above"
range_minutes = 5
# `threshold` is required for threshold monitors
threshold = 1
# `compare_days` and `tolerance` are required for anomaly detection monitors.
# Uncomment the two lines below for anomaly detection monitors.
# compare_days = 7
# tolerance = 25
notifier_ids = [
axiom_notifier.test_slack_notifier.id
]
alert_on_no_data = false
notify_by_group = false
}
```
This example creates a monitor using the dataset `test_dataset` and the notifier `test_slack_notifier`. These are resources you have created and accessed in the sections above.
* Customize the `name` and the `description` fields.
* In the `apl_query` field, specify the APL query for the monitor.
For more information on these fields, see the [documentation in the Terraform Registry](https://registry.terraform.io/providers/axiomhq/axiom/latest/docs/resources/monitor).
### Create user [#create-user]
To create a user in Axiom using the provider, add the following code to your Terraform configuration file. Customize the `name`, `email`, and `role` fields.
```hcl
resource "axiom_user" "test_user" {
name = "test_user"
email = "test@abc.com"
role = "user"
}
```
## Access existing resources [#access-existing-resources]
### Access existing dataset [#access-existing-dataset]
To access an existing dataset, follow these steps:
1. Determine the ID of the Axiom dataset by sending a GET request to the [`datasets` endpoint of the Axiom API](/restapi/endpoints/getDatasets).
2. Add the following code to your Terraform configuration file. Replace `DATASET_ID` with the ID of the Axiom dataset.
```hcl
data "axiom_dataset" "test_dataset" {
id = "DATASET_ID"
}
```
### Access existing notifier [#access-existing-notifier]
To access an existing notifier, follow these steps:
1. Determine the ID of the Axiom notifier by sending a GET request to the `notifiers` endpoint of the Axiom API.
2. Add the following code to your Terraform configuration file. Replace `NOTIFIER_ID` with the ID of the Axiom notifier.
```hcl
data "axiom_dataset" "test_slack_notifier" {
id = "NOTIFIER_ID"
}
```
### Access existing monitor [#access-existing-monitor]
To access an existing monitor, follow these steps:
1. Determine the ID of the Axiom monitor by sending a GET request to the `monitors` endpoint of the Axiom API.
2. Add the following code to your Terraform configuration file. Replace `MONITOR_ID` with the ID of the Axiom monitor.
```hcl
data "axiom_monitor" "test_monitor" {
id = "MONITOR_ID"
}
```
### Access existing user [#access-existing-user]
To access an existing user, follow these steps:
1. Determine the ID of the Axiom user by sending a GET request to the `users` endpoint of the Axiom API.
2. Add the following code to your Terraform configuration file. Replace `USER_ID` with the ID of the Axiom user.
```hcl
data "axiom_user" "test_user" {
id = "USER_ID"
}
```
---
# Connect Axiom with Vercel
Source: https://axiom.co/docs/apps/vercel
Connect Axiom with Vercel to get the deepest observability experience for your Vercel projects.
Easily monitor data from requests, functions, and web vitals in one place. 100% live and 100% of your data, no sampling.
Axiom’s Vercel app ships with a pre-built dashboard and pre-installed monitors so you can be in complete control of your projects with minimal effort.
If you use Axiom Vercel integration, [annotations](/query-data/annotate-charts) are automatically created for deployments.
The Vercel integration supports all [edge deployments](/reference/edge-deployments). The integration sends data to your organization’s default edge deployment automatically, without any additional configuration.
## What’s Vercel? [#whats-vercel]
Vercel is a platform for frontend frameworks and static sites, built to integrate with your headless content, commerce, or database.
Vercel provides a frictionless developer experience to take care of the hard things: deploying instantly, scaling automatically, and serving personalized content around the globe.
Vercel makes it easy for frontend teams to develop, preview, and ship delightful user experiences, where performance is the default.
## Send logs to Axiom [#send-logs-to-axiom]
Simply install the [Axiom Vercel app from here](https://vercel.com/integrations/axiom) and be streaming logs and web vitals within minutes.
## App Overview [#app-overview]
### Request and function logs [#request-and-function-logs]
For both requests and serverless functions, Axiom automatically installs a [drain](https://vercel.com/docs/drains/using-drains) in your Vercel account to capture data live.
As users interact with your website, various logs are produced. Axiom captures all these logs and ingests them into the `vercel` dataset. You can stream and analyze these logs live, or use the pre-built Vercel Dashboard to get an overview of all the important metrics. When you’re ready, you can fork the dashboard and start building your own.
For function logs, if you call `console.log`, `console.warn` or `console.error` in your function, the output is also captured and made available as part of the log. You can use APL to easily search these logs.
## Web vitals [#web-vitals]
Axiom supports capturing and analyzing Web Vital data directly from your user’s browser without any sampling and with more data than is available elsewhere. It’s perfect to pair with Vercel’s in-built analytics when you want to get really deep into a specific problem or debug issues with a specific audience (user-agent, location, region, etc).
Web Vitals are only currently supported for Next.js websites. Expanded support is coming soon.
### Installation [#installation]
Perform the following steps to install Web Vitals:
1. In your Vercel project, run `npm install --save next-axiom`.
2. In `next.config.js`, wrap your NextJS config in `withAxiom` as follows:
```js
const { withAxiom } = require('next-axiom');
module.exports = withAxiom({
// ... your existing config
})
```
This proxies the Axiom ingest call to improve deliverability.
3. For Web Vitals, navigate to `app/layout.tsx` and add the `AxiomWebVitals` component:
```js
import { AxiomWebVitals } from 'next-axiom';
export default function RootLayout() {
return (
...
...
);
}
```
WebVitals are sent only from production deployments.
4. Deploy your site and watch data coming into your Axiom dashboard.
* To send logs from different parts of your app, make use of the provided logging functions. For example:
```js
log.info('Payment completed', { userID: '123', amount: '25USD' });
```
### Client Components [#client-components]
For Client Components, replace the `log` prop usage with the `useLogger` hook:
```js
'use client';
import { useLogger } from 'next-axiom';
export default function ClientComponent() {
const log = useLogger();
log.debug('User logged in', { userId: 42 });
return Logged in ;
}
```
### Server Components [#server-components]
For Server Components, create a logger and make sure to call flush before returning:
```js
import { Logger } from 'next-axiom';
export default async function ServerComponent() {
const log = new Logger();
log.info('User logged in', { userId: 42 });
// ... other operations ...
await log.flush();
return Logged in ;
}
```
### Route Handlers [#route-handlers]
For Route Handlers, wrapping your Route Handlers in `withAxiom` adds a logger to your request and automatically log exceptions:
```js
import { withAxiom, AxiomRequest } from 'next-axiom';
export const GET = withAxiom((req: AxiomRequest) => {
req.log.info('Login function called');
// You can create intermediate loggers
const log = req.log.with({ scope: 'user' });
log.info('User logged in', { userId: 42 });
return NextResponse.json({ hello: 'world' });
});
```
## Use Next.js 12 for Web Vitals [#use-nextjs-12-for-web-vitals]
If you’re using Next.js version 12, follow the instructions below to integrate Axiom for logging and capturing Web Vitals data.
In your `pages/_app.js` or `pages/_app.ts` and add the following line:
```js
export { reportWebVitals } from 'next-axiom';
```
## Upgrade to Next.js 13 from Next.js 12 [#upgrade-to-nextjs-13-from-nextjs-12]
If you plan on upgrading to Next.js 13, you’ll need to make specific changes to ensure compatibility:
* Upgrade the next-axiom package to version `1.0.0` or higher:
* Make sure any exported variables have the `NEXT_PUBLIC_ prefix`, for example,, `NEXT_PUBLIC_AXIOM_TOKEN`.
* In client components, use the `useLogger` hook instead of the `log` prop.
* For server-side components, you need to create an instance of the `Logger` and flush the logs before the component returns.
* For Web Vitals tracking, you’ll replace the previous method of capturing data. Remove the `reportWebVitals()` line and instead integrate the `AxiomWebVitals` component into your layout.
## Vercel Function logs 4KB limit [#vercel-function-logs-4kb-limit]
The Vercel 4KB log limit refers to a restriction placed by Vercel on the size of log output generated by serverless functions running on their platform. The 4KB log limit means that each log entry produced by your function should be at most 4 Kilobytes in size.
If your log output is larger than 4KB, you might experience truncation or missing logs. To log above this limit, you can send your function logs using [next-axiom](https://github.com/axiomhq/next-axiom).
## Parse JSON on the message field [#parse-json-on-the-message-field]
If you use a logging library in your Vercel project that prints JSON, your **message** field contains a stringified and therefore escaped JSON object.
* If your Vercel logs are encoded as JSON, they look like this:
```json
{
"level": "error",
"message": "{ \"message\": \"user signed in\", \"metadata\": { \"userId\": 2234, \"signInType\": \"sso-google\" }}",
"request": {
"host": "www.axiom.co",
"id": "iad1:iad1::sgh2r-1655985890301-f7025aa764a9",
"ip": "199.16.157.13",
"method": "GET",
"path": "/sign-in/google",
"scheme": "https",
"statusCode": 500,
"teamName": "AxiomHQ",
},
"vercel": {
"deploymentId": "dpl_7UcdgdgNsdgbcPY3Lg6RoXPfA6xbo8",
"deploymentURL": "axiom-bdsgvweie6au-axiomhq.vercel.app",
"projectId": "prj_TxvF2SOZdgdgwJ2OBLnZH2QVw7f1Ih7",
"projectName": "axiom-co",
"region": "iad1",
"route": "/signin/[id]",
"source": "lambda-log"
}
}
```
* The **JSON** data in your **message** would be:
```json
{
"message": "user signed in",
"metadata": {
"userId": 2234,
"signInType": "sso-google"
}
}
```
You can **parse** the JSON using the [parse\_json function](/apl/scalar-functions/string-functions#parse-json\(\)) and run queries against the **values** in the **message** field.
### Example [#example]
```kusto
['vercel']
| extend parsed = parse_json(message)
```
* You can select the field to **insert** into new columns using the [project operator](/apl/tabular-operators/project-operator)
```kusto
['vercel']
| extend parsed = parse_json('{"message":"user signed in", "metadata": { "userId": 2234, "SignInType": "sso-google" }}')
| project parsed["message"]
```
### More Examples [#more-examples]
* If you have **null values** in your data you can use the **isnotnull()** function
```kusto
['vercel']
| extend parsed = parse_json(message)
| where isnotnull(parsed)
| summarize count() by parsed["message"], parsed["metadata"]["userId"]
```
* Check out the [APL Documentation on how to use more functions](/apl/scalar-functions/string-functions) and run your own queries against your Vercel logs.
## Migrate from Vercel app to next-axiom [#migrate-from-vercel-app-to-next-axiom]
In May 2024, Vercel [introduced higher costs](https://axiom.co/blog/changes-to-vercel-log-drains) for using Vercel Drains. Because the Axiom Vercel app depends on Drains, using the next-axiom library can be the cheaper option to analyze telemetry data for higher volume projects.
To migrate from the Axiom Vercel app to the next-axiom library, follow these steps:
1. Delete the existing log drain from your Vercel project.
2. Delete `NEXT_PUBLIC_AXIOM_INGEST_ENDPOINT` from the environment variables of your Vercel project. For more information, see the [Vercel documentation](https://vercel.com/projects/environment-variables).
3. [Create a new dataset in Axiom](/reference/datasets), and [create a new advanced API token](/reference/tokens) with ingest permissions for that dataset.
4. Add the following environment variables to your Vercel project:
* `NEXT_PUBLIC_AXIOM_DATASET` is the name of the Axiom dataset where you want to send data.
* `NEXT_PUBLIC_AXIOM_TOKEN` is the Axiom API token you have generated.
5. In your terminal, go to the root folder of your Next.js app, and then run `npm install --save next-axiom` to install the latest version of next-axiom.
6. In the `next.config.ts` file, wrap your Next.js configuration in `withAxiom`:
```js
const { withAxiom } = require('next-axiom');
module.exports = withAxiom({
// Your existing configuration
});
```
For more configuration options, see the [documentation in the next-axiom GitHub repository](https://github.com/axiomhq/next-axiom).
## Send logs from Vercel preview deployments [#send-logs-from-vercel-preview-deployments]
To send logs from Vercel preview deployments to Axiom, enable preview deployments for the environment variable `NEXT_PUBLIC_AXIOM_INGEST_ENDPOINT`. For more information, see the [Vercel documentation](https://vercel.com/docs/projects/environment-variables/managing-environment-variables).
---
# Intelligence
Source: https://axiom.co/docs/console/intelligence
Axiom’s Console is evolving from a powerful space for human-led investigation to incorporate assistance that helps you reach insights faster. By combining Axiom's cost-efficient data platform with intelligence features, Axiom is transforming common workflows from reactive chores into proactive, machine-assisted actions.
This section covers the intelligence features built into the Axiom Console.
## Spotlight [#spotlight]
Spotlight is an interactive analysis feature that helps you find the root cause faster.
Rather than reviewing data from a notable period event-by-event, Spotlight allows you to compare a subset against baseline automatically. Spotlight analyzes every field to surface the most significant differences, turning manual investigation into a fast, targeted discovery process.
Learn more about [Spotlight](/console/intelligence/spotlight).
## AI agent integrations [#ai-agent-integrations]
Axiom provides two complementary approaches for integrating AI agents with your data:
### Axiom MCP Server [#axiom-mcp-server]
Axiom MCP Server is a [Model Context Protocol](https://modelcontextprotocol.io/) server implementation that enables AI agents to query your data using Axiom Processing Language (APL). MCP provides OAuth-based credential isolation where agents never see your tokens.
Learn more about [Axiom MCP Server](/console/intelligence/mcp-server).
### Skills [#skills]
Skills are instruction files that give AI coding agents specialized capabilities. Skills work by embedding methodology and context directly into the agent's prompt.
Learn more about [Axiom Skills for AI agents](/console/intelligence/skills).
## AI-assisted workflows [#ai-assisted-workflows]
Axiom also includes several features that leverage AI to accelerate common workflows.
### Natural language querying [#natural-language-querying]
Ask questions of your data in plain English and have Axiom translate your request directly into a valid Axiom Processing Language (APL) query. This lowers the barrier to exploring your data and makes it easier for everyone on your team to find the answers they need.
Learn more about [generating queries using AI](/query-data/query-editor#generate-query-using-natural-language).
### AI-powered dashboard generation [#ai-powered-dashboard-generation]
Instead of building dashboards from scratch, you can describe your requirements in natural language and have Axiom generate a complete dashboard for you instantly. Axiom analyzes your events and goals to select the most appropriate visualizations.
Learn more about [generating dashboards using AI](/dashboards/create#generate-dashboards-using-ai).
### AI summaries in Spotlight [#ai-summaries-in-spotlight]
Spotlight provides you with clear, concise explanations of the key differences between selected and baseline data. This makes it easier to spot root causes, understand anomalies, and communicate findings to your team, without needing to pore over every visualization.
---
# Configure dashboard elements
Source: https://axiom.co/docs/dashboard-elements/configure
When you create a chart, click to access the following options.
## Values [#values]
Specify how to treat missing or undefined values:
* **Auto:** This option automatically decides the best way to represent missing or undefined values in the data series based on the chart type and the rest of the data.
* **Ignore:** This option ignores any missing or undefined values in the data series. This means that the chart only displays the known, defined values.
* **Join adjacent values:** This option connects adjacent data points in the data series, effectively filling in any gaps caused by missing values. The benefit of joining adjacent values is that it can provide a smoother, more continuous visualization of your data.
* **Fill with zeros:** This option replaces any missing or undefined values in the data series with zero. This can be useful if you want to emphasize that the data is missing or undefined, as it causes a drop to zero in your chart.
## Variant [#variant]
Specify the chart type.
**Area:** An area chart displays the area between the data line and the axes, often filled with a color or pattern. Stacked charts provide the capability to design and implement intricate query dashboards while integrating advanced visualizations, enriching your logging experience over time.
**Bars:** A bar chart represents data in rectangular bars. The length of each bar is proportional to the value it represents. Bar charts can be used to compare discrete quantities, or when you have categorical data.
**Line:** A line chart connects individual data points into a continuous line, which is useful for showing logs over time. Line charts are often used for time series data.
## Y-Axis [#y-axis]
Specify the scale of the vertical axis.
**Linear:** A linear scale maintains a consistent scale where equal distances represent equal changes in value. This is the most common scale type and is useful for most types of data.
**Log:** A logarithmic (or log) scale represents values in terms of their order of magnitude. Each unit of distance on a log scale represents a tenfold increase in value. Log scales make it easy to see backend errors and compare values across a wide range.
## Annotations [#annotations]
Specify the types of annotations to display in the chart:
* Show all annotations
* Hide all annotations
* Selective determine the annotations types to display
---
# Create dashboard elements
Source: https://axiom.co/docs/dashboard-elements/create
Dashboard elements are the different visual elements that you can include in your dashboard to display your data and other information. For example, you can track key metrics, logs, and traces, and monitor real-time data flow.
You can create the following dashboard elements:
* [Filter bar](/query-data/filters)
* [Gauge](/dashboard-elements/gauge)
* [Heatmap](/dashboard-elements/heatmap)
* [Log stream](/dashboard-elements/log-stream)
* [Monitor list](/dashboard-elements/monitor-list)
* [Note](/dashboard-elements/note)
* [Pie](/dashboard-elements/pie-chart)
* [Scatter](/dashboard-elements/scatter-plot)
* [Statistic](/dashboard-elements/statistic)
* [Table](/dashboard-elements/table)
* [Time series](/dashboard-elements/time-series)
* [Top list](/dashboard-elements/top-list)
* [Spacer](/dashboard-elements/spacer)
To organize related dashboard elements into collapsible groups, [create dashboard sections](/dashboards/sections).
## Create dashboard elements [#create-dashboard-elements]
1. [Create a dashboard](/dashboards/create) or open an existing dashboard.
2. Click **Edit dashboard**.
3. Click **Add element** in the top right corner.
4. Choose the dashboard element from the list.
5. For charts, select one of the following:
* Click **Builder** to create your chart using a [visual query builder](#create-chart-using-visual-query-builder).
* Click **APL** to create your chart using the Axiom Processing Language (APL). Create a chart in the same way you create a chart in the [Editor of the Query tab](/query-data/query-editor).
6. Optional: [Configure chart options](/dashboard-elements/configure).
7. Optional: Set a custom time range that’s different from the dashboard’s time range.
8. Click **Save**.
The new element appears in your dashboard. At the bottom, click **Save** to save your changes to the dashboard.
## Create chart using visual query builder [#create-chart-using-visual-query-builder]
Use the query builder to create or edit queries for the selected dataset:
This component is a visual query builder that eases the process of building visualizations and segments of your data.
This guide walks you through the individual sections of the query builder.
### Time range [#time-range]
Every query has a start and end time and the time range component allows quick selection of common time ranges as well as the ability to input specific start and end timestamps:
* Use the **Quick Range** items to quickly select popular ranges
* Use the **Custom Start/End Date** inputs to select specific times
* Use the **Resolution** items to choose between various time bucket resolutions
### Against [#against]
When a time series visualization is selected, such as `count`, the **Against** menu is enabled and it’s possible to select a historical time to compare the results of your time range too.
For example, to compare the last hour’s average response time to the same time yesterday, select `1 hr` in the time range menu, and then select `-1D` from the **Against** menu:
The results look like this:
The dotted line represents results from the base date, and the totals table includes the comparative totals.
When you add `field` to the `group by` clause, the **time range against** values are attached to each `events`.
### Visualizations [#visualizations]
Axiom provides powerful visualizations that display the output of running aggregate functions across your dataset. The Visualization menu allows you to add these visualizations and, where required, input their arguments:
You can select a visualization to add it to the query. If a visualization requires an argument (such as the field and/or other parameters), the menu allows you to select eligible fields and input those arguments. Press Enter to complete the addition.
Click Visualization in the query builder to edit it at any time.
[Learn about supported visualizations](/query-data/visualizations)
### Filters [#filters]
Use the filter menu to attach filter clauses to your search.
Axiom supports AND/OR operators at the top-level as well as one level deep. This means you can create filters that would read as `status == 200 AND (method == get OR method == head) AND (user-agent contains Mozilla or user-agent contains Webkit)`.
Filters are divided up by the field type they operate on, but some may apply to more than one field type.
#### List of filters [#list-of-filters]
*String Fields*
* `==`
* `!=`
* `exists`
* `not-exists`
* `starts-with`
* `not-starts-with`
* `ends-with`
* `not-ends-with`
* `contains`
* `not-contains`
* `regexp`
* `not-regexp`
*Number Fields*
* `==`
* `!=`
* `exists`
* `not-exists`
* `>`
* `>=`
* `<`
* `<=`
*Boolean Fields*
* `==`
* `!=`
* `exists`
* `not-exists`
*Array Fields*
* `contains`
* `not-contains`
* `exists`
* `not-exists`
### Group by (segmentation) [#group-by-segmentation]
When visualizing data, it can be useful to segment data into specific groups to more clearly understand how the data behaves.
The Group By component enables you to add one or more fields to group events by:
### Other options [#other-options]
#### Order [#order]
By default, Axiom automatically chooses the best ordering for results. However, you can manually set the desired order through this menu.
#### Limit [#limit]
By default, Axiom chooses a reasonable limit for the query that has been passed in. However, you can control that limit manually through this component.
## Change element’s position [#change-elements-position]
To change element’s position on the dashboard, drag the title bar of the chart.
## Change element size [#change-element-size]
To change the size of the element, drag the bottom-right corner.
## Set custom time range [#set-custom-time-range]
You can set a custom time range for individual dashboard elements that’s different from the dashboard’s time range. For example, the dashboard displays data about the last 30 minutes but individual dashboard elements display data for different time ranges. This can be useful for visualizing the same chart or statistic for different time periods, among others.
To set a custom time range for a dashboard element:
1. In the top right of the dashboard element, click **More >** **Edit**.
2. In the top right above the chart, click .
3. Click **Custom**.
4. Choose one of the following options:
* Use the **Quick range** items to quickly select popular time ranges.
* Use the **Custom start/end date** fields to select specific times.
5. Click **Save**.
Axiom displays the new time range in the top left of the dashboard element.
### Set custom time range in APL [#set-custom-time-range-in-apl]
To set a custom time range for dashboard elements created with APL, you can use the [procedure above](#set-custom-time-range) or define the time range in the APL query:
1. In the top right of the dashboard element, click **More >** **Edit**.
2. In the APL query, specify the custom time range using the [where](/apl/tabular-operators/where-operator) operator. For example:
```kusto
| where _time > now(-6h)
```
3. Click **Run query** to preview the result.
4. Click **Save**.
Axiom displays in the top left of the dashboard element to indicate that its time range is defined in the APL query and might be different from the dashboard’s time range.
## Set custom comparison period [#set-custom-comparison-period]
You can set a custom comparison time period for individual dashboard elements that’s different from the dashboard’s. For example, the dashboard compares against data from yesterday but individual dashboard elements display data for different comparison periods.
To set a custom comparison period for a dashboard element:
1. In the top right of the dashboard element, click **More >** **Edit**.
2. In the top right above the chart, click **Compare period**.
3. Click **Custom**.
4. Choose one of the following options:
* Use the **Quick range** items to quickly select popular comparison periods.
* Use the **Custom time** field to select specific comparison periods.
5. Click **Save**.
Axiom displays the new comparison period in the top left of the dashboard element.
---
# Gauge
Source: https://axiom.co/docs/dashboard-elements/gauge
Gauge dashboard elements compare a single numeric value with colored segments. Use a gauge to show whether a current value, such as an error count or average response time, is within an expected range.
## Configure gauge segments [#configure-gauge-segments]
Use the segment rail to configure the ranges:
1. Enter exact boundary values in the inputs above the rail, or drag a boundary handle.
2. Click the scissors control inside a segment to split that segment.
3. Click the remove control below a shared boundary to merge the adjacent segments.
For a row-based view, expand **Edit segment values**. Enter the **From** and **To** values for each segment, or use **Split segment** and **Remove**. Each segment starts where the previous segment ends.
The selected color scale assigns a color to each segment.
## Configure gauge display [#configure-gauge-display]
Configure the gauge with the following options:
* **Sparkline**: Display the value over time inside the gauge.
* **Color scale**: Apply colors from green to red or from red to green.
* **Labels**: Display evenly spaced **Intervals**, segment **Boundaries**, or no labels. **Intervals** is the default.
* **Scale to time range**: Scale segment values in proportion to changes in the dashboard time range. Enable this option for count and sum thresholds that represent the chart's saved time range.
* **Custom units**: Display a unit after the gauge value.
## Query requirements [#query-requirements]
Gauge queries return one numeric series. In Builder, add exactly one aggregation and leave **Group by** empty. In APL, use a `summarize` or `count` statement. If the query contains `summarize`, return one expression from the last `summarize` statement and group only by `_time`, if at all.
## Example with Builder [#example-with-builder]
In Builder, summarize `req_duration_ms` with the `avg` aggregation.
## Example with APL [#example-with-apl]
```kusto
['sample-http-logs']
| summarize avg(req_duration_ms) by bin_auto(_time)
```
---
# Heatmap
Source: https://axiom.co/docs/dashboard-elements/heatmap
Heatmaps represent the distribution of numerical data by grouping values into ranges or buckets. Each bucket reflects a frequency count of data points that fall within its range. Instead of showing individual events or measurements, heatmaps give a clear view of the overall distribution patterns. This allows you to identify performance bottlenecks, outliers, or shifts in behavior. For instance, you can use heatmaps to track response times, latency, or error rates.
## Example with Builder [#example-with-builder]
## Example with APL [#example-with-apl]
```kusto
['sample-http-logs']
| summarize histogram(req_duration_ms, 15) by bin_auto(_time)
```
---
# Log stream
Source: https://axiom.co/docs/dashboard-elements/log-stream
The log stream dashboard element displays your logs as they come in real-time. Each log appears as a separate line with various details. The benefit of a log stream is that it provides immediate visibility into your system’s operations. When you debug an issue or trying to understand an ongoing event, the log stream allows you to see exactly what’s happening as it occurs.
## Example with Builder [#example-with-builder]
## Example with APL [#example-with-apl]
```kusto
['sample-http-logs']
| project method, status, content_type
```
---
# Monitor list
Source: https://axiom.co/docs/dashboard-elements/monitor-list
The monitor list dashboard element provides a visual overview of the monitors you specify. It offers a quick glance into important developments about the monitors such as their status and history.
* [Create a monitor](/monitor-data/monitors).
## Create monitor list [#create-monitor-list]
1. Go to the Dashboards tab and open the dashboard to which you want to add the monitor list.
2. Click **Edit dashboard**.
3. Click **Add element** in the top right corner.
4. Click **Monitor list** from the list.
5. In **Columns**, select the type of information you want to display for each monitor:
* **Status** displays if the monitor state is normal, triggered, or turned off.
* **History** provides a visual overview of the recent runs of the monitor. Green squares mean normal operation and red squares mean triggered state.
* **Dataset** is the name of the dataset on which the monitor operates.
* **Type** is the type of the monitor.
* **Notifiers** displays the notifiers connected to the monitor.
6. From the list, select the monitors you want to display on the dashboard.
7. Click **Save**.
The new element appears in your dashboard. At the bottom, click **Save** to save your changes to the dashboard.
---
# Note
Source: https://axiom.co/docs/dashboard-elements/note
The note dashboard element adds a textbox to your dashboard that you can customise to your needs. For example, you can provide context in a note about the other dashboard elements.
## Create note [#create-note]
1. Go to the Dashboards tab and open the dashboard to which you want to add the note.
2. Click **Edit dashboard**.
3. Click **Add element** in the top right corner.
4. Click **Note** from the list.
5. Enter your text on the left in [GitHub Flavored Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) format. You see the preview of the note dashboard element on the right.
6. Click **Save**.
The new element appears in your dashboard. At the bottom, click **Save** to save your changes to the dashboard.
---
# Pie chart
Source: https://axiom.co/docs/dashboard-elements/pie-chart
Pie charts can illustrate the distribution of different types of event data. Each slice represents the proportion of a specific value relative to the total. For example, a pie chart can show the breakdown of status codes in HTTP logs. This helps quickly identify the dominant types of status responses and assess the system’s health at a glance.
## Example with Builder [#example-with-builder]
## Example with APL [#example-with-apl]
```kusto
['sample-http-logs']
| summarize count() by status
```
---
# Scatter plot
Source: https://axiom.co/docs/dashboard-elements/scatter-plot
Scatter plots are used to visualize the correlation or distribution between two distinct metrics or logs. Each point in the scatter plot could represent a log entry, with the X and Y axes showing different log attributes (like request time and response size). The scatter plot chart can be created using the simple query builder or advanced query builder.
For example, plot response size against response time for an API to see if larger responses are correlated with slower response times.
## Example with Builder [#example-with-builder]
## Example with APL [#example-with-apl]
```kusto
['sample-http-logs']
| summarize avg(req_duration_ms), avg(resp_header_size_bytes) by resp_body_size_bytes
```
---
# Spacer
Source: https://axiom.co/docs/dashboard-elements/spacer
The spacer dashboard element adds empty space to your dashboard layout. Use spacers to create visual separation between dashboard elements, improve the organization of your dashboard, and control the positioning of other elements.
## Create spacer [#create-spacer]
1. Go to the Dashboards tab and open the dashboard to which you want to add the spacer.
2. Click **Edit dashboard**.
3. Click **Add element** in the top right corner.
4. Click **Spacer** from the list.
5. Click **Save**.
The new element appears in your dashboard. At the bottom, click **Save** to save your changes to the dashboard.
You can resize and reposition the spacer element by dragging its edges or title bar, just like other dashboard elements.
---
# Statistic
Source: https://axiom.co/docs/dashboard-elements/statistic
Statistics dashboard elements display a summary of the selected metrics over a given time period. For example, you can use a statistic dashboard element to show the average, sum, min, max, and count of response times or error counts.
## Example with Builder [#example-with-builder]
## Example with APL [#example-with-apl]
```kusto
['sample-http-logs']
| summarize avg(resp_body_size_bytes)
```
---
# Table
Source: https://axiom.co/docs/dashboard-elements/table
The table dashboard element displays a summary of any attributes from your metrics, logs, or traces in a sortable table format. Each row in the table could represent a different service, host, or other entity, with columns showing various attributes or metrics for that entity.
## Example with Builder [#example-with-builder]
## Example with APL [#example-with-apl]
```kusto
['sample-http-logs']
| summarize avg(resp_body_size_bytes) by bin_auto(_time)
```
---
# Time series
Source: https://axiom.co/docs/dashboard-elements/time-series
Time series charts show the change in your data over time which can help identify infrastructure issues, spikes, or dips in the data. This can be a simple line chart, an area chart, or a bar chart. A time series chart might be used to show the change in the volume of log events, error rates, latency, or other time-sensitive data.
## Example with Builder [#example-with-builder]
## Example with APL [#example-with-apl]
```kusto
['sample-http-logs']
| summarize count() by bin_auto(_time)
```
---
# Top list
Source: https://axiom.co/docs/dashboard-elements/top-list
The top list dashboard element displays the top results from your query, showing the most significant items based on your aggregation and grouping. It can display results as either a table of totals or as time series charts, depending on the aggregation type used.
## Example with Builder [#example-with-builder]
## Example with APL [#example-with-apl]
```kusto
['sample-http-logs']
| summarize count() by status
| top 10 by count_ desc
```
---
# Configure dashboards
Source: https://axiom.co/docs/dashboards/configure
## Select time range [#select-time-range]
When you select the time range, you specify the time interval for which you want to display data in the dashboard. Changing the time range affects the data displayed in all dashboard elements.
To select the time range:
1. In the top right, click **Time range**.
2. Choose one of the following options:
* Use the **Quick range** items to quickly select popular time ranges.
* Use the **Custom start/end date** fields to select specific times.
3. Click **Apply**.
## Select refresh rate [#select-refresh-rate]
Your dashboard regularly queries your data in the background to show the latest trends. The refresh rate is the time interval between these queries.
To select the refresh rate:
1. In the top right, click **Time range**.
2. Select one of the options in **Refresh rate**.
3. Click **Apply**.
Each time your dashboard refreshes, it runs a query on your data which results in query costs. Selecting a short refresh rate (such as 15 s) for a long time range (such as 90 days) means that your dashboard frequently runs large queries in the background. To optimize query costs, choose a refresh rate that’s appropriate for the time range of your dashboard.
## Share dashboards [#share-dashboards]
To specify who can access a dashboard:
1. In the top right, click **Share**.
2. Select one of the following:
* Select **Just Me** to make the dashboard private. Only you can access the dashboard.
* Select a group in your Axiom organization. Only members of the selected group can access the dashboard. For more information about groups, see [Access](/reference/settings).
* Select **Everyone** to make the dashboard accessible to all users in your Axiom organization.
3. At the bottom, click **Save** to save your changes to the dashboard.
The data that individual users see in the dashboard is determined by the datasets the users have access to. If a user has access to a dashboard but only to some of the datasets referenced in the dashboard’s charts, the user only sees data from the datasets they have access to.
### Share dashboards with time range [#share-dashboards-with-time-range]
To share a dashboard with a specific time range:
1. [Specify who can access a dashboard](#share-dashboards).
2. Determine the dashboard URL in your browser’s address bar.
3. Add time range parameters to the dashboard URL.
* For standard time ranges, add `?t_qr=RANGE` to the dashboard URL. Replace `RANGE` with the time range. For example, `?t_qr=7d` for the last 7 days.
* For custom time ranges, add `?t_ts=START&t_te=END` to the dashboard URL. Replace `START` and `END` with the start and end times in ISO 8601 format. For example, `?t_ts=2023-01-01T00:00:00.000Z&t_te=2023-01-07T23:59:59.999Z` for January 1-7, 2023.
* To compare your current time range against a previous period, add `?t_qr=RANGE&t_against=COMPARISON` or `?t_ts=START&t_te=END&t_against=COMPARISON` to the dashboard URL. Replace `COMPARISON` with the comparison period. For example, `?t_qr=24h&t_against=-1d` to compare the last 24 hours against the same time yesterday.
## Control display of annotations [#control-display-of-annotations]
To specify the types of annotations to display in all dashboard elements:
1. In the top right, click **Annotations**.
2. Select one of the following:
* Show all annotations
* Hide all annotations
* Selective determine the annotations types to display
3. At the bottom, click **Save** to save your changes to the dashboard.
## Set dashboard as homepage [#set-dashboard-as-homepage]
To set a dashboard as the homepage of your browser, click **Set as homepage** in the top right.
## Enter full screen [#enter-full-screen]
Full-screen mode is useful for displaying the dashboard on a TV or shared monitor.
To enter full-screen mode, click **Full screen** in the top right.
---
# Create dashboards
Source: https://axiom.co/docs/dashboards/create
Dashboards provide a single view into your data. They visualize collections of queries across multiple datasets in one place. Dashboards are easy to share, benefit from collaboration, and bring separate datasets together in a single view.
## Dashboards tab [#dashboards-tab]
The Dashboards tab lists the dashboards you have access to.
* The **Integrations** section lists prebuilt dashboards. Axiom automatically built these dashboards as part of the [apps that enrich your Axiom experience](/apps/introduction). The integration dashboards are read-only and you can’t edit them. To create a copy of an integration dashboard that you can edit, [fork the original dashboard](#fork-dashboards).
* The sections below list the private and shared dashboards you can access.
To open a dashboard, click a dashboard in the list.
## Create dashboards [#create-dashboards]
To create a dashboard, choose one of the following:
* [Generate a dashboard](#generate-dashboards-using-ai) using AI based on a natural-language prompt.
* [Create an empty dashboard](#create-empty-dashboards).
* [Fork an existing dashboard](#fork-dashboards). This is how you make a copy of prebuilt integration dashboards that you can’t directly edit.
* [Duplicate an existing dashboard](#duplicate-dashboards). This is how you make a copy of dashboards other than prebuilt integration dashboards.
After creating a dashboard:
* [Add dashboard elements](/dashboard-elements/create). For example, add a table or a time series chart.
* [Configure the dashboard](/dashboards/configure). For example, control who can access the dashboard and change the time range.
## Generate dashboards using AI [#generate-dashboards-using-ai]
Explain in your own words what you want to see in your dashboard and Axiom’s AI generates it in seconds.
1. Click the Dashboards tab.
2. In the top right corner, click **New dashboard**.
3. In Type, click **Generate dashboard**.
4. In Dataset, select the dataset from which you want to generate the dashboard.
5. Add a name and a description. Explain in detail what you want to see in your dashboard. The more specific you are, the closer the generated dashboard matches your expectations.
6. Click **Create dashboard**.
You can currently generate dashboards based on a single dataset. After generating the dashboard, you can edit it and add dashboard elements that rely on data from other datasets.
Axiom samples events in your dataset to understand the data schema and generate dashboards using AI. For the sampling to be meaningful, your dataset needs to have a minimum number of events. The exact number varies, but around 3,000 events in the last 7 days is usually sufficient.
For more flexibility, use an AI coding assistant with the [Build dashboards skill](/console/intelligence/skills/build-dashboards). This approach supports multiple data sources, iterative follow-up prompts, and using screenshots of existing dashboards from other tools as context.
## Create empty dashboards [#create-empty-dashboards]
1. Click the Dashboards tab.
2. In the top right corner, click **New dashboard**.
3. In Type, select **Empty dashboard**.
4. Add a name and a description.
5. Click **Create dashboard**.
## Fork dashboards [#fork-dashboards]
1. Click the Dashboards tab.
2. Find the dashboard in the list and right-click it.
3. Click **Fork dashboard**.
## Duplicate dashboards [#duplicate-dashboards]
1. Click the Dashboards tab.
2. Find the dashboard in the list and right-click it.
3. Click **Duplicate dashboard**.
## Delete dashboard [#delete-dashboard]
1. Click the Dashboards tab.
2. Find the dashboard in the list and right-click it.
3. Click **Delete dashboard**.
4. Click **Delete**.
---
# Dashboard sections
Source: https://axiom.co/docs/dashboards/sections
Sections group related dashboard elements under a shared header. Use sections to divide a large dashboard. Each section expands and collapses independently. When you collapse a section, Axiom stops running queries for the elements inside it. Expand the section to load the elements again.
## Create section [#create-section]
1. Go to the Dashboards tab and open the dashboard where you want to add the section.
2. Click **Edit dashboard**.
3. Click **Add element** in the top right corner.
4. In **Section**, click **Add section**.
5. Enter a name for the section.
6. Click **Add**.
7. Click **Save dashboard**.
The new section appears below dashboard elements that aren't assigned to a section.
## Add dashboard elements to section [#add-dashboard-elements-to-section]
To create a dashboard element directly in a section:
1. Click **Edit dashboard**.
2. In the section header, click **Add element to section**.
3. Select the type of dashboard element to add.
4. Configure the dashboard element, and then click **Save**.
5. Click **Save dashboard**.
To move an existing dashboard element into a section, enter edit mode and drag the element by its title bar into the section. Drag elements between sections in the same way. To remove an element from a section, drag it to the area above the first section.
Filter bars remain outside sections because they apply to the entire dashboard.
## Expand and collapse section [#expand-and-collapse-section]
Click the section header to expand or collapse the section. Collapsing a section stops the queries for its dashboard elements. Expanding the section runs the queries and displays the elements again.
Axiom remembers the expanded or collapsed state for each section in the current browser. This state doesn't change the dashboard for other users.
## Configure section [#configure-section]
To configure a section:
1. Click **Edit dashboard**.
2. In the section header, click **Section options**.
3. Choose one of the following options:
* Click **Rename** to change the section name.
* Click **Collapse by default** to initially display the section as collapsed. Viewers can still expand it.
* Click **Expand by default** to initially display the section as expanded. Viewers can still collapse it.
4. Click **Save dashboard**.
To change the order of sections, enter edit mode and drag the handle to the left of the section name. Sections always appear below dashboard elements that aren't assigned to a section.
## Delete section [#delete-section]
1. Click **Edit dashboard**.
2. In the section header, click **Section options**.
3. Choose one of the following options:
* Click **Delete section, keep charts** to remove the section and move its dashboard elements outside all sections.
* Click **Delete section and charts** to remove the section and all its dashboard elements from the dashboard.
4. Confirm the deletion.
5. Click **Save dashboard**.
**Delete section and charts** removes every dashboard element in the section when you save the dashboard.
---
# Send data from Honeycomb to Axiom
Source: https://axiom.co/docs/endpoints/honeycomb
This page explains how to send data from Honeycomb to Axiom.
## Configure Honeycomb [#configure-honeycomb]
In Honeycomb, specify the following environment variables:
* `APIKey` or `WriteKey` is your Honeycomb API token. For information, see the [Honeycomb documentation](https://docs.honeycomb.io/get-started/configure/environments/manage-api-keys/).
* `APIHost` is the target URL for the endpoint you have generated in Axiom by following the procedure above. For example, `https://opbizplsf8klnw.ingress.axiom.co`.
* `Dataset` is the name of the Axiom dataset where you want to send data.
## Examples [#examples]
### Send logs from Honeycomb using JavaScript [#send-logs-from-honeycomb-using-javascript]
```js
const Libhoney = require('libhoney');
const hny = new Libhoney({
writeKey: '',
dataset: '',
apiHost: '',
});
hny.sendNow({ message: 'Welcome to Axiom Endpoints!' });
```
### Send logs from Honeycomb using Python [#send-logs-from-honeycomb-using-python]
```py
import libhoney
libhoney.init(writekey="", dataset="", api_host="")
event = libhoney.new_event()
event.add_field("foo", "bar")
event.add({"message": "Welcome, to Axiom Endpoints!"})
event.send()
```
### Send logs from Honeycomb using Golang [#send-logs-from-honeycomb-using-golang]
```go
package main
import (
"github.com/honeycombio/libhoney-go"
)
func main() {
libhoney.Init(libhoney.Config{
WriteKey: "",
Dataset: "",
APIHost: "",
})
defer libhoney.Close() // Flush any pending calls to Honeycomb
var ev = libhoney.NewEvent()
ev.Add(map[string]interface{}{
"duration_ms": 155.67,
"method": "post",
"hostname": "endpoints",
"payload_length": 43,
})
ev.Send()
}
```
---
# Send data from Loki to Axiom
Source: https://axiom.co/docs/endpoints/loki
This page explains how to send data from Loki to Axiom.
## Configure Loki [#configure-loki]
In Loki, specify the following environment variables:
* `host` or `url` is the target URL for the endpoint you have generated in Axiom by following the procedure above. For example, `https://opbizplsf8klnw.ingress.axiom.co`.
* Optional: Use `labels` or `tags` to specify labels or tags for your app.
## Examples [#examples]
### Send logs from Loki using JavaScript [#send-logs-from-loki-using-javascript]
```js
const { createLogger, transports, format, } = require("winston");
const LokiTransport = require("winston-loki");
let logger;
const initializeLogger = () => {
if (logger) {
return;
}
logger = createLogger({
transports: [
new LokiTransport({
host: "$LOKI_ENDPOINT_URL",
labels: { app: "axiom-loki-endpoint" },
json: true,
format: format.json(),
replaceTimestamp: true,
onConnectionError: (err) => console.error(err),
}),
new transports.Console({
format: format.combine(format.simple(), format.colorize()),
}),
],
});
};
initializeLogger()
logger.info("Starting app...");
```
### Send logs from Loki using Python [#send-logs-from-loki-using-python]
```py
import logging
import logging_loki
# Create a handler
handler = logging_loki.LokiHandler(
url='$LOKI_ENDPOINT_URL',
tags={'app': 'axiom-loki-py-endpoint'},
version='1',
)
# Create a logger
logger = logging.getLogger('loki')
# Add the handler to the logger
logger.addHandler(handler)
# Log some messages
logger.info('Hello, world from Python!')
logger.warning('This is a warning')
logger.error('This is an error')
```
---
# Send data from Splunk to Axiom
Source: https://axiom.co/docs/endpoints/splunk
This page explains how to send data from Splunk to Axiom.
## Configure Splunk [#configure-splunk]
In Splunk, specify the following environment variables:
* `token` is your Splunk API token. For information, see the [Splunk documentation](https://help.splunk.com/en/splunk-observability-cloud/administer/authentication-and-security/authentication-tokens/api-access-tokens).
* `url` or `host` is the target URL for the endpoint you have generated in Axiom by following the procedure above. For example, `https://opbizplsf8klnw.ingress.axiom.co`.
## Examples [#examples]
### Send logs from Splunk using JavaScript [#send-logs-from-splunk-using-javascript]
```js
var SplunkLogger = require('splunk-logging').Logger;
var config = {
token: '$SPLUNK_TOKEN',
url: '$AXIOM_ENDPOINT_URL',
};
var Logger = new SplunkLogger({
token: config.token,
url: config.url,
host: '$AXIOM_ENDPOINT_URL',
});
var payload = {
// Message can be anything; doesn’t have to be an object
message: {
temperature: '70F',
chickenCount: 500,
},
};
console.log('Sending payload', payload);
Logger.send(payload, function (err, resp, body) {
// If successful, body will be { text: 'Success', code: 0 }
console.log('Response from Splunk', body);
});
```
### Send logs from Splunk using Python [#send-logs-from-splunk-using-python]
* Your Splunk deployment `port` and `index` values are required in your Python code.
```py
import logging
from splunk_handler import SplunkHandler
splunk = SplunkHandler(
host="$AXIOM_SPLUNK_ENDPOINT_URL",
port='8088',
token='',
index='main'
)
logging.getLogger('').addHandler(splunk)
logging.warning('Axiom endpoints!')
```
### Send logs from Splunk using Golang [#send-logs-from-splunk-using-golang]
```js
package main
import "github.com/docker/docker/daemon/logger/splunk"
func main() {
// Create new Splunk client
splunk := splunk.NewClient(
nil,
"https://{$AXIOM_SPLUNK_ENDPOINT}:8088/services/collector",
"{your-token}",
"{your-source}",
"{your-sourcetype}",
"{your-index}"
)
err := splunk.Log(
interface{"msg": "axiom endpoints", "msg2": "endpoints"}
)
if err != nil {
return err
}
err = splunk.LogWithTime(
time.Now(),
interface{"msg": "axiom endpoints", "msg2": "endpoints"}
)
if err != nil {
return err
}
```
---
# Frequently asked questions
Source: https://axiom.co/docs/get-help/faq
{/* vale off */}
This page aims to offer a deeper understanding of Axiom. If you can’t find an answer to your questions, please feel free to [contact our team](https://axiom.co/contact).
## What’s Axiom? [#whats-axiom]
Axiom is a log management and analytics solution that reduces the cost and management overhead of logging as much data as you want.
With Axiom, organizations no longer need to choose between their data and their costs. Axiom has been built from the ground up to allow for highly efficient data ingestion and storage, and then a zero-to-infinite query scaling that allows you to query all your data, all the time.
Organizations use Axiom for continuous monitoring and observability, as well as an event store for running analytics and deriving insights from all their event data.
Axiom consists of a datastore and a user experience that work in tandem to provide a completely unique log-management and analytics experience.
## How is Axiom deployed? [#how-is-axiom-deployed]
Axiom offers Axiom Cloud, a fully managed cloud service with usage-based pricing.
## How is Axiom different than other logging solutions? [#how-is-axiom-different-than-other-logging-solutions]
At Axiom, our goal is that no organization has to ignore or delete a single piece of data no matter its source: logs, events, frontend, backends, audits, etc.
We found that existing solutions would place restrictions on how much data can be collected either on purpose or as a side-effect of their architectures.
For example, state of the art in logging is running stateful clusters that need shared knowledge of ingestion and will use a mixture of local SSD-based storage and remote object storage.
### Side-effects of legacy vendors [#side-effects-of-legacy-vendors]
1. There is a non-trivial cost in increasing your data ingest as clusters need to be scaled and more SSD storage and IOPs need to be provided
2. The choice needs to be made between hot and cold data, and also what is archived. Now your data is in 2-3 different places and queries can be fast or slow depending on where the data is
The end result is needing to carefully consider all data that’s ingested, and putting limits and/or sampling to control the DevOps and cost burden.
### The ways Axiom is different [#the-ways-axiom-is-different]
1. Decoupled ingest and querying pipelines
2. Stateless ingest pipeline that requires minimal compute/memory to storage as much as 1.5 TB/day per vCPU
3. Ingests all data into object storage, enabling the cheapest storage possible for all ingested data
4. Enables querying scale-out with cloud functions, requiring no constantly running servers waiting for a query to be processed. Instead, enjoy zero-to-infinity querying instantly
### The benefits of Axiom’s approach [#the-benefits-of-axioms-approach]
1. The most efficient ingestion pipeline for massive amounts of data
2. Store more data for less by exclusively using inexpensive object storage for all data
3. Query data that’s 10 milliseconds or 10 years old at any time
4. Reduce the total cost of ownership of your log management and analytics pipelines with simple scale and maintenance that Axiom provides
5. Free your organization to do more with it’s data
## How long can I retain data with Axiom? [#how-long-can-i-retain-data-with-axiom]
The free forever Personal plan provides a generous 30 days of retention.
The Axiom Cloud and the Bring Your Own Cloud plans allow you to customize the data retention period to your needs, with the option for “Forever” retention so your organization has access to all its data, all the time.
For more information, see [Pricing](https://axiom.co/pricing).
## Can I try Axiom for free? [#can-i-try-axiom-for-free]
Yes. The Personal plan is free forever with a generous allowance. It’s available to all customers.
With unlimited users included, the Axiom Cloud plan starting at $25/month is a great choice for growing companies and for enterprise organizations who want to run a proof-of-concept.
For more information, see [Pricing](https://axiom.co/pricing).
## How is Axiom licensed? [#how-is-axiom-licensed]
The Axiom Cloud plan is billed on a monthly basis.
For the Bring Your Own Cloud plan, the platform fee is billed on an annual basis.
For more information, see [Pricing](https://axiom.co/pricing).
---
# Glossary of key Axiom terms
Source: https://axiom.co/docs/getting-started-guide/glossary
{/* vale off */}
[A](#a) B [C](#c) [D](#d) [E](#e) F G H [I](#i) K [L](#l) [M](#m) [N](#n) [O](#o) [P](#p) [Q](#q) [R](#r) [S](#s) [T](#t) U [V](#v) W X Y Z
{/* vale on */}
## A [#a]
### Annotation [#annotation]
Annotations are visual elements that add context to the trends displayed in dashboard elements and make it easier to investigate issues. For more information, see [Annotate dashboard elements](/query-data/annotate-charts).
### Anomaly monitor [#anomaly-monitor]
Anomaly monitors allow you to aggregate your event data and compare the results of this aggregation to what can be considered normal for the query. When the results are too much above or below the value that Axiom expects based on the event history, the monitor enters the alert state. The monitor remains in the alert state until the results no longer deviate from the expected value. This can happen without the results returning to their previous level if they stabilize around a new value. An anomaly monitor sends you a notification each time it enters or exits the alert state.
For more information, see [Anomaly monitors](/monitor-data/anomaly-monitors).
### API [#api]
The Axiom API allows you to ingest structured data logs, handle queries, and manage your deployments.
For more information, see [Introduction to Axiom API](/restapi/introduction).
### API token [#api-token]
See [Tokens](#token).
### App [#app]
Axiom's dedicated apps enrich your Axiom organization by integrating into popular external services and providing out-of-the-box features such as prebuilt dashboards.
For more information, see [Introduction to apps](/apps/introduction).
### Axiom [#axiom]
Axiom represents the next generation of business intelligence. Designed and built for the cloud, Axiom is an event platform for logs, traces, and all technical data.
Axiom efficiently ingests, stores, and queries vast amounts of event data from any source at a fraction of the cost. The Axiom platform is built for unmatched efficiency, scalability, and performance.
### Axiom Cloud [#axiom-cloud]
Axiom Cloud is a deployment option and pricing plan with a fully managed cloud service and usage-based pricing.
### Axiom MCP Server [#axiom-mcp-server]
Axiom MCP Server is a Model Context Protocol (MCP) server implementation that enables AI agents to query your data using Axiom Processing Language (APL). It supports MCP tools for executing queries, listing datasets, retrieving schemas, and accessing dashboards and monitors. Axiom MCP Server allows AI agents like Claude and Cursor to interact directly with your Axiom data.
For more information, see [Axiom MCP Server](/console/intelligence/mcp-server).
### Axiom Processing Language (APL) [#axiom-processing-language-apl]
Axiom Processing Language (APL) is a query language that's perfect for getting deeper insights from your data. Whether logs, events, analytics, or similar, APL provides the flexibility to filter, manipulate, and summarize your data exactly the way you need it.
For more information, see [Introduction to APL](/apl/introduction).
## C [#c]
### CLI [#cli]
Axiom's command line interface (CLI) is an Axiom tool that lets you test, manage, and build your Axiom organizations by typing commands on the command-line. You can use the command line to ingest data, manage authentication state, and configure multiple organizations.
For more information, see [Introduction to CLI](/reference/cli).
### Console [#console]
Axiom Console is the web user interface for data management, querying, dashboarding, monitoring, and user administration. It provides tools for exploring data, building queries, creating dashboards, configuring monitors, and managing access controls.
## D [#d]
### Dashboard [#dashboard]
Dashboards allow you to visualize collections of queries across multiple datasets in one place. Dashboards are easy to share, benefit from collaboration, and bring separate datasets together in a single view.
For more information, see [Create dashboards](/dashboards/create).
### Dashboard element [#dashboard-element]
Dashboard elements are the different visual elements that you can include in your dashboard to display your data and other information. For example, you can track key metrics, logs, and traces, and monitor real-time data flow.
For more information, see [Create dashboard elements](/dashboard-elements/create).
### Dataset [#dataset]
Axiom's datastore is tuned for the efficient collection, storage, and analysis of timestamped event data. An individual piece of data is an event, and a dataset is a collection of related events. Datasets contain incoming event data.
For more information, see [Datasets](/reference/datasets).
## E [#e]
### Edge deployment [#edge-deployment]
An edge deployment is an infrastructure location where Axiom stores your event data at rest. When you create an organization, you choose a primary edge deployment. This determines the default location where your data is ingested, stored, and queried. Edge deployments will provide flexibility for data jurisdiction requirements while maintaining unified management through Axiom's Console.
For more information, see [Edge deployments](/reference/edge-deployments).
### Event [#event]
An event is a granular record capturing a specific action or interaction within a system, often represented as key-value pairs. It's the smallest unit of information detailing what occurred, who, or what was involved, and potentially when and where it took place. In Axiom's context, events are timestamped records, originating from human, machine, or sensor interactions, providing a foundational data point that informs a broader view of activities across different business units, from product, through security, to marketing, and more.
### EventDB [#eventdb]
EventDB is the foundation of Axiom's platform for ingesting, storing, and querying timestamped event data at scale. It features a multi-layered ingestion system, custom block-based storage format on object storage with extreme compression, and serverless ephemeral runtimes for query execution.
For more information, see [Architecture](/platform-overview/architecture).
## I [#i]
### Intelligence [#intelligence]
Intelligence refers to Axiom's suite of AI-powered features that accelerate insights and automate data analysis. This includes Spotlight for automatic root cause analysis, natural language querying, AI-powered dashboard generation, and the Axiom MCP Server for AI agent integration.
For more information, see [Intelligence](/console/intelligence).
## L [#l]
### Log [#log]
A log is a structured or semi-structured data record typically used to document actions or system states over time, primarily for monitoring, debugging, and auditing. Traditionally formatted as text entries with timestamps and message content, logs have evolved to include standardized key-value structures, making them easier to search, interpret, and correlate across distributed systems. In Axiom, logs represent historical records designed for consistent capture, storage, and collaborative analysis, allowing for real-time visibility and troubleshooting across services.
For more information, see [Axiom for observability](/getting-started-guide/observability).
## M [#m]
### Match monitor [#match-monitor]
Match monitors allow you to continuously filter your log data and send you matching events. Axiom sends a notification for each matching event. By default, the notification message contains the entire matching event in JSON format. When you define your match monitor using APL, you can control which event attributes to include in the notification message.
For more information, see [Match monitors](/monitor-data/match-monitors).
### Metric [#metric]
A metric is a quantitative measurement collected at specific time intervals, reflecting the state or performance of a system or component. Metrics focus on numeric values, such as CPU usage or memory consumption, enabling aggregation, trend analysis, and alerting based on thresholds. Within Axiom, metrics are data points associated with timestamps, labels, and values, designed to monitor resource utilization or performance. Metrics enable predictive insights by identifying patterns over time, offering foresight into system health and potential issues before they escalate.
For more information, see [Axiom for observability](/getting-started-guide/observability).
### MetricsDB [#metricsdb]
MetricsDB is Axiom's dedicated metrics datastore purpose-built for high-cardinality time-series data. Unlike traditional metrics datastores that struggle with dimensional complexity, MetricsDB is designed to handle high numbers of unique tag combinations efficiently without performance degradation or cost penalties.
For more information, see [Architecture](/platform-overview/architecture).
### Model Context Protocol (MCP) [#model-context-protocol-mcp]
Model Context Protocol (MCP) is an open standard that enables AI agents to interact with external data sources and tools. Axiom implements MCP through the Axiom MCP Server, allowing AI agents to query datasets, list resources, and execute APL queries programmatically.
For more information, see [Axiom MCP Server](/console/intelligence/mcp-server).
### Monitor [#monitor]
A monitor is a background task that periodically runs a query that you define. For example, it counts the number of error messages in your logs over the previous 5 minutes. A notifier defines how Axiom notifies you about the monitor output. For example, Axiom can send you an email.
You can use the following types of monitor:
* [Anomaly monitors](#anomaly-monitor) aggregate event data over time and look for values that are unexpected based on the event history. When the results of the aggregation are too high or low compared to the expected value, Axiom sends you an alert.
* [Match monitors](#match-monitor) filter for key events and send them to you.
* [Threshold monitors](#threshold-monitor) aggregate event data over time. When the results of the aggregation cross a threshold, Axiom sends you an alert.
For more information, see [Introduction to monitors](/monitor-data/monitors).
## N [#n]
### Notifier [#notifier]
A monitor is a background task that periodically runs a query that you define. For example, it counts the number of error messages in your logs over the previous 5 minutes. A notifier defines how Axiom notifies you about the monitor output. For example, Axiom can send you an email.
For more information, see [Introduction to notifiers](/monitor-data/notifiers-overview).
## O [#o]
### Observability [#observability]
Observability is a principle in software engineering and systems monitoring that focuses on the ability to understand and diagnose the internal state of a system by examining the data it generates, such as logs, metrics, and traces. It goes beyond traditional monitoring by giving teams the power to pinpoint and resolve issues, optimize performance, and understand user behaviors across complex, interconnected services. Observability leverages various types of [event data](#event) to provide granular insights that span everything from simple log messages to multi-service transactions (traces) and performance metrics.
Traditionally, observability has been associated with three pillars:
* Logs capture individual events or errors.
* Metrics provide quantitative data over time, like CPU usage.
* Traces represent workflows across microservices.
However, modern observability expands on this by aggregating diverse data types from engineering, product, marketing, and security functions, all of which contribute to understanding the deeper "why" behind user interactions and system behaviors. This holistic view, in turn, enables real-time diagnostics, predictive analyses, and proactive issue resolution.
In essence, observability transforms raw event data into actionable insights, helping organizations not only to answer "what happened?" but also to delve into "why it happened" and "what might happen next."
For more information, see [Axiom for observability](/getting-started-guide/observability).
### OpenTelemetry [#opentelemetry]
OpenTelemetry (OTel) is an open-source observability framework for cloud-native software. It provides a standardized way to collect and export telemetry data (logs, traces, and metrics) from applications. Axiom supports OpenTelemetry and allows you to send data from any existing OpenTelemetry shipper, library, or tool.
For more information, see [Send OpenTelemetry data to Axiom](/send-data/opentelemetry).
## P [#p]
### Personal access token (PAT) [#personal-access-token-pat]
See [Tokens](#token).
### Playground [#playground]
Axiom Playground is an interactive sandbox environment where you can quickly try out Axiom's capabilities.
To try out Axiom, go to the [Axiom Playground](https://play.axiom.co/).
## Q [#q]
### Query [#query]
In Axiom, a query is a specific, structured request used to get deeper insights into your data. It typically involves looking for information based on defined parameters like keywords, date ranges, or specific fields. The intent of a query is precision: to locate, analyze, or manipulate specific subsets of data within vast data structures, enhancing insights into various operational aspects or user behaviors.
Querying enables you to filter, manipulate, extend, and summarize your data.
### Query-hours [#query-hours]
When you run queries, your usage of the Axiom platform is measured in query-hours. The unit of this measurement is GB-hours which reflects the duration (measured in milliseconds) serverless functions are running to execute your query multiplied by the amount of memory (GB) allocated to execution. This metric is important for monitoring and managing your usage against the monthly allowance included in your plan.
For more information, see [Optimize usage](/reference/optimize-usage).
## R [#r]
### Role-Based Access Control (RBAC) [#role-based-access-control-rbac]
Role-Based Access Control (RBAC) allows you to manage and restrict access to your data and resources efficiently.
For more information, see [Access](/reference/settings).
## S [#s]
### Span [#span]
A span represents an individual action or operation within a trace. Spans capture the duration and metadata of specific operations as a request flows through a distributed system. Related spans are grouped together using trace IDs to form complete traces that show the full path of a request.
For more information, see [Trace](#trace).
### Spotlight [#spotlight]
Spotlight is an interactive analysis feature that helps you find the root cause of issues faster. It allows you to highlight a region of event data and automatically identifies how it deviates from baseline across different fields. Instead of manually crafting queries to investigate anomalies, Spotlight analyzes every field in your data and presents the most significant differences.
For more information, see [Spotlight](/console/intelligence/spotlight).
### Stream [#stream]
The Stream tab in Axiom Console allows you to inspect individual events and watch as they're ingested live. It provides real-time visibility into your data, enabling you to filter events, view event details, and investigate issues as they happen.
For more information, see [Stream data](/query-data/stream).
## T [#t]
### Threshold monitor [#threshold-monitor]
Threshold monitors allow you to periodically aggregate your event data and compare the results of this aggregation to a threshold that you define. When the results cross the threshold, the monitor enters the alert state. The monitor remains in the alert state until the results no longer cross the threshold. A threshold monitor sends you a notification each time it enters or exits the alert state.
For more information, see [Threshold monitors](/monitor-data/threshold-monitors).
### Token [#token]
You can use the Axiom API and CLI to programmatically ingest and query data, and manage settings and resources. For example, you can create new API tokens and change existing datasets with API requests. To prove that these requests come from you, you must include forms of authentication called tokens in your API requests. Axiom offers two types of tokens:
* API tokens let you control the actions that can be performed with the token. For example, you can specify that requests authenticated with a certain API token can only query data from a particular dataset.
* Personal access tokens (PATs) provide full control over your Axiom account. Requests authenticated with a PAT can perform every action you can perform in Axiom.
For more information, see [Tokens](/reference/tokens).
### Trace [#trace]
A trace is a sequence of events that captures the path and flow of a single request as it navigates through multiple services or components within a distributed system. Utilizing trace IDs to group-related spans (individual actions or operations within a request), traces enable visibility into the lifecycle of a request, illustrating how it progresses, where delays or errors may occur, and how components interact. By connecting each event in the request journey, traces provide insights into system performance, pinpointing bottlenecks and latency.
## V [#v]
### Virtual fields [#virtual-fields]
Virtual fields allow you to derive new values from your data in real time, eliminating the need for up-front data structuring. Instead of transforming data during ingestion, you can use virtual fields to manipulate data during queries using APL expressions. Virtual fields can be used for filtering, visualization, and segmentation.
Virtual fields are query-time representations only. They don't create new data in storage.
For more information, see [Virtual fields](/query-data/virtual-fields).
---
# Axiom for observability
Source: https://axiom.co/docs/getting-started-guide/observability
Axiom helps you leverage the power of timestamped machine data. A common use case of machine data is observability (o11y) in the field of software engineering. Observability is the ability to explain what’s happening inside a software system by observing it from the outside. It allows you to understand the behavior of systems based on their outputs such as telemetry data, which is a type of machine data.
Software engineers most often work with timestamped machine data in the form of logs, metrics, and traces. However, Axiom believes that machine data reflects a much broader range of interactions, crossing boundaries from engineering to product management, security, and beyond.
## Types of machine data in observability [#types-of-machine-data-in-observability]
Traditionally, observability has been associated with three pillars, each effectively a specialized view of machine data:
* **Logs**: Logs record discrete events, such as error messages or access requests, typically associated with engineering or security.
* **Traces**: Traces track the path of requests through a system, capturing each step’s duration. By linking related spans within a trace, developers can identify bottlenecks and dependencies.
* **Metrics**: Metrics quantify state over time, recording data like CPU usage or user count at intervals. Product or engineering teams can then monitor and aggregate these values for performance insights.
Axiom supports all three types of data, allowing for fine-grained, efficient tracking across the three pillars.
## Logs, traces, and events [#logs-traces-and-events]
Axiom excels at collecting, storing, and analyzing timestamped event data.
For logs and traces, Axiom offers unparalleled efficiency and query performance. You can send logs and traces to Axiom from a wide range of popular sources. For more information, see [Send data to Axiom](/send-data/methods).
## Metrics [#metrics]
For metrics, Axiom offers a dedicated metrics datastore with query performance optimized for high-cardinality time-series data. You can send OpenTelemetry metrics to Axiom and query them using MPL (Metrics Processing Language). For more information, see [Metrics](/query-data/metrics).
---
# Axiom for product analytics
Source: https://axiom.co/docs/getting-started-guide/product-analytics
Axiom helps you leverage the power of timestamped event data. Axiom believes that event data reflects a broad range of interactions, crossing boundaries from engineering to product management, security, and beyond.
This page explains how you can leverage the power of event data for the product analytics use case.
In product analytics, the ability to harness and interpret data effectively can determine the success of a product. Axiom allows product analytics to leverage the power of timestamped event data and easily read every single event. Your organization can gain actionable insights, optimize user experiences, and drive product innovation.
## Why event data matters in product analytics [#why-event-data-matters-in-product-analytics]
Event data captures the actions and interactions users have with a product over time. From button clicks and page views to error events and feature usage, every timestamped event tells a story about user behavior. Axiom’s platform is specifically designed to process and analyze these granular datasets, making it an indispensable tool for product teams aiming to do the following:
* **Understand user behavior:** By tracking and analyzing event streams, Axiom provides a clear picture of how users engage with your product.
* **Identify trends and patterns:** Time-series analysis reveals emerging trends, helping teams anticipate user needs and adjust strategies proactively.
* **Optimize product features:** Pinpoint which features drive the most value and identify friction points that need improvement.
## Key features of Axiom for product analytics [#key-features-of-axiom-for-product-analytics]
The following key features make Axiom perfect for product analytics:
* **Real-time event monitoring:** Axiom’s ability to ingest and process data in real-time means you can monitor user activity as it happens. This empowers product managers to act quickly in response to anomalies or unexpected usage patterns, reducing downtime and improving user satisfaction. For example:
* Track whether feature flags are toggling as expected.
* Watch for broken signup flows or onboarding drop-offs immediately after a deploy.
* Monitor if newly launched features are generating engagement.
* **Unified data platform:** Axiom eliminates data silos by integrating event data from diverse sources into a single, cohesive platform. Axiom stores system telemetry alongside product analytics data. This eliminates the traditional separation between “user behavior” tools and “engineering” tools, and this convergence unlocks powerful debugging and insight scenarios:
* Correlate frontend feature usage with backend latency.
* View conversion funnel stages alongside HTTP error logs.
* Link user drop-off to infrastructure anomalies.
* **Advanced query capabilities:** With a robust query language, Axiom enables product teams to dive deep into data analysis. Perform detailed segmentation, drill down into specific user journeys, and uncover insights that would otherwise remain hidden.
* **Custom dashboards and visualizations:** Intuitive dashboards and customizable visualizations make it easy for product managers to communicate insights to stakeholders. Axiom’s visual tools enhance collaboration and decision-making.
* **Scalable infrastructure:** As your product grows, so does the volume of event data. Axiom’s architecture is built to scale effortlessly, ensuring that your analytics remain robust and reliable, even with massive datasets.
## Standard patterns for product data: Segment compatibility [#standard-patterns-for-product-data-segment-compatibility]
Axiom supports event ingestion via widely adopted patterns such as the [Segment specification](https://segment.com/docs/connections/spec/):
* **`identify`** associates events with known users.
* **`track`** records user interactions like button clicks or page views.
* **`group`** associates users with organizations or accounts.
These event types are foundational to many analytics workflows and are supported by tools like Mixpanel, Amplitude, June, and RudderStack. Axiom’s compatibility with this ecosystem enables product teams to reuse existing instrumentation patterns and schemas with minimal changes.
### How Axiom receives Segment events [#how-axiom-receives-segment-events]
To send Segment data to Axiom:
1. In Axiom, [create a dataset](/reference/datasets) for the Segment events and [generate an API token](/reference/tokens) with permission to ingest into that dataset.
2. In Segment, add a [webhook destination](https://segment.com/docs/connections/destinations/catalog/webhooks/) with the following settings:
* **Webhook URL**: `https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME`
* **Header** `Authorization`: `Bearer API_TOKEN`
* **Header** `Content-Type`: `application/x-ndjson`
For more details on the ingest endpoint, see [Send data via the REST API](/restapi/ingest).
Segment sends event payloads to Axiom in JSON format. Axiom stores each incoming payload as a structured event, preserving keys such as `event`, `userId`, `traits`, `properties`, and `_time` (automatically inferred or provided). No custom transformation is required. Segment’s default schema maps naturally into Axiom’s JSON ingestion pipeline.
After sending events to Axiom, query them using APL. For example, to count “Button Clicked” events per user by hour:
```kusto
['segment-frontend-prod']
| where event == "Button Clicked"
| summarize count() by userId, bin(_time, 1h)
```
## Use cases: from funnels to retention [#use-cases-from-funnels-to-retention]
Here are some ways product teams use Axiom:
### Feature adoption tracking [#feature-adoption-tracking]
Measure which users are engaging with newly released features.
```kusto
['segment-frontend-prod']
| where event == "AI Chat Created" and properties.featureName == "AI Chat"
| summarize count() by userId, bin(_time, 1h)
```
### Retention and churn analysis [#retention-and-churn-analysis]
Analyze returning users over time:
```kusto
['segment-frontend-prod']
| where event == "Logged In"
| summarize sessions = count(), users = dcount(userId) by bin(_time, 1w)
```
### Data for funnel diagnostics [#data-for-funnel-diagnostics]
Trace where users drop off between signup, onboarding, and first value.
```kusto
['segment-frontend-prod']
| where event in ("Signed Up", "Completed Onboarding", "Created Project")
| project userId, event, _time
| sort by userId, _time
```
### A/B test measurement [#ab-test-measurement]
Compare experiment cohorts based on downstream engagement:
```kusto
['segment-frontend-prod']
| where properties.experimentGroup == "variant_a"
| where event == "Clicked Upgrade"
| summarize conversions = dcount(userId)
```
## Why choose Axiom for product analytics [#why-choose-axiom-for-product-analytics]
Axiom’s focus on timestamped event data makes it perfect for product analytics. By crossing boundaries from engineering to product management and security, Axiom empowers cross-functional teams to collaborate effectively. Its comprehensive feature set ensures that organizations can unlock the full potential of their data, driving smarter decisions and fostering innovation.
In competitive markets, understanding your users is paramount. With Axiom, you gain a trusted partner in turning event data into actionable insights that propel your product to new heights. Experience the future of product analytics with Axiom and transform how you build, analyze, and optimize your product.
---
# Acceptable use policy
Source: https://axiom.co/docs/legal/acceptable-use-policy
{/* vale off */}
This Axiom Acceptable Use Policy ("AUP") explains the polices that govern your access and use of the Service offered by Axiom to You. You must use the Service in a lawful manner that is consistent with Axiom’s published AUP. The examples provided by Axiom are a non-exhaustive list of prohibited conduct. Axiom reserves the right to modify this AUP at any time by posting an updated version of the AUP on Axiom’s website. Changes to the policy are deemed effective upon posting and You are responsible for monitoring the website for changes to the policy. Use of any Axiom Service by You is deemed an acceptance of the most current version of the AUP. If the terms of this AUP are violated, Axiom reserves the right to immediately suspend or terminate Your use of the Service.
**Responsibility for Content**\
You are solely responsible for the content and data you use with the Axiom Service. Axiom does not monitor such content for illegal activity. You shall comply with all applicable laws and regulations. Any transmission, storage or distribution, or any use that directly facilitates a violation of applicable law or regulation is strictly prohibited.
**Inappropriate and Illegal Content**\
You may not use, distribute or instruct others to use the Axiom Services for illegal or harmful conduct. The following is a non-exhaustive list of prohibited conduct:
* **Illegal Activities:** Use of content or data in connection with the Axiom Service that is illegal, harassing, defamatory, libelous, indecent, obscene, pornographic, promotes online gambling, or otherwise objectionable.
* **Harmful Content:** Use of content or data in connection with the Axiom Service that is harmful to Axiom’s Service, network or other users of the network including, but not limited to, viruses, Trojan horses, time bombs, or any other computer programming that may damage, interfere with program data or information.
* **Infringing Content:** Use of content or data in connection with the Axiom Service that infringes or misappropriates the intellectual property or property right of others including, but not limited to, material protected by copyright, trademark, patent, or trade secret.
* **Export Violations:** Use of the Axiom Service in violation of U.S. and International export laws, regulations and rules.
**Email, Spam and Usenet**\
Axiom explicitly prohibits you from using the Axiom Service to send unsolicited messages to users without their permission. The following is a non-exhaustive list of prohibited conduct.
* Using the Axiom Service to send unsolicited bulk email, commercial advertising, informational announcements or promotional material (“spam”).
* Using the Axiom Service to post email messages that are excessive or intended to harass others.
* Using the Axiom Service in a manner that assumes the identity of a user without the users explicit permission.
**Security Violations**\
You may not use the Axiom Service to violate the security or integrity of the Axiom Service or Axiom’s network. The following is non-exhaustive a list of prohibited conduct:
* Unauthorized access of networks, data, servers, or databases without obtaining permission.
* Any attempt to test the vulnerability of a network or system, probe or scan or to breach security or authentication measures without permission.
* Any attempt to interfere with, disrupt or disable Service to any user, or network, including, but not limited to, via means of attempts to overload the system, crashing, mail bombing, denial of service attacks, or flooding techniques.
* The forging of any TCP/IP packet header, or using a computer system or program that intentionally conceals the source of information.
* Any usage or attempted usage of the Service You aren’t authorized to use.
* Any attempt or action designed to circumvent or alter any use limitations, including but not limited to, storage restrictions or billing measurement on the Services.
**Reporting Violations**\
It is your responsibility to report all known or suspected violations of this AUP. Axiom reserves the right, but is not obligated to investigate any violation or misuse of the Services. Axiom reserves the right to immediately block access, suspend or terminate you access or use of the Axiom Service, Axiom deems in its sole discretion, has violated the terms of this AUP. Axiom will attempt to provide you notice of possible suspension of Services, but reserves the right to immediately suspend or terminate your access or use of the Axiom Service that would have an adverse effect on Axiom, the Axiom Service, the Axiom network, or any Axiom customer. Axiom shall not be liable for any damages arising out of the violation of this AUP and you shall indemnify Axiom for any and all damages that may arise from a violation of this AUP by You.
---
# Cookie policy
Source: https://axiom.co/docs/legal/cookies
{/* vale off */}
## What are cookies? [#what-are-cookies]
Cookies are small data files that are placed on your computer when you visit a site. Cookies serve different purposes, like helping us understand how our site is being used, letting you navigate between pages efficiently, remembering your preferences, and generally improving your browsing experience. Cookies can also help ensure advertising you see online is more relevant to you and your interests.
## Who places cookies on my device? [#who-places-cookies-on-my-device]
Cookies set by the site you visit are called "first party cookies". Cookies set by parties other than us are called "third party cookies". Third party cookies enable third party features or functionality within the site, such as site analytics, advertising and social media features. The parties that set these third party cookies can recognize your computer or device both when it visits the site in question and also when it visits certain other sites and/or mobile apps. We do not control how these third parties' use your information, which is subject to their own privacy policies. See below for details on use of third party cookies and similar technologies with our sites and app.
## How long will cookies stay on my device? [#how-long-will-cookies-stay-on-my-device]
The length of time a cookie will stay on your device depends on whether it is a "persistent" or "session" cookie. Session cookies will only stay on your device until you stop browsing. Persistent cookies stay on your browsing device after you have finished browsing until they expire or are deleted.
## What other tracking technologies should I know about? [#what-other-tracking-technologies-should-i-know-about]
Cookies are not the only way to track visitors to a site or app. Companies use tiny graphics files with unique identifiers called beacons (and also "pixels" or "clear gifs") to recognize when someone visits its sites. These technologies often depend on cookies to function properly, and so disabling cookies may impair their functioning.
## What types of cookies and similar tracking technologies does Axiom use? [#what-types-of-cookies-and-similar-tracking-technologies-does-axiom-use]
We use cookies and other tracking technologies in the following categories described below.
### Essential cookies [#essential-cookies]
These cookies are essential to provide you with services available through our websites and to enable you to use some of their features. Without these cookies, the services that you have asked for cannot be provided, and we only use these cookies to provide you with those services.
**Who serves these cookies:** Axiom, Inc.
**How to refuse:** Because these cookies are strictly necessary to deliver the Websites to you, you cannot refuse them. You can block or delete them by changing your browser settings however, as described below under the heading "Your choices".
### Functionality cookies [#functionality-cookies]
These cookies allow our websites to remember choices you make when you use them. The purpose of these cookies is to provide you with a more personal experience and to avoid you having to re-select your preferences every time you visit our websites.
**Who serves these cookies:** Axiom, Inc.
**How to refuse:** To refuse these cookies, please follow the instructions below under the heading "Your choices".
### Analytics and performance cookies [#analytics-and-performance-cookies]
These cookies are used to collect information about traffic to our websites and how users use our websites. The information gathered may include the number of visitors to our websites, the websites that referred them to our websites, the pages they visited on our websites, what time of day they visited our websites, whether they have visited our websites before, and other similar information. We use this information to help operate our websites more efficiently, to gather broad demographic information, monitor the level of activity on our websites, and improve the websites.
**Who serves these cookies:**
**Segment** (Twilio, Inc.)
The subsite, [https://app.axiom.co](https://app.axiom.co) (a subsite of axiom.co) uses Segment to help analyze how users use the site. The tool does not use cookies however user data is shared with Segment. In addition to your name and email address, your IP may be transmitted to Segment (though never stored there). This information is then used to evaluate the use of the service as well as compute statistical reports on website activity to help us build a better product.
You can find more information about Segment's privacy policy here: [https://www.twilio.com/en-us/legal/privacy](https://www.twilio.com/en-us/legal/privacy). If you wish to not share usage information with Twilio/Segment, please let us know at [privacy@axiom.co](mailto:privacy@axiom.co)
**Mixpanel**
The subsite, [https://app.axiom.co](https://app.axiom.co) (a subsite of axiom.co) uses Mixpanel to help analyze how users use the site. The tool does not use cookies however user data is shared with Mixpanel. In addition to your name and email address, your IP may be transmitted to Mixpanel (though never stored there). This information is then used to evaluate the use of the service as well as compute statistical reports on website activity to help us build a better product.
You can find more information about Mixpanel's privacy policy here: [https://mixpanel.com/legal/privacy-policy](https://mixpanel.com/legal/privacy-policy). If you wish to not share usage information with Mixpanel, please let us know at [privacy@axiom.co](mailto:privacy@axiom.co)
**Hubspot** (Hubspot, Inc.)
The utilization of HubSpot's services enables the collection, storage, and analysis of user interaction data, enhancing our capacity to provide tailored content, optimize user experience, and conduct comprehensive website performance evaluations. It is imperative to note that all data captured through HubSpot's cookie tracking technology is processed and stored in strict adherence to applicable data protection laws and regulations, ensuring the utmost level of data integrity and security. Users retain the right to opt-out of cookie tracking at any given time, a provision that can be executed through the designated privacy settings available on our website. By continuing to navigate our website, users express their informed consent to our use of HubSpot for cookie tracking purposes, acknowledging the vital role it plays in our ongoing efforts to refine and personalize the user experience.
**Warmly** (Warmly, Inc.)
The Services use cookies and similar tracking technologies such as pixel tags, web beacons, and JavaScript to enable our servers to recognize your web browser, tell us how and when you visit and use our Services, analyze trends, learn about our user base and operate and improve our Services. These technologies help us measure and improve our services and personalize your experience. For more information about Warmly's privacy practices, visit [https://www.warmly.ai/p/privacy-policy](https://www.warmly.ai/p/privacy-policy).
**X, (Formally, Twitter)** (X, Inc. (Previously Twitter))
Pixels are small amounts of code placed on a web page, in a web-enabled app, or an email. We use pixels, some of which we provide to advertisers to place on their web properties, to learn whether you've interacted with specific web or email content — as many services do. This helps us measure and improve our services and personalize your experience, including the ads you see.
**Linkedin** (Linkedin, Inc.)
Pixels are small amounts of code placed on a web page, in a web-enabled app, or an email. We use pixels, some of which we provide to advertisers to place on their web properties, to learn whether you've interacted with specific web or email content — as many services do. This helps us measure and improve our services and personalize your experience, including the ads you see.
**Google Tag Manager, Google Analytics** (Google, LLC.)
Pixels are small amounts of code placed on a web page, in a web-enabled app, or an email. We use pixels, some of which we provide to advertisers to place on their web properties, to learn whether you've interacted with specific web or email content — as many services do. This helps us measure and improve our services and personalize your experience, including the ads you see.
**How to refuse:** To refuse these cookies, please follow the instructions below under the heading "Your choices" Alternatively, please click on the relevant opt-out link below.
You can control these cookies as described in the Your choices section below. The third parties who serve cookies listed in the table above may use other third parties to place cookies, but any such indirect placement of cookies is out of our control. You should review the privacy policies of the third parties listed in the table above to find out more information about their use of cookies.
## Your choices [#your-choices]
You can access cookie preferences by using the "Cookie settings" button above. However, most browsers let you remove or reject cookies. To do this, follow the instructions in your browser settings. Many browsers accept cookies by default until you change your settings. Please note that if you set your browser to disable cookies, parts of the site may not work properly. For more information about cookies, including how to see what cookies have been set on your computer or mobile device and how to manage and delete them, visit [www.allaboutcookies.org](http://www.allaboutcookies.org).
## Changes [#changes]
Information about the cookies we use may be updated from time to time, so please check back on a regular basis for any changes.
## Questions [#questions]
If you have any questions about this Cookie Policy, please contact us by email at [privacy@axiom.co](mailto:privacy@axiom.co).
---
# Data processing addendum
Source: https://axiom.co/docs/legal/data-processing
{/* vale off */}
This Data Processing Addendum (“**DPA**”) is incorporated into and forms part of the Axiom Terms of Service [https://axiom.co/docs/legal/terms-of-service](/legal/terms-of-service) or other applicable agreement governing the use of the Services (the “**Agreement**”).
**By accessing or using the Services, or otherwise indicating your acceptance of the Agreement, Customer hereby agrees to the terms of this DPA on behalf of itself and any affiliated entities it represent.**
This DPA applies to Axiom Inc.’s (“**Axiom**” or “**Company**”) Processing of Personal Data in connection with the Services and is effective as of the date Customer first access or use of the Services after the date of publication of this DPA (the “**Effective Date**”). This DPA does not require a signature to be valid or enforceable and is deemed to be mutually agreed upon and entered into by Customer and Axiom through Customer’s acceptance of the Agreement. This DPA applies only if and to the extent Applicable Data Protection Laws govern Axiom’s Processing of Customer Personal Data in performance of the Services as a ‘processor’, ‘service provider’ or similar role defined under Data Protection Laws. Accordingly, this DPA does not apply to Axiom’s Processing of any Personal Data for its own business or customer relationship, administration purposes, its own marketing or service analytics, its own information and systems security purposes supporting the operation of the Services, nor its own legal, regulatory or compliance purposes.
For a copy of the signed DPA for recordkeeping purposes, or if Customer requires a countersigned version due to internal policies, please contact Axiom at [privacy@axiom.co](mailto:privacy@axiom.co).
**1. Definitions**
In this DPA:
a) “**Business**”, “**Controller**”, “**Data Subject**”, “**Personal Data**”, “**Personal Data Breach**”, “**Processing**”, “**Processo**r”, “**Service Provider**” and “**Supervisory Authority**” have the meaning given to them in Data Protection Law;
b) “**Customer**” means the entity that is receiving the Services and has entered into the Agreement with Company.
c) “**Customer Personal Data**” means any Customer data that constitutes Personal Data, the Processing of which is subject to Data Protection Law, for which Customer or Customer’s customers are the Controller, and which is Processed by Company as part of providing the Services;
d) "**Data Protection Law**" means all applicable privacy and data protection laws relating to the processing of Customer Personal Data in connection with the Agreement, including but not limited to (i) the Regulation (EU) 2016/679 of the European Parliament and of the Council of 27th April 2016 on the protection of natural persons with regard to the processing of personal data and on the free movement of such data (“**GDPR**”), (ii) the GDPR as it forms part of the law of England and Wales, Scotland and Northern Ireland by virtue of section 3 of the European Union (Withdrawal) Act 2018, (iii) the e-Privacy Directive 2002/58/EC (as amended by Directive 2009/136/EC) and their national implementations in the European Economic Area (“**EEA**”) and the United Kingdom; and (iv) the California Consumer Privacy Act as amended by the California Privacy Rights Act (California Civil Code § 1798.100) (“CCPA”), each as applicable, and as may be amended or replaced from time to time;
e) “**Data Subject Rights**” means Data Subjects’ rights as set out in Data Protection Law;
f) “**International Data Transfer**” means any transfer of Customer Personal Data from the EEA, Switzerland or the United Kingdom to an international organization or to a country outside of the EEA, Switzerland and the United Kingdom;
g) “**Sell**” means to sell, rent, release, disclose, disseminate, make available, transfer, or otherwise communicate Customer Personal Data to a third party for monetary or other valuable consideration;
h) “**Services**” means the services provided by Company to Customer under the Agreement;
i) “**Share**” means to share, rent, release, disclose, disseminate, make available, transfer, or otherwise communicate Customer Personal Data to third parties for targeted advertising to an individual based on Personal Data obtained from the individual’s activity across non-affiliated or distinctly-branded websites, applications, or services;
j) “**Subprocessor**” means a Processor engaged by Company to Process Customer Personal Data;
k) “**Standard Contractual Clauses**" means the standard contractual clauses approved by the European Commission pursuant to implementing Decision (EU) 2021/914.
l) **“Third-Party Controller”** means a Controller for which Customer acts as a Processor; and
m) “**UK Addendum**” means the International Data Transfer Addendum to the Standard Contractual Clauses issued by the UK Information Commissioner’s Office, in force as of 21 March 2022, available at international-data-transfer-addendum.pdf (ico.org.uk).
**2. Scope and applicability**
2.1. This DPA applies to Processing of Customer Personal Data by the Company to provide the Services\*\*.\*\*
2.2. The subject matter, nature and purpose of the Processing, the types of Customer Personal Data and categories of Data Subjects are set out in Annex I.
2.3. Customer is a Controller and appoints Company as a Processor and, with respect to CCPA, a Service Provider, on behalf of Customer. Customer is responsible for compliance with the requirements of Data Protection Law applicable to Controllers.
**3. Instructions**
3.1. Company will Process Customer Personal Data to provide the Services and in accordance with Customer’s documented instructions and applicable Data Protection Law.
3.2. It is the parties’ intent that Company is a Service Provider, and Company certifies that it will not (i) Sell or Share Customer Personal Data; (ii) Process Customer Personal Data outside the direct business relationship between the parties or for any purpose other than to provide the Services in accordance with the Agreement, unless required or authorized by Data Protection Law; or (iii) combine the Personal Data that Company receives from or on behalf of Customer with Personal Data that Company collects or receives from another person.
3.3. The Controller’s instructions are documented in this DPA and the Agreement. Customer may reasonably issue additional instructions as necessary to comply with Data Protection Law. Company may charge a reasonable fee to comply with any additional instructions.
3.4. Unless prohibited by applicable law, Company will inform Customer if Company is subject to a legal obligation that requires Company to Process Customer Personal Data in contravention of Customer’s documented instructions.
3.5. Company will notify Customer after it makes a determination that it can no longer meet its obligations under Data Protection Law. Customer has the right, upon notice, to take reasonable and appropriate steps to stop and remediate Company’s unauthorized use of Customer Personal Data and to ensure that Company uses the Customer Personal Data that it collected pursuant to the Agreement in a manner consistent with Customer’s obligations under Data Protection Law.
**4. Personnel**
Company will ensure that all personnel authorized to Process Customer Personal Data are subject to an obligation of confidentiality.
**5. Security and Personal Data Breaches**
5.1. Taking into account the state of the art, the costs of implementation and the nature, scope, context and purposes of Processing as well as the risk of varying likelihood and severity for the rights and freedoms of natural persons, Company will implement and maintain appropriate technical and organizational measures designed to provide a level of security appropriate to the risk, including the measures listed in Annex II.
5.2. Customer acknowledges that the security measures in Annex II are appropriate in relation to the risks associated with Customer’s intended Processing, and will notify Company prior to any intended Processing for which Company’s security measures may not be appropriate.
5.3. Company will notify Customer without undue delay after becoming aware of a Personal Data Breach involving Customer Personal Data. If Company’s notification is delayed, it will be accompanied by reasons for the delay.
**6. Subprocessing**
6.1. Customer hereby authorizes Company to engage Subprocessors. A list of Company’s current Subprocessors is available at [https://axiom.co/sub-processors](https://axiom.co/sub-processors) (the “**Subprocessors Page**”), and may be updated by Company from time to time in accordance with this DPA.
6.2. Company will enter into a written agreement with Subprocessors which imposes the obligations consistent with applicable Data Protection Law. Subject to the limitations of liability included in the Agreement, Company agrees to be liable for the acts and omissions of its Subprocessors to the same extent Company would be liable under the terms of the DPA if it performed such acts or omissions itself.
6.3. When any new Subprocessor is engaged, Company will notify Customer of the engagement, which notice may be given by updating the Subprocessor Page and/or via a message through email or the Service. Axiom will give such notice at least ten (10) calendar days before the new Subprocessor Processes any Customer Personal Data, except that if Company reasonably believes engaging a new Subprocessor on an expedited basis is necessary to protect the confidentiality, integrity or availability of the Customer Personal Data or avoid material disruption to the Services, Axiom will give such notice as soon as reasonably practicable. If, within five (5) calendar days after such notice, Customer notifies Company in writing that Customer objects to the appointment of a new Subprocessor based on reasonable data protection concerns, the parties will discuss such concerns in good faith and whether they can be resolved. If the parties are not able to mutually agree to a resolution of such concerns, Customer, as its sole and exclusive remedy, may terminate the Agreement for convenience with no refunds and Customer will remain liable to pay any committed fees in an order form, order, statement of work or other similar ordering document.
**7. Assistance**
7.1. Taking into account the nature of the Processing, and the information available to Company, Company will assist Customer, including, as appropriate, by implementing technical and organizational measures, with the fulfillment of Customer’s own obligations under Data Protection Law to: comply with requests to exercise Data Subject Rights; conduct data protection impact assessments, and prior consultations with Supervisory Authorities; and notify a Personal Data Breach. Company reserves the right to charge a reasonable fee for assistance under this Section 7.
**8. Audit**
8.1. Upon reasonable request, Company must make available to Customer all information necessary to demonstrate compliance with the obligations of this DPA and allow for and contribute to audits, including inspections, as mandated by a Supervisory Authority or reasonably requested no more than once a year by Customer and performed by an independent auditor as agreed upon by Customer and Company. The foregoing shall only extend to those documents and facilities relevant and material to the Processing of Customer Personal Data, and shall be conducted during normal business hours and in a manner that causes minimal disruption.
8.2. Company will inform Customer if Company believes that Customer’s instruction under Section 8.1 infringes Data Protection Law. Company may suspend the audit or inspection, or withhold requested information until Company has modified or confirmed the lawfulness of the instructions in writing. Company and Customer each bear their own costs related to an audit.
8.3. Company may retain some or all of the Customer Personal Data to the extent required by Data Protection Law or other applicable law.
**9. International Data Transfers**
9.1. Customer hereby authorizes Company to carry out International Data Transfers with respect to Customer Personal Data in accordance with Data Protection Law. Company shall Process and store Customer Personal Data in the geographic location where Customer Personal Data is submitted to the Services by Customer, or otherwise made available to Company and such Customer Personal Data will not be transferred to, or replicated in another geographical location unless authorized by Customer, or necessary to provide the Services to Customer..
9.2. Customer Personal Data may be processed in the United States or the EU. Transfer Mechanisms (e.g., Standard Contractual Clauses) will apply as needed. Data hosting locations are managed with the same security measures and protocols as defined herein. To the extent that Axiom Processes Customer Personal Data originating from and protected by Data Protection Laws in one of the jurisdictions listed in Schedule 4 (Jurisdiction Specific Terms), then the terms specified therein with respect to the applicable jurisdiction(s) will apply in addition to the terms of this DPA.
9.3. To the extent that Customer’s use of the Services requires an onward transfer mechanism to lawfully transfer personal data from a jurisdiction (i.e., the European Economic Area (“**EEA**\*”),\* the United Kingdom (“**UK**”), Switzerland or any other jurisdiction listed in Schedule 3) to Axiom located outside of that jurisdiction (a “**Transfer Mechanism**”), the terms and conditions of Schedule 3 (Cross Border Transfer Mechanisms) will apply.
9.4. If Company’s compliance with Data Protection Law applicable to International Data Transfers is affected by circumstances outside of Company’s control, including circumstances affecting the validity of an applicable legal instrument, Company and Customer will work together in good faith to reasonably resolve such non-compliance.
9.5. The parties agree that the data export solutions identified in this Section 9 will not apply if and to the extent that Company adopts an alternative data export solution for the lawful transfer of Customer Personal Data (as recognized under applicable Data Protection Laws), in which event, Customer shall reasonably cooperate with Company to implement such solution and such alternative data export solution will apply instead (but solely to the extent such alternative data export solution extends to the territories to which Customer Personal Data is transferred under this DPA).
**10. Notifications**
10.1. Customer will send all notifications, requests and instructions under this DPA to [privacy@axiom.co](mailto:privacy@axiom.co). Company will send all notifications under this DPA to Customer’s registered email address.
**11. Liability**
11.1. Notwithstanding anything to the contrary in this DPA, the Agreement, or otherwise, the limitations of liability specified in the Agreement shall apply to any and all Company liability and obligation arising under or otherwise related to this DPA.
**12. Termination and return or deletion**
12.1. This DPA is terminated upon the termination or expiration of the Agreement. Unless required or permitted by applicable law, Company will delete all remaining copies of Customer Personal Data within sixty (60) days following termination or expiration of the Agreement.
**13. Modification of this DPA**
13.1. This DPA may only be modified by a written amendment mutually agreed upon and signed by both Company and Customer.
**14. Invalidity and severability**
14.1. If any provision of this DPA is found by any court or administrative body of competent jurisdiction to be invalid or unenforceable, then the invalidity or unenforceability of such provision does not affect any other provision of this DPA and all provisions not affected by such invalidity or unenforceability will remain in full force and effect.
**Annex I**
**DETAILS OF PROCESSING**
**A. LIST OF PARTIES**
**COMPANY / ‘DATA IMPORTER’ DETAILS**
| | |
| ---------------------------------------- | ------------------------------------------------------------------------------------------ |
| **Name:** | Axiom, Inc. a Delaware corporation. |
| **Address:** | 1390 Market Street, Suite 200 San Francisco CA 94102 USA |
| **Contact Details for Data Protection:** | Available upon request. |
| **Activities:** | Axiom will process Personal Data as necessary to provide the Services under the Agreement. |
| **Role:** | Processor |
**CUSTOMER / ‘DATA EXPORTER’ DETAILS**
| | |
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **Name:** | The entity or other person who is a counterparty to the Agreement. |
| **Address:** | As specified in the Service Order to the Agreement. |
| **Contact Details for Data Protection:** | The Customer signatory to the Service Order. |
| **Customer Activities:** | The use the Services as part of its ongoing business operations under and in accordance with the Agreement. |
| **Role:** | Controller |
**B. DESCRIPTION OF PROCESSING / TRANSFER**
| | |
| ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Categories of Data Subjects whose Personal Data is transferred** | The Data Subjects whose Personal Data are processed by Company when providing the Services to Customer, including: Personal Data with respect to Customer’s customers, end users, employees, agents and partners (who are natural persons). |
| **Categories of Personal Data transferred** | The categories of Personal Data that are processed by Company when providing the Services to Customer, including: contact information (name, age, gender, address, telephone number, email address, etc.), and device identifiers and internet or electronic network activity (IP addresses, GAID/IDFA, browsing history, timestamps, etc.). |
| **Sensitive data transferred (if applicable) and applied restrictions or safeguards** | No sensitive data is processed under the Agreement. |
| **Frequency of Transfer** | Continuous basis for the duration of the Agreement. |
| **Nature and purpose(s) of the data transfer and Processing** | Company will process Personal Data as necessary to provide the Services under the Agreement. |
| **Retention period (or, if not possible to determine, the criteria used to determine the period)** | Personal Data will be retained for as long as necessary taking into account the purpose of the processing, and in compliance with applicable laws, including laws on the statute of limitations. |
| **For transfers to (sub-)processors, also specify subject matter, nature, and duration of the processing** | Company will restrict the onward Subprocessor’s access to Customer Personal Data only to what is strictly necessary to provide the Services. |
| **Identify the competent supervisory authority/ies in accordance with Clause 13** | Where the EU GDPR applies, the competent authority will be determined in accordance with Clause 13 of the Standard Contractual Clauses. Where the UK GDPR applies, the UK Information Commissioner's Office. |
**Annex II**
**TECHNICAL AND ORGANIZATIONAL SECURITY MEASURES**
**DPA - Technical and Organizational Security Measures Annex**
The Company will implement the following specific security measures, as applicable:
| | |
| -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Security Management** | Axiom maintains a comprehensive written information security program (ISP) that is aligned with leading security frameworks, including ISO 27001 and SOC 2. This program includes policies, processes, and controls governing the processing of Personal Data, designed to: (a) Secure Personal Data against accidental or unlawful loss, access, or disclosure; (b) Identify and manage reasonably foreseeable risks to the security of the Axiom Production Environment; and (c) Minimize security risks through continuous risk assessment and regular testing. For the purpose of this Schedule, the “Axiom Production Environment" means Axiom's cloud infrastructure, servers, networking services, assets, and hosting software and systems managed by Axiom within its cloud service providers (including Amazon Web Services (AWS) and Cloudflare) used to process or store Personal Data. |
| **Maintaining of an Information Security Policy** | Axiom's ISP is established and maintained in accordance with its **SOC 2 and ISO 27001 certifications**. The policies are regularly reviewed, updated, and communicated to all relevant personnel. Security policies and procedures clearly define information security responsibilities for all aspects of our service, including: Maintaining and reviewing security policies and procedures. Secure software development (SDLC), operation, and maintenance. Security incident response and escalation procedures. User access administration based on the Principle of Least Privilege. Monitoring and control of all systems within the Axiom Production Environment. |
| **Secure Networks and Systems** | To protect Personal Data, Axiom utilizes a multi-layered security approach, leveraging cloud-native technologies such as **Virtual Private Clouds (VPCs), security groups, network access control lists (NACLs), and web application firewalls (WAFs)**. These controls are configured to deny all traffic by default and only permit traffic required for the provision of the service, effectively isolating the Axiom Production Environment from untrusted networks. All network security configurations are documented, subject to change control, and reviewed regularly. |
| **Personal Data Protection Measures (including storage limitation, data minimization and retention and encryption)** | **Encryption:** All Personal Data is encrypted both **at rest** using strong encryption standards (e.g., AES-256) and **in transit** across public networks using industry-standard cryptographic protocols (e.g., TLS 1.2 or higher). We have documented procedures to protect cryptographic keys against misuse. **Data Minimization & Retention:** In line with our compliance obligations, including **HIPAA**, Axiom limits Personal Data storage to the minimum necessary for the provision of our services and enforces defined data retention and disposal policies. |
| **Vulnerability Management Efforts** | Axiom protects its systems against malicious software using advanced **endpoint detection and response (EDR)** solutions and automated malware scanning. We maintain secure systems and applications by: Establishing processes to continuously identify and remediate security vulnerabilities. Implementing strict change management procedures, including the separation of development, testing, and production environments. Personal Data is not used in non-production environments. Following a secure software development lifecycle (SDLC) that incorporates security reviews, code analysis, and testing throughout the development process. |
| **Access Control Measures** | Access to Personal Data is strictly restricted on a **need-to-know basis** (Principle of Least Privilege) and limited to authorized personnel for legitimate business purposes. This is achieved by: Utilizing a centralized **Identity and Access Management (IAM)** system with a default "deny-all" setting. Assigning individually unique IDs to all users and requiring **Multi-Factor Authentication (MFA)** for access to the Axiom Production Environment. Enforcing strong password complexity rules and implementing processes for timely provisioning, modification, and de-provisioning of user access. Automatically locking user accounts after repeated failed login attempts and terminating idle sessions. |
| **Restriction of Physical Access to Personal Data Processing Systems** | As a cloud-native company, Axiom does not own or operate physical data centers. We leverage premier cloud service providers (**AWS and Cloudflare**), which are responsible for the physical security of the data centers housing the Axiom Production Environment. These providers are leading global companies that maintain robust physical security programs with controls such as: 24/7/365 on-site security personnel. Biometric and electronic access control. Perimeter security, including fencing and video surveillance. Regular audits and certifications (e.g., **SOC 2, ISO 27001**). Axiom ensures that backups are encrypted, logically secured, and stored in geo-redundant locations. Media containing Personal Data is disposed of using secure data destruction techniques. |
| **Regular Monitoring and Testing of Networks** | Access to Recipient Network and Personal Data is monitored using mechanisms that allow tracking, alerting, and analysis on a regular basis as well as upon need. All systems that process Personal Data are provided with correct and consistent time and audit trails. Audit trails for critical systems are kept for, at least, one year. The security of our systems is regularly tested as part of our **ISO 27001 and SOC 2** compliance programs, including: **Quarterly** internal and external network vulnerability scans. **Annual** internal and external penetration tests conducted by a qualified third party. Test findings are tracked and remediated in a timely manner according to their severity. |
| **Incident Response Plan** | Axiom maintains a formal Incident Response Plan to ensure a timely and effective response to any security breach. The plan is tested regularly and includes procedures for identification, containment, eradication, and recovery, as well as breach notification procedures. Axiom also maintains a **Business Continuity and Disaster Recovery (BCDR)** plan, which includes data backup and recovery procedures that are tested regularly to ensure service availability. |
| **Third Party Risk Management Program** | Axiom maintains a formal **Third-Party Risk Management (TPRM)** program. Before engaging any new vendor or service provider that will access Personal Data or the Axiom Production Environment, we conduct a thorough due diligence process to assess their security and compliance posture. |
**SCHEDULE 3**
**CROSS BORDER DATA TRANSFER MECHANISM**
**1. Definitions**
a. **“Standard Contractual Clauses”** means, depending on the circumstances unique to any particular Customer, any of the following:
(i) UK Standard Contractual Clauses; and (ii) 2021 Standard Contractual Clauses.
b. “**UK Standard Contractual Clauses**” means:
(i) Standard Contractual Clauses for data controller to data processor transfers approved by the European Commission in decision 2010/87/EU (“**UK Controller to Processor SCCs**”); and
(ii) Standard Contractual Clauses for data controller to data controller transfers approved by the European Commission in decision 2004/915/EC (“**UK Controller to Controller SCCs”**).
c. "**2021 Standard Contractual Clauses**" means the Standard Contractual Clauses approved by the European Commission in decision 2021/914.
**2. UK Standard Contractual Clauses**. For data transfers from the United Kingdom that are subject to the UK Standard Contractual Clauses, the UK Standard Contractual Clauses will be deemed entered into (and incorporated into this Addendum by reference) and completed as follows:
a. The UK Controller to Processor SCCs will apply where Axiom is processing Customer Data. The illustrative indemnification clause will not apply. Schedule 1 serves as Appendix 1 of the UK Controller to Processor SCCs. Schedule 2 serves as Appendix 2 of the UK Controller to Processor SCCs.
b. The UK Controller to Controller SCCs will apply where Axiom is processing Usage Data. In Clause II(h), Axiom will process personal data in accordance with the data processing principles set forth in Annex A of the UK Controller to Controller SCCs. The illustrative commercial clause will not apply. Schedule 1 serves as Annex B of the UK Controller to Controller SCCs. Personal Data transferred under these clauses may only be disclosed to the following categories of recipients: i) Axiom’s employees, agents, Affiliates, advisors and independent contractors with a reasonable business purpose for needing such personal data; ii) Axiom vendors that, in their performance of their obligations to Axiom, must process such personal data acting on behalf of and according to instructions from Axiom; and iii) any person (natural or legal) or organisation to whom Axiom may be required by applicable law or regulation to disclose personal data, including law enforcement authorities, central and local government.
**3. The 2021 Standard Contractual Clauses**. For data transfers from the European Economic Area, the UK, and Switzerland that are subject to the 2021 Standard Contractual Clauses, the 2021 Standard Contractual Clauses will apply in the following manner:
a. Module One (Controller to Controller) will apply where Customer is a controller of Usage Data and Axiom is a controller of Usage Data.
b. Module Two (Controller to Processor) will apply where Customer is a controller of Customer Data and Axiom is a processor of Customer Data;
c. For each Module, where applicable:
(i) in Clause 7, the option docking clause will not apply;
(ii) in Clause 9, Option 2 will apply, and the time period for prior notice of sub-processor changes will be as set forth in Section 6 (Subprocessing) of this Addendum;
(iii) in Clause 11, the optional language will not apply;
(iv) in Clause 17 (Option 1), the 2021 Standard Contractual Clauses will be governed by Irish law.
(v) in Clause 18(b), disputes will be resolved before the courts of Ireland; (vi) In Annex I, Part A: Data Exporter: Customer and authorized Affiliates of Customer.
Contact Details: Customer’s account owner email address, or to the email address(es) for which Customer elects to receive privacy communications.
Data Exporter Role: The Data Exporter’s role is outlined in Section 3 of this Addendum Schedule.
Signature & Date: By entering into the Agreement, Data Exporter is deemed to have signed these Standard Contractual Clauses incorporated herein, including their Annexes, as of the Effective Date of the Agreement.
Data Importer: Axiom Inc.
Contact Details: Axiom Privacy Team – [privacy@axiom.co](mailto:privacy@axiom.co)
Data Importer Role: The Data Importer’s role is outlined in Section 3 of this Addendum Schedule.
Signature & Date: By entering into the Agreement, Data Importer is deemed to have signed these Standard Contractual Clauses, incorporated herein, including their Annexes, as of the Effective Date of the Agreement.
(vii) In Annex I, Part B:
The categories of data subjects are described in Schedule 1, Section 4.
The sensitive data transferred is described in Schedule 1, Section 6.
The frequency of the transfer is a continuous basis for the duration of the Agreement. The nature of the processing is described in Schedule 1, Section 1.
The purpose of the processing is described in Schedule 1, Section 1.
The period of the processing is described in Schedule 1, Section 3.
For transfers to sub-processors, the subject matter, nature, and duration of the processing is outlined at [https://axiom.co/legal/sub-processors](https://axiom.co/legal/sub-processors).
(viii) In Annex I, Part C: The Irish Data Protection Commission will be the competent supervisory authority.
(ix) Schedule 2 serves as Annex II of the Standard Contractual Clauses.
4\. As to the specific modules, the parties agree that the following modules apply, as the circumstances of the transfer may apply:
Controller-Controller - Module One
Controller-Processor - Module Two
5\. To the extent there is any conflict between the Standard Contractual Clauses and any other terms in this Addendum, including Schedule 4 (Jurisdiction Specific Terms), the provisions of the Standard Contractual Clauses will prevail.
**SCHEDULE 4**
**JURISDICTION SPECIFIC TERMS**
1\. California
a. The definition of “**Applicable Data Protection Law**” includes the California Consumer Privacy Act (“**CCPA**”).
b. The terms “**business**”, “**commercial purpose**”, “**service provider**”, “**sell**” and “**personal information**” have the meanings given in the CCPA.
c. With respect to Customer Data, Axiom is a service provider under the CCPA.
d. Axiom will not (a) sell Customer Data; (b) retain, use or disclose any Customer Data for any purpose other than for the specific purpose of providing the Services, including retaining, using or disclosing the Customer Data for a commercial purpose other than providing the Services; or (c) retain, use or disclose the Customer Data outside of the direct business relationship between Axiom and Customer.
e. The parties acknowledge and agree that the Processing of Customer Data authorized by Customer’s instructions described in Section 5 of this Addendum is integral to and encompassed by Axiom’s provision of the Services and the direct business relationship between the parties.
f. Notwithstanding anything in the Agreement or any Order Form entered in connection therewith, the parties acknowledge and agree that Axiom’s access to Customer Data does not constitute part of the consideration exchanged by the parties in respect of the Agreement.
g. To the extent that any Usage Data (as defined in the Agreement) is considered Personal Data, if and when Axiom is subject to the CCPA, Axiom is the business under the CCPA with respect to such data and will Process such data in accordance with its Privacy Policy. As of October 1, 2021 Axiom is not subject to the CCPA as a business.
2\. EEA
a. The definition of “**Applicable Data Protection Laws**” includes the General Data Protection Regulation (EU 2016/679)(“**GDPR**”).
b. When Axiom engages a Subprocessor under Section 6 (Subprocessing), it will:
(i) require any appointed Subprocessor to protect Customer Data to the standard required by Applicable Data Protection Laws, such as including the same data protection obligations referred to in Article 28(3) of the GDPR, in particular providing sufficient guarantees to implement appropriate technical and organizational measures in such a manner that the processing will meet the requirements of the GDPR; and
(ii) require any appointed Subprocessor to agree in writing to only process data in a country that the European Union has declared to have an “adequate” level of protection; or to only process data on terms equivalent to the Standard Contractual Clauses.
c. GDPR Penalties. Notwithstanding anything to the contrary in this Addendum or in the Agreement (including, without limitation, either party’s indemnification obligations), neither party will be responsible for any GDPR fines issued or levied under Article 83 of the GDPR against the other party by a regulatory authority or governmental body in connection with such other party’s violation of the GDPR.
3\. Switzerland
a. The definition of “Applicable Data Protection Laws” includes the Swiss Federal Act on Data Protection.
b. When Axiom engages a Subprocessor under Section 6 (Subprocessing), it will:
(i) require any appointed Subprocessor to protect Customer Data to the standard required by Applicable Data Protection Laws, such as including the same data protection obligations referred to in Article 28(3) of the GDPR, in particular providing sufficient guarantees to implement appropriate technical and organizational measures in such a manner that the processing will meet the requirements of the GDPR; and
(ii) require any appointed Subprocessor to agree in writing to only process data in a country that the European Union has declared to have an “adequate” level of protection; or to only process data on terms equivalent to the Standard Contractual Clauses.
4\. United Kingdom
a. References in this Addendum to GDPR will to that extent be deemed to be references to the corresponding laws of the United Kingdom (including the UK GDPR and Data Protection Act 2018).
b. When Axiom engages a Subprocessor under Section 6 (Subprocessing), it will:
(i) require any appointed Subprocessor to protect Customer Data to the standard required by Applicable Data Protection Laws, such as including the same data protection obligations referred to in Article 28(3) of the GDPR, in particular providing sufficient guarantees to implement appropriate technical and organizational measures in such a manner that the processing will meet the requirements of the GDPR; and
(ii) require any appointed Subprocessor to agree in writing to only process data in a country that the European Union has declared to have an “adequate” level of protection; or to only process data on terms equivalent to the Standard Contractual Clauses.
---
# HIPAA anti-retaliation policy
Source: https://axiom.co/docs/legal/hipaa
{/* vale off */}
Title II of the Federal Health Insurance Portability and Accountability Act (42 USC 1320d to 1329d-8, and Section 264 of Public Law 104191), and its accompanying Privacy Regulations, 45 CFR Parts 160 and 164, require that "covered entities," as defined by the HIPAA Privacy Regulations, refrain from any retaliatory acts targeted toward those who file complaints or otherwise report HIPAA violations or infractions. The purpose of this policy is to clearly state the position of Axiom.co on intimidation and retaliation. This policy applies to all workforce, volunteers, and management of Axiom.co.
Under no circumstances shall Axiom.co intimidate, threaten, coerce, discriminate against, or take other retaliatory action against any individual for:
1. The exercise of rights guaranteed under HIPAA, including the filing of a HIPAA complaint against Axiom.co;
2. The filing of a HIPAA complaint with the Secretary of HHS;
3. Testifying, assisting, or participating in a HIPAA investigation, compliance, review, proceeding, or hearing;
4. Opposing any act or practice that is counter to the HIPAA regulations, provided the individual or person has a good faith belief that the practice opposed is unlawful, and the manner of the opposition is reasonable and does not involve a disclosure of PHI in violation of HIPAA.
No retaliatory action against an individual or group involved in filing HIPAA complaints or otherwise reporting infractions will be tolerated.
Under no circumstances shall Axiom.co require any member(s) of its workforce, volunteers, or management to waive their rights under HIPAA.
All allegations of HIPAA retaliation against individuals will be reviewed and investigated by Axiom.co in a timely manner.
---
# Privacy policy
Source: https://axiom.co/docs/legal/privacy
{/* vale off */}
At Axiom, Inc. (“Axiom”, “Company” or “we”), we take privacy and the security of data seriously. This policy (“Privacy Policy” or “Policy”) is established to help advise you about how we treat your personal data. By using or accessing any Company websites (collectively, “Sites”), you acknowledge awareness of the practices and policies outlined below, and hereby consent that we will collect, use and share your personal data as described in this Privacy Policy.
Remember that use of our Services is at all times subject to our Terms of Service available at the following URL: [https://axiom.co/docs/legal/terms-of-service](/legal/terms-of-service). Any terms used in this Policy that are otherwise undefined have the definitions given to them in our Terms of Use.
We may modify this Privacy Policy from time to time. When material modifications are made, we will alert you to any such changes by placing a notice on a Site, by sending you an email and/or by some other means. Please note that if you’ve opted not to receive legal notice emails from us (or haven’t provided us with a valid email address), those legal notices will still apply. If you use or access a Site after any changes to the Privacy Policy have been published on our website, you consent and agree to all of the changes.
### **Privacy Policy Table of Contents** [#privacy-policy-table-of-contents]
* What this Privacy Policy Covers
* Personal Data
* How We Disclose Your Personal Data
* Tracking Tools and Opt-Out
* Data Security
* Data Retention
* Personal Data of Children
* California Resident Rights
* Virginia Resident Rights
* Other State Law Privacy Rights
* European Union and United Kingdom Data Subject Rights
* Transfers of Personal Data
* Contact Information
### **What this Privacy Policy Covers** [#what-this-privacy-policy-covers]
Our Privacy Policy covers how we treat Personal Data that we gather when you access or use any Site. “Personal Data” means information that identifies or relates to a particular individual and includes information referred to as “personally identifiable information” or “personal information” under applicable data privacy laws, rules or regulations. Our Privacy Policy does not cover the practices of companies we don’t own or control or people we don’t manage.
### **Personal Data** [#personal-data]
#### **Categories of Personal Data We Collect** [#categories-of-personal-data-we-collect]
This chart details the categories of Personal Data that we collect and have collected over the past 12 months:
| Category of Personal Data | Examples of Personal Data We Collect | Categories of Third Parties With Whom We Share this Personal Data |
| :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------- |
| Profile or Contact Data | First and last name Email address | Service Providers |
| Device/IP Data | IP Address Domain Server Type of device, operating system, browser used to access service | Service Providers |
| Web Analytics | Referring webpage, source through which users accessed the Services Non-identifiable request IDs Statistics associated with the interaction between device or browser and Services Browsing or search history | Service Providers |
| Geolocation Data | IP address-based location information | Service Providers |
#### **Categories of Sources of Personal Data** [#categories-of-sources-of-personal-data]
Personal Data about you is collected from the following categories of sources:
* When you provide such information directly to us.
* When you create an account or use our interactive tools or Sites.
* When you voluntarily provide information in free-form text boxes through the Sites or through responses to surveys or questionnaires.
* When you send us an email or otherwise contact us.
* When you use Sites such information is collected automatically, by us and/or third parties.
* Through Cookies (defined in [Cookies and Similar Technologies](/legal/cookies) ).
* If you use a location-enabled browser, we and/or third parties may receive information about your location.
* If you download and install certain applications and software we make available, we and/or third parties may receive and collect information transmitted from your computing device for the purpose of providing you the relevant Sites, such as information regarding when you are logged on and available to receive updates or alert notices.
#### **Third Parties** [#third-parties]
* Vendors
* We may use analytics providers to analyze how you interact and engage with Sites, or third parties may help us provide you with customer support.
* We may use vendors to obtain information to generate leads and create user profiles.
#### **Our Business Purposes for Collecting or Disclosing Personal Data** [#our-business-purposes-for-collecting-or-disclosing-personal-data]
* Providing, Customizing and Improving Sites
* Creating and managing your account or other user profiles.
* Processing orders or other transactions; billing.
* Providing you with the products, services or information you request.
* Meeting or fulfilling the reason you provided the information to us.
* Providing support and assistance regarding Sites
* Improving the Sits, including testing, research, internal analytics and product development.
* Doing fraud protection, security and debugging.
* Corresponding with You
* Responding to correspondence that we receive from you, contacting you when necessary or requested, and sending you information regarding the Company or Sites.
* Sending emails and other communications according to your preferences or that display content that we think will interest you.
* Meeting Legal Requirements and Enforcing Legal Terms
* Fulfilling our legal obligations under applicable law, regulation, court order or other legal process, such as preventing, detecting and investigating security incidents and potentially illegal or prohibited activities.
We will not collect additional categories of Personal Data or use the Personal Data we collected for materially different, unrelated or incompatible purposes without providing you notice as is described above.
#### **How We Disclose Your Personal Data** [#how-we-disclose-your-personal-data]
We disclose your Personal Data to categories of service providers and other parties listed in this section. Some of these disclosures may constitute a “sale” of your Personal Data as defined under applicable laws. For more information, please refer to the state-specific sections below.
* Service Providers. These parties help us provide the Services or perform business functions on our behalf. They include:
* Hosting, technology and communication providers.
* Analytics providers
* Support and customer service vendors.
* Payment processors.
* Our payment processing partner Stripe, Inc. (“Stripe”) collects your voluntarily-provided payment card information necessary to process your payment.
* Please see Stripe’s terms of service and privacy policy for information on its use and storage of your Personal Data.
### **Fulfilling Legal Obligations** [#fulfilling-legal-obligations]
We may share any Personal Data that we collect with third parties in relation to the activities set forth under “Meeting Legal Requirements and Enforcing Legal Terms” in the “Our Business Purposes for Collecting Personal Data” section above.
### **Business Transfers** [#business-transfers]
Personal Data collected may be transferred to a third party if we undergo a merger, acquisition, bankruptcy or other transaction in which such third party assumes control of our business (in whole or in part). In such an event, we will make reasonable efforts to notify you before your information becomes subject to different privacy and security policies and practices as authorized or mandated by applicable law.
### **Data that is Not Personal Data** [#data-that-is-not-personal-data]
We may create aggregated, de-identified or anonymized data from the Personal Data we collect, including by removing information that makes the data personally identifiable to a particular user. We may use such aggregated, de-identified or anonymized data and share it with third parties for our lawful business purposes, including to analyze, build and improve the Services and promote our business, provided that we will not share such data in a manner that could identify you.
### **Tracking Tools and Opt-Out** [#tracking-tools-and-opt-out]
The Services use cookies and similar technologies such as pixel tags, web beacons, clear GIFs and JavaScript (collectively, “Cookies”) to enable our servers to recognize your web browser, tell us how and when you visit and use our Services, analyze trends, learn about our user base and operate and improve our Services. Cookies are small pieces of data– usually text files – placed on your computer, tablet, phone or similar device when you use that device to access our Services. We may also supplement the information we collect from you with information received from third parties, including third parties that have placed their own Cookies on your device(s).
### **We use the following types of Cookies:** [#we-use-the-following-types-of-cookies]
* Essential Cookies. Essential Cookies are required for providing you with features or services that you have requested. For example, certain Cookies enable you to log into secure areas of our Services. Disabling these Cookies may make certain features and services unavailable.
* Functional Cookies. Functional Cookies are used to record your choices and settings regarding our Services, maintain your preferences over time and recognize you when you return to our Services. These Cookies help us to personalize our content for you, greet you by name and remember your preferences (for example, your choice of language or region).
* Performance/Analytical Cookies. Performance/Analytical Cookies allow us to understand how visitors use our Services. They do this by collecting information about the number of visitors to the Services, what pages visitors view on our Services and how long visitors are viewing pages on the Services. Performance/Analytical Cookies also help us measure the performance of our advertising campaigns to help us improve our campaigns and Services’ content for those who engage with our advertising.
You can decide whether or not to accept Cookies through your internet browser’s settings. Most browsers have an option for turning off the Cookie feature, which will prevent your browser from accepting new Cookies, as well as (depending on the sophistication of your browser software) allow you to decide on acceptance of each new Cookie in a variety of ways. You can also delete all Cookies that are already on your device. If you do this, however, you may have to manually adjust some preferences every time you visit our website and some of the Services and functionalities may not work.
To find out more information about Cookies generally, including information about how to manage and delete Cookies, please visit [https://allaboutcookies.org/](https://allaboutcookies.org/) or [https://ico.org.uk/for-the-public/online/cookies/](https://ico.org.uk/for-the-public/online/cookies/) if you are located in the European Union.
**Information from third party sites**. Our Sites include interfaces that allow you to connect with third party sites, such as when you create an account on our Sites by logging in to your Google account. If you connect to a third party site through the Sites, you authorize us to access, use and store the information that you agreed the third party site could provide to us based on your settings on that third party site. We will access, use and store that information in accordance with this Privacy Policy. You can revoke our access to the information you provide in this way at any time by amending the appropriate settings from within your account settings on the applicable third party site.
**Information Collected Automatically.** We may automatically log information about you and your computer or mobile device when you access our Sites. For example, we may log your operating system name and version, manufacturer and model, device identifier, browser type, screen resolution, the website you visited before browsing to our Sites, pages you viewed, how long you spent on a page, access times, general location information such as city, state or geographic area, and information about your use of and actions on our Sites. We collect this information about you using cookies. Please refer to the [Cookies and Similar Technologies](/legal/cookies) section for more details.
### **Website Monitoring, Recording, and Analytics** [#website-monitoring-recording-and-analytics]
When you interact with Sites, we and our service providers may collect information regarding your interactions in real time, including pages visited, search queries, navigation paths, mouse movements, scrolling activity, clicks, and information entered into forms or similar features. We may use analytics, session replay, or similar technologies to understand how you and users interact with our Sites, improve functionality, and identify technical or security issues. These technologies may record interactions as they occur and are configured to exclude or mask sensitive information, such as passwords or payment details. Our analytics, monitoring, and support providers process this information solely on our behalf, act as our service providers and agents, and are contractually restricted from using such information for their own independent purposes.
**By accessing or using our sites, you consent to the monitoring, recording, and collection of your interactions as described above.**
### **Data Security** [#data-security]
We endeavor to protect your Personal Data from unauthorized access, use and disclosure using appropriate physical, technical, organizational and administrative security measures based on our Services,the type of Personal Data being collected and how we are processing that data. You should also help protect your data by selecting and protecting your password and/or other sign-on mechanism(s) with care; limiting access to your computer or device and browser; and signing off after you have finished accessing your account. Although we work to protect the security of your account and other data that we hold in our records, be aware that no method of transmitting data over the internet or storing data is completely secure.
More information about our security and related compliance measures can be found here:
### **Data Retention** [#data-retention]
We retain Personal Data about you for as long as reasonably necessary to provide you with our Services or otherwise in support of our business or commercial purposes for utilization of your Personal Data, as expressed. When establishing a retention period for particular categories of data, we consider who we collected the data from, our need for the Personal Data, why we collected the Personal Data, and the sensitivity of the Personal Data. In some cases we retain Personal Data for a longer period, if doing so is necessary to comply with our legal obligations, resolve disputes or collect fees owed, or as is otherwise permitted or required by applicable law, rule or regulation. We may further retain information in an anonymous or aggregated form where such information would not identify you personally.
For example:
* We retain your profile information and credentials for as long as you have an account with us.
* We retain your payment data for as long as we need to process your purchase or subscription.
* We retain your device/IP data for as long as we need it to ensure that our systems are working appropriately, effectively and efficiently.
### **Personal Data of Children** [#personal-data-of-children]
As noted in the Terms of Use, we do not knowingly collect or solicit Personal Data from children under 13 years of age; if you are a child under the age of 13, please do not attempt to register for or otherwise use the Services or send us any Personal Data. If we learn we have collected Personal Data from a child under 13 years of age, we will delete that information as quickly as possible. If you believe that a child under 13 years of age may have provided Personal Data to us, please contact us at [privacy@axiom.co](mailto:privacy@axiom.co).
### **California Resident Rights** [#california-resident-rights]
If you are a California resident, you have the rights set forth in this section. Please see the “Exercising Your Rights” section below for instructions regarding how to exercise these rights. Please note that we may process Personal Data of our customers’ end users or employees in connection with our provision of certain services to our customers. If we are processing your Personal Data as a service provider, you may contact the entity that collected your Personal Data in the first instance to address your rights with respect to such data as desired.
If there are any conflicts between this section and any other provision of this Privacy Policy and you are a California resident, the portion that is more protective of Personal Data shall control to the extent of such conflict. If you have any questions about this section or whether any of the following rights apply to you, please contact us at [privacy@axiom.co](mailto:privacy@axiom.co).
### **Access** [#access]
You have the right to request certain information about our collection and use of your Personal Data over the past 12 months. In response, we will provide you with the following information:
* The categories of Personal Data that we have collected about you.
* The categories of sources from which that Personal Data was collected.
* The business or commercial purpose for collecting or selling your Personal Data.
* The categories of third parties with whom we have shared your Personal Data.
* The specific pieces of Personal Data that we have collected about you.
If we have disclosed your Personal Data to any third parties for a business purpose over the past 12 months, we will identify the categories of Personal Data shared with each category of third party recipient. If we have sold your Personal Data over the past 12 months, we will identify the categories of Personal Data sold to each category of third party recipient.
### **Deletion** [#deletion]
You have the right to request that we delete the Personal Data that we have collected about you. Under the CCPA, this right is subject to certain exceptions: for e.g., we may need to retain your Personal Data to provide you with the Services or complete a transaction or other action you may have requested, or if deletion of your Personal Data involves disproportionate effort to achieve. If your deletion request is subject to one of these exceptions, we may deny your deletion request to such data.
### **Correction** [#correction]
You have the right to request that we correct any inaccurate Personal Data we have collected about you. Under the CCPA, this right is subject to certain exceptions: for example, if we reasonably decide, based on the totality of circumstances related to your Personal Data, that such data is correct. If your correction request is subject to one of these CCPA exceptions, we may deny your request to correct such data.
### **Processing of Sensitive Personal Information Opt-Out** [#processing-of-sensitive-personal-information-opt-out]
Consumers have certain rights over the processing of their sensitive information. However, we do not intentionally collect sensitive categories of personal information, but it is possible to share sensitive information with us through your use of the Services. It is your responsibility not to share any such sensitive information when you use the Services.
### **Personal Data Sales Opt-Out and Opt-In** [#personal-data-sales-opt-out-and-opt-in]
We will not sell your Personal Data, and have not done so over the last 12 months. To our knowledge, we do not sell the Personal Data of minors under 16 years of age. Under the CCPA, California residents have certain rights when a business “shares” Personal Data with third parties for purposes of cross-contextual behavioral advertising. We have shared the foregoing categories of Personal Data for the purposes of cross-contextual behavioral advertising.
Under California Civil Code Sections 1798.83-1798.84, California residents are entitled to contact us to prevent disclosure of Personal Data to third parties for such third parties’ direct marketing purposes; in order to submit such a request, please contact us at [privacy@axiom.co](mailto:privacy@axiom.co).
Your browser may offer you a “Do Not Track” option, which allows you to signal to operators of websites and web applications and services that you do not wish such operators to track certain of your online activities over time and across different websites. Our Services do not support Do Not Track requests at this time. To find out more about “Do Not Track,” you can visit [www.allaboutdnt.com](https://www.allaboutdnt.com/).
### **Virginia Resident Rights** [#virginia-resident-rights]
If you are a Virginia resident, you have the rights set forth under the Virginia Consumer Data Protection Act (“VCDPA”). Please see the “Exercising Your Rights” section below for instructions regarding how to exercise these rights. Please note that we may process Personal Data of our customers’ end users or employees in connection with our provision of certain services to our customers. If we are processing your Personal Data as a service provider, you should contact the entity that collected your Personal Data in the first instance to address your rights with respect to such data. Additionally, please note that these rights are subject to certain conditions and exceptions under applicable law, which may permit or require us to deny your request.
If there are any conflicts between this section and any other provision of this Privacy Policy and you are a Virginia resident, the portion that is more protective of Personal Data shall control to the extent of such conflict. If you have any questions about this section or whether any of the following rights apply to you, please contact us at [privacy@axiom.co](mailto:privacy@axiom.co).
### **Access** [#access-1]
You have the right to request confirmation of whether or not we are processing your Personal Data and to access your Personal Data.
### **Correction** [#correction-1]
You have the right to correct inaccuracies in your Personal Data, to the extent such correction is appropriate in consideration of the nature of such data and our purposes of processing your Personal Data.
### **Portability** [#portability]
You have the right to request a copy of your Personal Data in a machine-readable format, to the extent technically feasible.
### **Deletion** [#deletion-1]
You have the right to delete your Personal Data.
### **Opt-Out of Certain Processing Activities** [#opt-out-of-certain-processing-activities]
* You have the right to opt-out of the processing of your Personal Data for targeted advertising purposes. We do not process your Personal Data for targeted advertising purposes.
* You have the right to opt-out to the sale of your Personal Data. We do not currently sell your Personal Data as defined under the VCDPA.
* You have the right to opt-out from the processing of your Personal Data for the purposes of profiling in furtherance of decisions that produce legal or similarly significant effects to you, if applicable.
### **Appealing a Denial** [#appealing-a-denial]
If we refuse to take action on a request within a reasonable period of time after receiving your request in accordance with this section, you may appeal our decision. In such an appeal, you must (1) provide sufficient information to allow us to verify that you are the person about whom the original request pertains and to clearly identify the original request, and (2) provide a sufficient description of the basis of your appeal. Please note that your appeal will be subject to your rights and obligations afforded to you under the VCDPA. We will respond to your appeal within 60 days of receiving your request. If we deny your appeal, you have the right to contact the Virginia Attorney General using the methods described at [https://www.oag.state.va.us/consumer-protection/index.php/file-a-complaint](https://www.oag.state.va.us/consumer-protection/index.php/file-a-complaint).
You may appeal a decision by us using the following methods:
* Email us at: [privacy@axiom.co](mailto:privacy@axiom.co) (title must include “VCDPA Appeal”)
* Call us at: (628) 200-3079
### **Exercising Your Rights under CCPA** [#exercising-your-rights-under-ccpa]
To exercise the rights described in this Privacy Policy, you or, if you are a California resident, your Authorized Agent (as defined below) can send us a request that (1) provides sufficient information to allow us to adequately verify that you are the person about whom we have collected Personal Data, and (2) describes your request in sufficient detail to allow us to understand, evaluate and respond ( a “Valid Request”). We are not obligated to respond to requests that do not meet these criteria. We will only use Personal Data provided in a Valid Request to verify your identity and complete your request.
We are committed to respond to Valid Requests within the time frame required by applicable law. We will not charge you a fee for making a Valid Request unless your Valid Request(s) is excessive, repetitive or manifestly unfounded. If we determine that your Valid Request warrants a fee, we will notify you of the fee and explain that decision before completing your request.
You may submit a Valid Request using the following methods:
* Email us at: [privacy@axiom.co](mailto:privacy@axiom.co)\
Call us at: (628) 200-3079
If you are a California resident, you may also authorize an agent (an “Authorized Agent”) to exercise your rights on your behalf.
### **We Will Not Discriminate Against You for Exercising Your Rights** [#we-will-not-discriminate-against-you-for-exercising-your-rights]
We will not discriminate against you for exercising your rights under applicable data protection laws. We will not deny you our goods or services, charge you different prices or rates, or provide you a lower quality of goods and services if you exercise your rights under applicable law. However, we may offer different tiers of our Services, as allowed by applicable law, with varying prices, rates or levels of quality of the goods or services you receive related to the value of Personal Data that we receive from you.
## **Other State Law Privacy Rights** [#other-state-law-privacy-rights]
### **Nevada Resident Rights** [#nevada-resident-rights]
If you are a resident of Nevada, you have the right to opt-out of the sale of certain Personal Data to third parties. You can exercise this right by contacting us at [privacy@axiom.co](mailto:privacy@axiom.co) with the subject line “Nevada Do Not Sell Request” and providing us with your name and the email address associated with your account.
## **European Union and United Kingdom Data Subject Rights** [#european-union-and-united-kingdom-data-subject-rights]
### **EU and UK Residents** [#eu-and-uk-residents]
If you are a resident of the European Union (“EU”), United Kingdom (“UK”), Lichtenstein, Norway or Iceland, you may have additional rights under the EU or UK General Data Protection Regulation (the “GDPR”) with respect to your Personal Data, as outlined below.
We use the terms “Personal Data” and “processing” as they are defined in the GDPR in this section, but “Personal Data” generally means information that can be used to individually identify a person, and “processing” generally covers actions that can be performed in connection with data such as collection, use, storage and disclosure. Company will be the controller of your Personal Data processed in connection with the Services.
If there are any conflicts between this section and any other provision of this Privacy Policy, the policy or portion that is more protective of Personal Data shall control to the extent of such conflict. If you have any questions about this section or whether any of the following applies to you, please contact us at [privacy@axiom.co](mailto:privacy@axiom.co). Note that we may also process Personal Data of our customers’ end users or employees in connection with our provision of certain services to you, in which case we are the processor of Personal Data. If we are the processor of your Personal Data, please contact the controller party in the first instance to address your rights with respect to such data.
### **Personal Data We Collect** [#personal-data-we-collect]
The “Categories of Personal Data We Collect” section above details the Personal Data that we collect from you.
### **Personal Data Use and Processing Grounds** [#personal-data-use-and-processing-grounds]
The “Our Commercial or Business Purposes for Collecting Personal Data” section above explains how we use your Personal Data.
We will only process your Personal Data if we have a lawful basis for doing so. Lawful bases for processing include consent, contractual necessity and our “legitimate interests” or the legitimate interest of others, as further described below.
* Contractual Necessity: We process the following categories of Personal Data as a matter of “contractual necessity”, meaning that we need to process the data to perform under our Terms of Use with you, which enables us to provide you with the Services. When we process data due to contractual necessity, failure to provide such Personal Data will result in your inability to use some or all portions of the Services that require such data.
* Profile or Contact Data
* Payment Data
* Legitimate Interest: We process the following categories of Personal Data when we believe it furthers the legitimate interest of us or third parties:
* Device/IP Data
* Web Analytics
* We may also de-identify or anonymize Personal Data to further our legitimate interests.
* Examples of these legitimate interests include (as described in more detail above):
* Providing, customizing and improving the Services.
* Marketing the Services.
* Corresponding with you.
* Meeting legal requirements and enforcing legal terms.
* Completing corporate transactions.
* Consent: In some cases, we process Personal Data based on the consent you expressly grant to us at the time we collect such data.
* Other Processing Grounds: From time to time we may also need to process Personal Data to comply with a legal obligation, if it is necessary to protect the interests of you or other data subjects, or if it is necessary in the public interest.
### **Sharing Personal Data** [#sharing-personal-data]
The “How We Share Your Personal Data” section above details how we share your Personal Data with third parties.
### **EU Data Subject Rights** [#eu-data-subject-rights]
For more information about these EU or UK personal data terms and your rights related thereto, or to submit a request for information, please email us at [privacy@axiom.co](mailto:privacy@axiom.co). Please note that in some circumstances, we may not be able to fully comply with your request, such as if it is frivolous or impractical, if it jeopardizes the rights of others, or if it is not required by law, but, in those circumstances, we are committed to respond to notify you of such a decision regardless. In some cases, we may also need you to provide us with additional information, which may include Personal Data, if necessary to verify your identity and the nature of your request.
Access: You can request more information about the Personal Data we hold about you and request a copy of such Personal Data. You can also access certain of your Personal Data by logging on to your account.
Rectification: If you believe that any Personal Data we are holding about you is incorrect or incomplete, you can request that we correct or supplement such data. You can also correct some of this information directly by logging on to your account.
Erasure: You can request that we erase some or all of your Personal Data from our systems.
Withdrawal of Consent: If we are processing your Personal Data based on your consent, you have the right to withdraw your consent at any time. Please note, however, that if you exercise this right, you may have to then provide express consent on a case-by-case basis for the use or disclosure of certain of your Personal Data, if such use or disclosure is necessary to enable you to utilize some or all of our Services.
Portability: You can ask for a copy of your Personal Data in a machine-readable format. You can also request that we transmit the data to another controller where technically feasible.
Objection: You can contact us to let us know that you object to the further use or disclosure of your Personal Data for certain purposes, such as for direct marketing purposes.
Restriction of Processing: You can ask us to restrict further processing of your Personal Data.
Right to File Complaint: You have the right to lodge a complaint about Company's practices with respect to your Personal Data with the supervisory authority of your country or EU Member State. A list of Supervisory Authorities is available here: [https://edpb.europa.eu/about-edpb/board/members\_en](https://edpb.europa.eu/about-edpb/board/members_en)
### **Transfers of Personal Data** [#transfers-of-personal-data]
The Services are hosted and operated in the United States (“U.S.”) through Company and its service providers. By using the Services, you acknowledge that any Personal Data about you is being provided to Company in the U.S. and will be hosted on U.S. servers, and you authorize Company to transfer, store and process your information to and in the U.S., and possibly other countries. In some circumstances, your Personal Data may be transferred to the U.S. pursuant to a data processing agreement incorporating legally required data protection clauses.
### **Contact Information:** [#contact-information]
If you have additional questions about this Privacy Policy, the methods in which we collect and use your Personal Data or your choices and rights regarding such collection and use, please do not hesitate to contact us at:
* [https://axiom.co](https://axiom.co)
* [privacy@axiom.co](mailto:privacy@axiom.co)
* Axiom, Inc., 1390 Market Street, Suite 200, San Francisco, CA 94102
---
# SLA
Source: https://axiom.co/docs/legal/sla
{/* vale off */}
Axiom is committed to delivering a reliable, performant, and secure data platform. We want our customers to have full confidence in the Axiom Service, backed by transparent commitments. This Service Quality document covers:
1. **Service availability** and corresponding SLAs
2. **Priorities, support response times, and escalation**
3. **Incident management and communications**
Please note this Axiom Service Quality document is subject to the agreement between Customer and Axiom with respect to the Axiom Service (the “Agreement”). All terms not otherwise defined herein are defined as set forth in the Agreement.
## Service availability [#service-availability]
### Scope [#scope]
This Axiom Service Level Agreement (“SLA”) describes the service availability regarding the Axiom Service specified in the applicable Service Description or otherwise ordered by Customer. This SLA applies to the core functionality of the applicable Axiom Service: components such as data ingestion, query execution, alerting, and access to the Axiom Console and API; it does not apply to preview features unless otherwise noted herein.
### Availability commitments [#availability-commitments]
Axiom provides the following target uptime (the “Monthly Uptime Percentage” or “MUP”) with respect to Axiom Cloud:
| Axiom Service | Monthly Uptime Percentage |
| :------------ | :----------------------------- |
| Axiom Cloud | At least 99.9% (“three nines”) |
Axiom measures MUP on a calendar-month basis by taking the total minutes in a calendar month, subtracting any Scheduled Downtime and Exclusions, and then calculating how many of the remaining minutes the Axiom service was available.
### Scheduled and unscheduled downtime [#scheduled-and-unscheduled-downtime]
* **Scheduled Downtime:** means the maintenance windows that Axiom communicates to Customer at least seven (7) days in advance. Axiom strives to limit Scheduled Downtime to three (3) hours per month.
* **Unscheduled Downtime**: means any period of time the Axiom Service is unavailable, other than Scheduled Downtime or Exclusions. For example, system failures, unplanned service degradations, or regional outages.
* **Exclusions:** means any of the following:
* emergency maintenance;
* circumstances or events beyond the reasonable control of Axiom, including without limitation, any Force Majeure events;
* the performance or availability of local internet-service-providers engaged by Customer, and outages directly traceable to external providers (e.g., cloud platforms, DNS);
* issues resulting from Customer’s use of third-party hardware, software, or systems, network, firewalls, or infrastructure preventing normal Axiom usage;
* use by Customer of the Axiom Service not in strict compliance with the applicable Documentation;
* incidents caused by misuse, negligence, or unapproved modifications of Axiom Services;
* Unscheduled Downtime with respect to any Axiom Service made available on a no-fee or trial basis, or for which Customer has not paid to Axiom all of the applicable fees;
* Services clearly labelled “Preview”; and
* any issues resulting from changes to a Customer’s configuration of the Axiom Service (e.g., configuration changes related to BYOB or BYOC).
**“Incident**” means a failure to meet the applicable MUP due to Unscheduled Downtime.
### Service credits [#service-credits]
If an Incident occurs with respect to Axiom Service , Customer may request credits with respect to such Axiom Service (“Credits”). To receive Credits, Customer must meet the following requirements:
* Notify the Axiom support team within thirty (30) days of the date the Incident occurred with the following phrase in the title: “SLA Incident” (the “Notice”);
* Notice must include: logs and supporting information reasonably necessary for Axiom to confirm the SLA Issue; and dates and times with respect to the SLA Issue.
Once verified by Axiom, Credits will be issued as a percentage of Customer’s monthly (or prorated) bill for the affected Axiom Service. Customer acknowledges and agrees that notwithstanding anything to the contrary: (a) the Credit is the sole and exclusive remedy with respect to any SLA Issue; (b) the Credit may only be used by Customer to reduce the applicable monthly fees with respect to future payment obligations regarding the Axiom Service impacted by the Incident; (c) Credits may not be converted into cash, cash equivalents, or entitle Customer to a refund; (d) any unused Credits shall expire and be of no use following the expiration or termination of the applicable Service Order or the Agreement; and (e) Credits may not be used to reduce the applicable monthly fees by more than fifty percent (50%).
The table below shows how Credits are calculated:
| Monthly Uptime Percentage | Service Credit Percentage |
| :------------------------------------- | :------------------------ |
| **Axiom Cloud:** \< 99.9% but >= 99.0% | 10% |
| **Axiom Cloud:** \< 99.0% but >= 95.0% | 25% |
| **Axiom Cloud:** \< 95.0% | 50% |
## Priorities, support response times, and escalation [#priorities-support-response-times-and-escalation]
### Bug resolution [#bug-resolution]
Axiom investigates all reported Issues. Axiom uses commercially reasonable efforts to fix or provide a workaround as set forth in this Service Quality document. If Axiom needs more time to address an Issue, Axiom will escalate internally and keep Customer informed regarding progress.
“Issue” means an incident that investigation reveals is caused by the Axiom Service’s failure to perform materially in accordance with the specifications set forth in the Documentation for such Axiom Service. An incident will not be classified as an Issue if (a) the relevant Axiom Service is not used for its intended purpose; (b) the incident is caused by Customer’s or a third party’s software or equipment; or (c) the version of the Axiom Service on which the Issue has purportedly occurred is not the most current version of such Axiom Service made available to Customer under the Agreement.
### Priority levels and escalation [#priority-levels-and-escalation]
Axiom offers **Standard** and **Premium** support. Standard support is included for all paying customers. Premium support is available as an add-on. Axiom classifies Issues by priority, ensuring faster responses and more frequent updates for critical issues.
| Priority | Description | Standard Response | Premium Response | Update Frequency |
| :---------------- | :---------------------------------------------------------------------------------------------- | :---------------- | :--------------- | :---------------------------------- |
| **Critical** (P1) | Issue results in complete system failure or critical business process disrupted. | 3 hours (24x7) | 1 hour (24x7) | Hourly |
| **High** (P2) | Issue results in partial system failures or disruptions to important business processes. | 1 business day | 4 hours (24x7) | Every 4 hours during business hours |
| **Medium** (P3) | Issue results in minor system failures or disruptions to non-critical business processes. | 2 business days | 1 business day | Weekly |
| **Low** (P4) | Issue results in minor issues or requests that do not significantly affect business operations. | 3 business days | 2 business days | On material updates |
For Critical, High, and Medium Issues that require additional attention, Axiom offers a tiered escalation process:
1. Technical Customer Support Engineer
2. Head of Product
3. VP of Engineering
4. CTO
Escalations to these levels are not automatic but are considered based on the nature and urgency and impact of the Issue. Axiom’s support team will manage this process, ensuring that the right people are involved at the right time.
#### Escalating a request [#escalating-a-request]
* If your request is already being handled by Customer Support, reply in the existing support thread and request an escalation.
* If you have a new request, write an email to [support@axiom.co](mailto:support@axiom.co) with "Escalation Request" in the subject line.
Our team monitors these channels closely and will respond promptly to escalation requests.
### Business hours [#business-hours]
Axiom Customer Support is offered during business hours as set forth below:
* **Business hours**: 8:00 AM – 6:00 PM Eastern Time (US) on weekdays, excluding U.S. public holidays.
* **EU support**: For EU-based requests, 8:00 AM – 6:00 PM ET on weekdays, excluding local public holidays.
## Incident management and communications [#incident-management-and-communications]
Axiom continuously monitors systems and has an on-call rotation that spans multiple time zones. For real-time visibility of incidents and maintenance, visit [status.axiom.co](https://status.axiom.co). In the event of **Critical** (P1) or **High** (P2) issues:
1. We will post updates to [status.axiom.co](https://status.axiom.co) (at least hourly during business hours).
2. Once resolved, we will share a post-mortem for Critical (P1) issues detailing what happened, how we mitigated the Issue, and how we plan to prevent future Issues.
An Issue will be considered resolved when one of the following has been completed:
1. a resolution to the Issue is made available to Customer;
2. a computer software code change in the form of a patch or a new revision that corrects the Issue is made available to Customer;
3. a short-term workaround is made available to Customer; or
4. an engineering commitment is made to correct the Issue in a future release of the Axiom Service.
Axiom is not obligated to correct any Issue or issue that meets any of the following conditions:
1. where the Axiom Service is not used for its intended purpose; or
2. where the Axiom Service has been altered, damaged, modified in a manner not approved in writing by Axiom; or
3. where the Axiom Service is a version that is no longer supported by Axiom; or
4. which is caused by Customer’s or a third party’s software, equipment, network or system;
5. which is caused by Customer’s negligence, abuse, misapplication, or use of the Axiom Service other than as specified in the Documentation; or
6. which would be resolved by the Customer using an error correction or update regarding the Axiom Service.
Customer acknowledges that new features may be added to the Axiom Service based on market demand and technological innovation. Accordingly, as Axiom develops enhanced versions of the Axiom Service, Axiom may cease to maintain and support older versions.
---
# Terms of service
Source: https://axiom.co/docs/legal/terms-of-service
{/* vale off */}
PLEASE READ THESE TERMS OF SERVICE CAREFULLY BEFORE USING THE SERVICE OFFERED BY AXIOM, INC. (“**AXIOM**”). BY MUTUALLY EXECUTING ONE OR MORE SERVICE ORDERS WITH AXIOM WHICH REFERENCE THESE TERMS (EACH, A “**SERVICE** **ORDER**”) OR BY ACCESSING OR USING THE SERVICES IN ANY MANNER, YOU (“**YOU**” OR “**CUSTOMER**”) AGREE TO BE BOUND BY THESE TERMS (TOGETHER WITH THE APPLICABLE SERVICE DESCRIPTION, THE “**AGREEMENT**”) TO THE EXCLUSION OF ALL OTHER TERMS. YOU REPRESENT AND WARRANT THAT YOU HAVE THE AUTHORITY TO ENTER INTO THIS AGREEMENT; IF YOU ARE ENTERING INTO THIS AGREEMENT ON BEHALF OF AN ORGANIZATION OR ENTITY, REFERENCES TO “CUSTOMER” AND “YOU” IN THIS AGREEMENT, REFER TO THAT ORGANIZATION OR ENTITY. IF YOU DO NOT AGREE TO ALL OF THE FOLLOWING, YOU MAY NOT USE OR ACCESS THE SERVICES IN ANY MANNER. IF THE TERMS OF THIS AGREEMENT ARE CONSIDERED AN OFFER, ACCEPTANCE IS EXPRESSLY LIMITED TO SUCH TERMS.
**1 SCOPE OF SERVICE AND RESTRICTIONS**
**1.1 Access to and Scope of Service**. Subject to Axiom’ receipt of the applicable Fees with respect to the service specified in the corresponding Service Description (the “**Service**”), Axiom will use commercially reasonable efforts to make the Service available to Customer as set forth in this Agreement. Subject to Customer’s compliance with the terms and conditions of the Agreement, Customer may access and use the Service as specified in the Service Description and the applicable Supplemental Terms. ”**Service Description**” means the applicable use limitations, fees, use period, Supplemental Terms and related limitations with respect to the applicable Service. The applicable Service Description is available on the Axiom website, presented in connection with billing or invoicing, or as specified in a Service Order.
**1.2 Supplemental Terms**. The Service is available in a number of versions, packages and implementation types (each an “**Offering**”). Each Offering may be subject to additional requirements, use limitation and associated specifics with respect to the operation and use of such Offering (the “**Supplemental Terms**”). The Supplemental Terms will be specified in the applicable Service Description.
**1.3 Trials.** If Customer is accessing or making use of the Service on a trial basis or no-fee basis as identified in the corresponding Service Description (the “**Trial**”), Customer may use the Service during the Trial provided that (a) access or such use does not to exceed the scope of the corresponding Service Description; (b) Customer acknowledges and agrees that the Trial is made available on an “as-is” basis without support, warranty, or indemnification; and (c) Axiom shall have no liability or obligation with respect to any Trial.
**1.4 Restrictions**. Customer will use the Service only in accordance with all applicable laws, including, but not limited to, rules and regulations related to data and personally identifiable information. Customer agrees not to, and will not allow any third party to: (i) remove or otherwise alter any proprietary notices or labels from the Service or any portion thereof; (ii) reverse engineer, decompile, disassemble, or otherwise attempt to discover the underlying structure, ideas, or algorithms of the Service or any software used to provide or make the Service available; (iii) rent, resell or otherwise allow any third party access to or use of the Service; (iv) use or access the Service in any manner inconsistent with the applicable Service Description; or (v) use or access of the Service in any manner inconsistent with the Axiom Acceptable Use Policy available at the following URL: [https://axiom.co/docs/legal/acceptable-use-policy](https://axiom.co/docs/legal/acceptable-use-policy) (the “**AUP**”).
**1.5 Ownership**. Axiom retains all right, title, and interest in and to the: the Service, Documentation, Axiom Confidential Information; any improvements to and derivative works of the same; Axiom Templates; and all other intellectual property created, used, provided, or made available by Axiom under or in connection with the Service (collectively, “**Axiom IP**”). Customer may from time to time provide suggestions, comments, or other feedback to Axiom with respect to the Service or Documentation (“**Feedback**”). Customer shall, and hereby does, grant to Axiom a nonexclusive, worldwide, perpetual, irrevocable, transferable, sublicensable, royalty-free, fully paid-up license to use the Feedback for any purpose.
**1.6 Customer Data**. Customer is solely responsible for Customer Data including, but not limited to: (a) compliance with all applicable laws and this Agreement; (b) any claims relating to Customer Data; and (c) any claims that Customer Data infringes, misappropriates, or otherwise violates the rights of any third party. Customer acknowledges and agrees that Customer Data may be irretrievably deleted after fifteen (15) days following a termination or expiration of this Agreement. Customer authorizes Axiom to use Customer Data as necessary to provide the Service to Customer. For purposes of this Agreement, “**Customer Data**” shall mean any data, information or other material provided, uploaded, or submitted by Customer to the Service in the course of using the Service. Customer shall retain all right, title and interest in and to the Customer Data, including all intellectual property rights therein.
**1.7 Telemetry Data** Axiom may collect or otherwise receive Telemetry Data and use the same in connection with improvements to the Service, and to monitor Customer’s compliance with the Service Description and the terms of this Agreement. “**Telemetry Data**” shall mean data and information generated by or collected by Axiom regarding Customer’s use of the Service, the health and performance of the Service, and related information, excluding any Customer Data. Telemetry Data will be held in aggregated and anonymized format and will not identify Customer, or reveal any Customer Confidential Information.
**1.8 Personal Data.** Customer acknowledges and agrees that the exchange of personal information subject to applicable personal data laws or regulations (“PII”) is not required to make use of the Service, and to the extent Customer transfers, submits, or otherwise makes PII available to Axiom or the Service, Customer agrees to Axiom’s Data Processing Agreement available at the following URL: [https://axiom.co/docs/legal/data-processing](https://axiom.co/docs/legal/data-processing) (the “DPA”).
**1.9 Support**. To the extent Axiom support is specified in the applicable Service Description, Axiom will use commercially reasonable efforts to provide support for the Service according to the Axiom Support Policy available at the following URL: [https://axiom.co/support](https://axiom.co/support) (“**Axiom Support**”).
**1.10 Service Suspension**. Axiom may suspend Customer’s access to or use of the Service as follows: (a) immediately if Axiom reasonably believes Customer’s use of the Service may pose a security risk to or may adversely impact the Service; (b) immediately if Customer become insolvent, has ceased to operate in the ordinary course, made an assignment for the benefit of creditors, or becomes the subject of any bankruptcy, reorganization, liquidation, dissolution or similar proceeding; (c) following thirty (30) days written notice if Customer is in breach of this Agreement or any Service Description (and has not cured such breach, if curable, within the thirty (30) days of such notice); or (d) Customer has failed to pay Axiom the Fees with respect to the Service. If any amount owing by Customer is thirty (30) or more days overdue (or 10 or more days overdue in the case of invoices to be paid by credit card), Axiom may, without limiting any rights and remedies, accelerate Customer’s unpaid fee obligations to become immediately due and payable, and suspend the provision of the Service to Customer until the overdue amounts are paid in full.
**2 FEES,TAXES, AND AUTHORIZED RESELLERS**
**2.1 Fees and Invoicing Terms**. Customer shall pay to Axiom the fees as set forth in each applicable Service Description according to the billing frequency and method stated therein or otherwise presented to Customer (the “**Fees**”). Customer shall provide accurate and updated billing contact information. If Fees are not received by Axiom by the due date, then without limiting Axiom’ rights or remedies: (a) those charges may accrue late interest at the rate of 1.5% of the outstanding balance per month, or the maximum rate permitted by law, whichever is lower, and (b) Axiom may condition future renewals and Service Descriptions on different payment terms.
**2.2 Taxes**. Any and all payments made by Customer in accordance with this Agreement are exclusive of any taxes that might be assessed by any jurisdiction. Customer shall pay or reimburse Axiom for all value-added, sales, use, property, and similar taxes; all customs duties, import fees, stamp duties, license fees and similar charges; and all other mandatory payments to government agencies of whatever kind, except taxes imposed on the net or gross income of Axiom. All amounts payable to Axiom under this Agreement shall be without set-off and without deduction of any taxes, levies, imposts, charges, withholdings or duties of any nature which may be levied or imposed, including without limitation, value added tax, customs duty and withholding tax. To the extent Customer is required by the local taxing authority to withhold value added tax or a similar withholding tax, Customer agrees to true-up Fees payable to Axiom to account for such withholding.
**2.3 Authorized Resellers**. Customer may purchase subscriptions to the Service through a third party authorized in writing by Axiom to resell subscriptions to the Service (an “**Authorized Reseller**”). Service subscriptions resold to Customer by an Authorized Reseller (each a “**Resale Transaction**”) are subject to the terms and conditions of this Agreement, other than Sections 2.1, and 2.2. Customer will pay the Authorized Reseller the applicable fees according to the payment terms, fees, refund rights (if any), and associated commercial terms determined by and between Customer and the corresponding Authorized Reseller.
**3 TERM AND TERMINATION**
**3.1 Term**. The term of this Agreement shall commence on the Effective Date and unless terminated earlier according to this Section 3, will end on the last day of the term specified in the Service Description (the “**Term**”). Unless otherwise specified in the Service Description or the applicable Service Order, each Service Description will renew automatically at the end of the applicable term (each such renewal, a “**Service Renewal**”), unless either party provides to the other advance written notice with respect to non-renewal at least thirty (30) days prior to the end of the then current term. Customer acknowledges and agrees that each Service Renewal shall be subject to the then-current, on-demand standard use rates, unless otherwise specified in the applicable Service Description.
**3.2 Termination**. This Agreement and the Service Descriptions hereunder may be terminated: (a) by either party if the other has materially breached this Agreement, within thirty (30) calendar days after written notice of such breach to the other party if the breach is remediable or immediately upon notice if the breach is not remediable; or (b) by Axiom upon written notice to Customer if Customer (i) has made or attempted to make any assignment for the benefit of its creditors or any compositions with creditors, (ii) has any action or proceedings under any bankruptcy or insolvency laws taken by or against it which have not been dismissed within sixty (60) days, (iii) has effected a compulsory or voluntary liquidation or dissolution, or (iv) has undergone the occurrence of any event analogous to any of the foregoing under the law of any jurisdiction.
**3.3 Effect of Termination**. Upon any expiration or termination of this Agreement, Customer shall (i) immediately cease use of the Service, and (ii) return all Axiom Confidential Information and other materials and information provided by Axiom. Any termination or expiration shall not relieve Customer of its obligation to pay all Fees accruing prior to termination. If the Agreement is terminated due to Section 3.2 (a), Customer shall pay to Axiom all Fees set forth in the corresponding Service Description(s).
**3.4 Survival.** The following provisions will survive termination of this Agreement: Sections 1.4, 1.5, 1.7, 2.1, 2.2, 3, 4, 5, 6.3, 7, and 8.
**4 CONFIDENTIALITY**
During the term of this Agreement, either party may provide the other party with confidential and/or proprietary materials and information (“**Confidential Information”**). All materials and information provided by the disclosing party and identified at the time of disclosure as “Confidential” or bearing a similar legend, and all other information that the receiving party reasonably should have known was the Confidential Information of the disclosing party, shall be considered Confidential Information. This Agreement is Confidential Information, and all pricing terms are Axiom Confidential Information. The receiving party shall maintain the confidentiality of the Confidential Information and will not disclose such information to any third party without the prior written consent of the disclosing party. The receiving party will only use the Confidential Information internally for the purposes contemplated hereunder. The obligations in this Section 4 shall not apply to any information that: (a) is made generally available to the public without breach of this Agreement, (b) is developed by the receiving party independently from and without reference to the Confidential Information, (c) is disclosed to the receiving party by a third party without restriction, or (d) was in the receiving party’s lawful possession prior to the disclosure and was not obtained by the receiving party either directly or indirectly from the disclosing party. The receiving party may disclose Confidential Information as required by law or court order; provided that, the receiving party provides the disclosing with prompt written notice thereof and uses the receiving party’s best efforts to limit disclosure. At any time, upon the disclosing party’s written request, the receiving party shall return to the disclosing party all disclosing party’s Confidential Information in its possession, including, without limitation, all copies and extracts thereof.
**5 INDEMNIFICATION**
**5.1 Indemnification by Customer**. Customer will defend, indemnify, and hold Axiom, its affiliates, suppliers and licensors harmless and each of their respective officers, directors, employees and representatives from and against any claims, damages, losses, liabilities, costs, and expenses (including reasonable attorneys’ fees) arising out of or relating to any third party claim with respect to: (a) Customer Data; (b) breach of this Agreement or violation of applicable law by Customer; or (c) alleged infringement or misappropriation of third-party’s intellectual property rights resulting from Customer Data.
**5.2 Indemnification by Axiom**. Axiom will defend, indemnify, and hold Customer harmless from and against any third-party claims, damages, losses, liabilities, costs, and expenses (including reasonable attorneys’ fees) arising from claims by a thirty party that Customer’s use of the Service directly infringes or misappropriates a third party’s intellectual property rights (an “**Infringement Claim**”). Notwithstanding anything to the contrary, Axiom shall have no obligation to indemnify or reimburse Customer with respect to any Infringement Claim to the extent arising from: (a) the combination of any Customer Data with the Service; (b) the combination of any products or services, other than those provided by Axiom to Customer under this Agreement, with the Service; or (c) non-discretionary designs or specifications provided to Axiom by Customer that caused such Infringement Claim. Customer agrees to reimburse Axiom for any and all damages, losses, costs and expenses incurred as a result of any of the foregoing actions.
**5.3 Notice of Claim and Indemnity Procedure**. In the event of a claim for which a party seeks indemnity or reimbursement under this Section 5 (each an “**Indemnified Party**”) and as conditions of the indemnity, the Indemnified Party shall: (a) notify the indemnifying party in writing as soon as practicable, but in no event later than thirty (30) days after receipt of such claim, together with such further information as is necessary for the indemnifying party to evaluate such claim; and (b) the Indemnified Party allows the indemnifying party to assume full control of the defense of the claim, including retaining counsel of its own choosing. Upon the assumption by the indemnifying party of the defense of a claim with counsel of its choosing, the indemnifying party will not be liable for the fees and expenses of additional counsel retained by any Indemnified Party. The Indemnified Party shall cooperate with the indemnifying party in the defense of any such claim. Notwithstanding the foregoing provisions, the indemnifying party shall have no obligation to indemnify or reimburse for any losses, damages, costs, disbursements, expenses, settlement liability of a claim or other sums paid by any Indemnified Party voluntarily, and without the indemnifying party’s prior written consent, to settle a claim. Subject to the maximum liability set forth in Section 7, the provisions of this Section 5 constitute the entire understanding of the parties regarding each party’s respective liability under this Section 5, including but not limited to Infringement Claims (including related claims for breach of warranty) and each party’s sole obligation to indemnify and reimburse any Indemnified Party.
**6 WARRANTY**
**6.1 Warranty.** The Service, when used by Customer in accordance with the provisions of this Agreement and in compliance with the Service documentation published by Axiom (the “**Documentation**”), will conform in material respects, to the Documentation during the period of use specified in the applicable Service Description.
**6.2 Exclusive Remedies.** Customer shall report to Axiom, pursuant to the notice provision of this Agreement, any breach of the warranties set forth in this Section 6. In the event of a breach of warranty by Axiom under this Agreement, Customer’s sole and exclusive remedy, and Axiom’ entire liability, shall be prompt correction of any material non-conformance in order to minimize any material adverse effect on Customer’s business.
**6.3 Disclaimer of Warranty**. Axiom does not represent or warrant that the operation of the Service (or any portion thereof) will be uninterrupted or error free, or that the Service (or any portion thereof) will operate in combination with other hardware, software, systems, or data not provided by Axiom, except as expressly specified in the applicable Documentation. CUSTOMER ACKNOWLEDGES THAT, EXCEPT AS EXPRESSLY SET FORTH IN SECTION 6.1, AXIOM MAKES NO EXPRESS OR IMPLIED REPRESENTATIONS OR WARRANTIES OF ANY KIND WITH RESPECT TO THE SERVICE OR SERVICES, OR THEIR CONDITION. AXIOM IS FURNISHING THE WARRANTIES SET FORTH IN SECTION 6.1 IN LIEU OF, AND AXIOM HEREBY EXPRESSLY EXCLUDES, ANY AND ALL OTHER EXPRESS OR IMPLIED REPRESENTATIONS OR WARRANTIES, WHETHER UNDER COMMON LAW, STATUTE OR OTHERWISE, INCLUDING WITHOUT LIMITATION ANY AND ALL WARRANTIES AS TO MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, SATISFACTORY QUALITY, NON-INFRINGEMENT OF THIRD-PARTY RIGHTS.
**7 LIMITATIONS OF LIABILITY**
IN NO EVENT SHALL AXIOM BE LIABLE FOR ANY, LOST PROFITS, BUSINESS INTERRUPTION, REPLACEMENT SERVICE OR OTHER SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE, OR INDIRECT DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THEORY OF LIABILITY. AXIOM’S LIABILITY FOR ALL CLAIMS ARISING UNDER THIS AGREEMENT, WHETHER IN CONTRACT, TORT OR OTHERWISE, SHALL NOT EXCEED THE AMOUNT OF FEES PAID OR PAYABLE BY CUSTOMER UNDER THE APPLICABLE SERVICE DESCRIPTION DURING THE TWELVE (12) MONTH PERIOD PRECEDING THE CLAIM.
**8 MISCELLANEOUS**
**8.1 Export Control**. Customer hereby certifies that Customer will comply with all current applicable export control laws applicable to Axiom Confidential Information, Customer’s use of the Service, and Customer Data. Customer agrees to defend, indemnify and hold Axiom harmless from any liability for Customer’s violation of any applicable export control laws.
**8.2 Compliance with Laws.** Customer shall comply with all applicable laws and regulations in its use of the Service and with respect to Customer Data, including without limitation the unlawful gathering or collecting, or assisting in the gathering or collecting of information in violation of any privacy laws or regulations. Customer shall, at its own expense, defend, indemnify and hold harmless Axiom from and against any and all claims, losses, liabilities, damages, judgments, government or federal sanctions, costs and expenses (including attorneys’ fees) incurred by Axiom arising from any claim or assertion by any third party of violation of privacy laws or regulations by Customer or any of its agents, officers, directors or employees.
**8.3 Assignment**. Neither party may transfer and assign its rights and obligations under this Agreement without the prior written consent of the other party. Notwithstanding the foregoing, Axiom may transfer and assign its rights under this Agreement without consent from the other party in connection with a change in control, acquisition or sale of all or substantially all of its assets.
**8.4 Force Majeure**. Neither party shall be responsible for failure or delay in performance by events out of their reasonable control, including but not limited to, acts of God, Internet outage, terrorism, war, fires, earthquakes and other disasters (each a “**Force Majeure**”). Notwithstanding the foregoing: (i) Customer shall be liable for payment obligations for Service rendered; and (ii) if a Force Majeure continues for more than thirty (30) days, either party may to terminate this agreement upon written notice to the other party.
**8.5 Notice**. All notices between the parties shall be in writing and shall be deemed to have been given if personally delivered or sent by registered or certified mail (return receipt), or by recognized courier service.
**8.6 No Agency**. Both parties agree that no agency, partnership, joint venture, or employment is created as a result of this Agreement. Customer does not have any authority of any kind to bind Axiom.
**8.7 Governing Law**. This Agreement and all matters relating to this Agreement shall be construed in accordance with and controlled by the laws of the State of California, without reference to its conflict of law principles. The parties agree to submit to the non-exclusive jurisdiction and venue of the courts located in Santa Clara, California and hereby waive any objections to the jurisdiction and venue of such courts.
**8.8 Entire Agreement**. This Agreement is the complete and exclusive statement of the mutual understanding of the parties and supersedes and cancels all previous written and oral agreements, communications, and other understandings relating to the subject matter of this Agreement, and all waivers and modifications must be in a writing signed by both parties, except as otherwise provided herein. Any term or provision of this Agreement held to be illegal or unenforceable shall be, to the fullest extent possible, interpreted so as to be construed as valid, but in any event the validity or enforceability of the remainder hereof shall not be affected. In the event of a conflict between this Agreement, the Service Description, or Supplemental Terms, the terms of this Agreement shall control.
---
# Terms of use
Source: https://axiom.co/docs/legal/terms-of-use
{/* vale off */}
These Terms of Use constitute a legally binding agreement made between you, whether personally or on behalf of an entity ("**you**") and Axiom, Inc., its affiliates or agents ("**Company**", "**we**", or "**our"**) concerning your access to and use of the axiom.co website as well as any other media form, media channel, mobile website or mobile application related, linked, or otherwise connected thereto (collectively, the "**Site**"). In the event that you are registering for a user account, evaluating our hosted / platform as a service-based service or buying paid-for services from Company, the Terms of Service [https://axiom.co/docs/legal/terms-of-service](/legal/terms-of-service) apply and shall govern your use of such services. We are registered in Delaware, United States and have our registered office at Axiom, Inc. 1390 Market Street, Suite 200, San Francisco, CA 94102. You agree that by accessing the Site, you have read, understood, and agreed to be bound by all of these Terms of Use. IF YOU DO NOT AGREE WITH ALL OF THESE TERMS OF USE, THEN YOU ARE EXPRESSLY PROHIBITED FROM USING THE SITE AND YOU MUST DISCONTINUE USE IMMEDIATELY.\
\
We reserve the right, in our sole discretion, to make changes or modifications to these Terms of Use from time to time. We will alert you about any changes by updating the “Last updated” date of these Terms of Use, and you waive any right to receive specific notice of each such change. Please ensure that you check the applicable Terms every time you use our Site so that you understand which Terms apply. You will be subject to, and will be deemed to have been made aware of and to have accepted, the changes in any revised Terms of Use by your continued use of the Site after the date such revised Terms of Use are posted.\
\
The information provided on the Site is not intended for distribution to or use by any person or entity in any jurisdiction or country where such distribution or use would be contrary to law or regulation or which would subject us to any registration requirement within such jurisdiction or country. Accordingly, those persons who choose to access the Site from other locations do so on their own initiative and are solely responsible for compliance with local laws, if and to the extent local laws are applicable.\
\
The Site is not tailored to comply with industry-specific regulations (Health Insurance Portability and Accountability Act (HIPAA), Federal Information Security Management Act (FISMA), etc.), so if your interactions would be subjected to such laws, you may not use this Site. You may not use the Site in a way that would violate the Gramm-Leach-Bliley Act (GLBA).\
\
The Site is intended for users who are at least 18 years old. Persons under the age of 18 are not permitted to use or register for the Site.
Unless otherwise expressly indicated herein, the Site is our proprietary property and all source code, databases, functionality, software, website designs, audio, video, text, photographs, and graphics on the Site (collectively, the “**Content**”) and the trademarks, service marks, and logos contained therein (the “**Marks**”) are owned or controlled by us or licensed to us, and are protected by copyright and trademark laws and various other intellectual property rights and unfair competition laws of the United States, international copyright laws, and international conventions. The Content and the Marks are provided on the Site “AS IS” for your information and personal use only. Except as expressly provided in these Terms of Use, no part of the Site and no Content or Marks may be copied, reproduced, aggregated, republished, uploaded, posted, publicly displayed, encoded, translated, transmitted, distributed, sold, licensed, or otherwise exploited for any commercial purpose whatsoever, without our express prior written permission.\
\
Provided that you are eligible to use the Site, you are granted a limited license to access and use the Site and to download or print a copy of any portion of the Content to which you have properly gained access solely for your personal, non-commercial use. We reserve all rights not expressly granted to you in and to the Site, the Content and the Marks.\
\
By using the Site, you represent and warrant that: (1) you have the legal capacity and you agree to comply with these Terms of Use; (2) you are not a minor in the jurisdiction in which you reside; (3) you will not use the Site for any illegal or unauthorized purpose; and (4) your use of the Site will not violate any applicable law or regulation.
If you provide any information that is untrue, inaccurate, not current, or incomplete, or otherwise violate these Terms of Use, we have the right to suspend or terminate your account and refuse any and all current or future use of the Site (or any portion thereof).\
\
You may not access or use the Site for any purpose other than that for which we make the Site available.\
\
**As a user of the Site, you agree not to:**
1. Systematically retrieve data or other content from the Site to create or compile, directly or indirectly, a collection, compilation, database, or directory without written permission from us.
2. Trick, defraud, or mislead us and other users, especially in any attempt to learn sensitive account information such as user passwords.
3. Circumvent, disable, or otherwise interfere with security-related features of the Site, including features that prevent or restrict the use or copying of any Content or enforce limitations on the use of the Site and/or the Content contained therein.
4. Disparage, tarnish, or otherwise harm, in our opinion, us and/or the Site.
5. Use any information obtained from the Site in order to harass, abuse, or harm another person.
6. Make improper use of our support services or submit false reports of abuse or misconduct.
7. Use the Site in a manner inconsistent with any applicable laws or regulations.
8. Engage in unauthorized framing of or linking to the Site.
9. Upload or transmit (or attempt to upload or to transmit) viruses, Trojan horses, or other material, including excessive use of capital letters and spamming (continuous posting of repetitive text), that interferes with any party’s uninterrupted use and enjoyment of the Site or modifies, impairs, disrupts, alters, or interferes with the use, features, functions, operation, or maintenance of the Site.
10. Engage in any automated use of the system, such as using scripts to send comments or messages, or using any data mining, robots, or similar data gathering and extraction tools.
11. Delete the copyright or other proprietary rights notice from any Content (including Third Party Content as defined below).
12. Attempt to impersonate another user or person or use the username of another user.
13. Upload or transmit (or attempt to upload or to transmit) any material that acts as a passive or active information collection or transmission mechanism, including without limitation, clear graphics interchange formats (“gifs”), 1×1 pixels, web bugs, cookies, or other similar devices (sometimes referred to as “spyware” or “passive collection mechanisms” or “pcms”).
14. Interfere with, disrupt, or create an undue burden on the Site or the networks or services connected to the Site.
15. Harass, annoy, intimidate, or threaten any of our employees or agents engaged in providing any portion of the Site to you.
16. Attempt to bypass any measures of the Site designed to prevent or restrict access to the Site, or any portion of the Site.
17. Copy or adapt the Site’s software, including but not limited to Flash, PHP, HTML, JavaScript, or other code.
18. Except as permitted by applicable law, decipher, decompile, disassemble, or reverse engineer any of the software comprising or in any way making up a part of the Site.
19. Except as may be the result of standard search engine or Internet browser usage, use, launch, develop, or distribute any automated system, including without limitation, any spider, robot, cheat utility, scraper, or offline reader that accesses the Site, or using or launching any unauthorized script or other software.
20. Use a buying agent or purchasing agent to make purchases on the Site.
21. Make any unauthorized use of the Site, including collecting usernames and/or email addresses of users by electronic or other means for the purpose of sending unsolicited email, or creating user accounts by automated means or under false pretenses.
22. Use the Site as part of any effort to compete with us or otherwise use the Site and/or the Content for any revenue-generating endeavor or commercial enterprise.
23. Use the Site to advertise or offer to sell goods and services, except as may be authorized by us.
Any use of the Site in violation of the foregoing violates these Terms of Use and may result in, among other things, termination or suspension of your rights to use the Site.\
\
You agree that we may access, store, process, and use any information and personal data that you provide following the terms of our Privacy Policy and your choices related thereto (including settings).\
\
By submitting suggestions or other feedback regarding the Site, you agree that we can use and share such feedback for any purpose without compensation to you.\
\
We do not assert any ownership over your Contributions, except to the extent that content included within such Contributions is considered a Submission as defined below. We are not liable for any statements or representations in your Contributions provided by you in any area on the Site. You are solely responsible for your Contributions to the Site and you expressly agree to exonerate us from any and all responsibility and to refrain from any legal action against us regarding your Contributions.\
\
You acknowledge and agree that any questions, comments, suggestions, ideas, feedback, or other information regarding the Site ("**Submissions**") provided by you to us are non-confidential. As between us, Company shall own exclusive rights, including all intellectual property rights, and shall be entitled to the unrestricted use, modification and dissemination of these Submissions for any lawful purpose, commercial or otherwise, without acknowledgment or compensation to you. Further, and to the extent that any of Your Submissions may be subject to copyright protection, You hereby grant to Company and to any recipients of software distributed by Company, a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and any derivative works therefrom. You hereby waive all moral rights to any such Submissions, and you hereby warrant that any such Submissions are original with you or that you have the right to submit such Submissions. You agree there shall be no recourse against us for any alleged or actual infringement or misappropriation of any proprietary right in your Submissions.\
\
The Site may contain (or you may be sent via the Site) links to other websites ("**Third-Party Websites**") as well as articles, photographs, text, graphics, pictures, designs, music, sound, video, information, applications, software, and other content or items belonging to or originating from third parties ("**Third-Party Content**"). Such Third-Party Websites and Third-Party Content are not investigated, monitored, or checked for accuracy, appropriateness, or completeness by us, and we are not responsible for any Third-Party Websites accessed through the Site or any Third-Party Content posted on, available through, or installed from the Site, nor any content, accuracy, offensiveness, opinions, reliability, privacy practices, or other policies of or contained in the Third-Party Websites or the Third-Party Content. Inclusion of, linking to, or permitting the use or installation of any Third-Party Websites or any Third-Party Content does not imply approval or endorsement by us. If you decide to leave the Site and access the Third-Party Websites or to use or install any Third-Party Content, you do so at your own risk, and these Terms of Use no longer govern your use of such Third Party Websites. You should review the applicable terms and policies, including privacy and data gathering practices, of any website to which you navigate from the Site or relating to any applications you use or install from the Site. Any purchases you make through Third-Party Websites will be through other websites and from other companies, and we take no responsibility whatsoever in relation to such purchases which are exclusively between you and the applicable third party. You agree and acknowledge that we do not endorse the products or services offered on Third-Party Websites and you shall hold us harmless from any harm caused by your purchase of such products or services. Additionally, you shall hold us harmless from any losses sustained by you or harm caused to you relating to or resulting in any way from any Third-Party Content or any contact with Third-Party Websites\
\
We reserve the right, but not the obligation, to: (1) monitor the Site for violations of these Terms of Use; (2) take appropriate legal action against anyone who, in our sole discretion, violates the law or these Terms of Use, including without limitation, reporting such user to law enforcement authorities; (3) in our sole discretion and without limitation, refuse, restrict access to, limit the availability of, or disable (to the extent technologically feasible) any of your Contributions or any portion thereof; (4) in our sole discretion and without limitation, notice, or liability, to remove from the Site or otherwise disable all files and content that are excessive in size or are in any way burdensome to our systems; and (5) otherwise manage the Site in a manner designed to protect our rights and property and to facilitate the proper functioning of the Site.\
\
By using the Site, you agree to be bound by our Privacy Policy posted on the Site, which is incorporated into these Terms of Use. Please be advised the Site is hosted in the United States. If you access the Site from any other region of the world with laws or other requirements governing personal data collection, use, or disclosure that differ from applicable laws in the United States, then through your continued use of the Site, you are transferring your data to the United States, and you agree to have your data transferred to and processed in United States.\
\
These Terms of Use shall remain in full force and effect while you use the Site. WITHOUT LIMITING ANY OTHER PROVISION OF THESE TERMS OF USE, WE RESERVE THE RIGHT TO, IN OUR SOLE DISCRETION AND WITHOUT NOTICE OR LIABILITY, DENY ACCESS TO AND USE OF THE SITE (INCLUDING BLOCKING CERTAIN IP ADDRESSES), TO ANY PERSON FOR ANY REASON OR FOR NO REASON, INCLUDING WITHOUT LIMITATION FOR BREACH OF ANY REPRESENTATION, WARRANTY, OR COVENANT CONTAINED IN THESE TERMS OF USE OR OF ANY APPLICABLE LAW OR REGULATION. WE MAY TERMINATE YOUR USE OR PARTICIPATION IN THE SITE OR DELETE ANY CONTENT OR INFORMATION THAT YOU POSTED AT ANY TIME, WITHOUT WARNING, IN OUR SOLE DISCRETION.\
\
If we terminate or suspend your account for any reason, you are prohibited from registering and creating a new account under your name, a fake or borrowed name, or the name of any third party, even if you may be acting on behalf of the third party. In addition to terminating or suspending your account, we reserve the right to take appropriate legal action, including without limitation pursuing civil, criminal, and injunctive redress.\
\
We reserve the right to change, modify, or remove the contents of the Site at any time or for any reason at our sole discretion without notice. However, we have no obligation to update any information on our Site. We also reserve the right to modify or discontinue all or part of the Site without notice at any time. We will not be liable to you or any third party for any modification, price change, suspension, or discontinuance of the Site.\
\
We cannot guarantee the Site will be available at all times. We may experience hardware, software, or other problems or need to perform maintenance related to the Site, resulting in interruptions, delays, or errors. We reserve the right to change, revise, update, suspend, discontinue, or otherwise modify the Site at any time or for any reason without notice to you. You agree that we have no liability whatsoever for any loss, damage, or inconvenience caused by your inability to access or use the Site during any downtime or discontinuance of the Site. Nothing in these Terms of Use will be construed to obligate us to maintain and support the Site or to supply any corrections, updates, or releases in connection therewith.\
\
These Terms of Use and your use of the Site are governed by and construed in accordance with the laws of the State of California, without regard to its conflict of law principles.\
\
**Disclaimer and Limitation of Our Liability**
There may be information on the Site that contains typographical errors, inaccuracies, or omissions, including descriptions, pricing, availability, and various other information. We reserve the right to correct any errors, inaccuracies, or omissions and to change or update the information on the Site at any time, without prior notice.\
\
THE SITE IS PROVIDED ON AN AS-IS AND AS-AVAILABLE BASIS. YOU AGREE THAT YOUR USE OF THE SITE AND OUR SERVICES WILL BE AT YOUR SOLE RISK. TO THE FULLEST EXTENT PERMITTED BY LAW, WE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, IN CONNECTION WITH THE SITE AND YOUR USE THEREOF, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. WE MAKE NO WARRANTIES OR REPRESENTATIONS ABOUT THE ACCURACY OR COMPLETENESS OF THE SITE’S CONTENT OR THE CONTENT OF ANY WEBSITES LINKED TO THE SITE AND WE WILL ASSUME NO LIABILITY OR RESPONSIBILITY FOR ANY (1) ERRORS, MISTAKES, OR INACCURACIES OF CONTENT AND MATERIALS, (2) PERSONAL INJURY OR PROPERTY DAMAGE, OF ANY NATURE WHATSOEVER, RESULTING FROM YOUR ACCESS TO AND USE OF THE SITE, (3) ANY UNAUTHORIZED ACCESS TO OR USE OF OUR SECURE SERVERS AND/OR ANY AND ALL PERSONAL INFORMATION AND/OR FINANCIAL INFORMATION STORED THEREIN, (4) ANY INTERRUPTION OR CESSATION OF TRANSMISSION TO OR FROM THE SITE, (5) ANY BUGS, VIRUSES, TROJAN HORSES, OR THE LIKE WHICH MAY BE TRANSMITTED TO OR THROUGH THE SITE BY ANY THIRD PARTY, AND/OR (6) ANY ERRORS OR OMISSIONS IN ANY CONTENT AND MATERIALS OR FOR ANY LOSS OR DAMAGE OF ANY KIND INCURRED AS A RESULT OF THE USE OF ANY CONTENT POSTED, TRANSMITTED, OR OTHERWISE MADE AVAILABLE VIA THE SITE. WE DO NOT WARRANT, ENDORSE, GUARANTEE, OR ASSUME RESPONSIBILITY FOR ANY PRODUCT OR SERVICE ADVERTISED OR OFFERED BY A THIRD PARTY THROUGH THE SITE, ANY HYPERLINKED WEBSITE, OR ANY WEBSITE OR MOBILE APPLICATION FEATURED IN ANY BANNER OR OTHER ADVERTISING, AND WE WILL NOT BE A PARTY TO OR IN ANY WAY BE RESPONSIBLE FOR MONITORING ANY TRANSACTION BETWEEN YOU AND ANY THIRD-PARTY PROVIDERS OF PRODUCTS OR SERVICES.\
\
IN NO EVENT WILL WE OR OUR DIRECTORS, EMPLOYEES, OR AGENTS BE LIABLE TO YOU OR ANY THIRD PARTY FOR ANY DIRECT, INDIRECT, CONSEQUENTIAL, EXEMPLARY, INCIDENTAL, SPECIAL, OR PUNITIVE DAMAGES, INCLUDING LOST PROFIT, LOST REVENUE, LOSS OF DATA, OR OTHER DAMAGES ARISING FROM YOUR USE OF THE SITE, EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. NOTWITHSTANDING ANYTHING TO THE CONTRARY CONTAINED HEREIN, OUR LIABILITY TO YOU FOR ANY CAUSE WHATSOEVER AND REGARDLESS OF THE FORM OF THE ACTION, WILL AT ALL TIMES BE LIMITED TO ONE THOUSAND US DOLLARS ($1,000.00). CERTAIN US STATE LAWS AND INTERNATIONAL LAWS DO NOT ALLOW LIMITATIONS ON IMPLIED WARRANTIES OR THE EXCLUSION OR LIMITATION OF CERTAIN DAMAGES. IF THESE LAWS APPLY TO YOU, SOME OR ALL OF THE ABOVE DISCLAIMERS OR LIMITATIONS MAY NOT APPLY TO YOU, AND YOU MAY HAVE ADDITIONAL RIGHTS.\
\
You agree to defend, indemnify, and hold us harmless from and against any loss, damage, liability, claim, or demand, including reasonable attorneys’ fees and expenses, made by any third party due to or arising out of: (1) use of the Site; (2) breach of these Terms of Use; (3) any breach of your representations and warranties set forth in these Terms of Use; (4) your violation of the rights of a third party; or (5) any overt harmful act toward any other user of the Site with whom you connected via the Site. Notwithstanding the foregoing, we reserve the right, at your expense, to assume the exclusive defense and control of any matter for which you are required to indemnify us, and you agree to cooperate, at your expense, with our defense of such claims. We will use reasonable efforts to notify you of any such claim, action, or proceeding which is subject to this indemnification upon becoming aware of it.\
\
We will maintain certain data that you transmit to the Site for the purpose of managing the performance of the Site, as well as data relating to your use of the Site. Although we perform regular routine backups of data, you are solely responsible for all data that you transmit or that relates to any activity you have undertaken using the Site. You agree that we shall have no liability to you for any loss or corruption of any such data, and you hereby waive any right of action against us arising from any such loss or corruption of such data.\
\
Visiting the Site, sending us emails, and completing online forms constitute electronic communications. You consent to receive electronic communications, and you agree that all agreements, notices, disclosures, and other communications we provide to you electronically, via email and on the Site, satisfy any legal requirement that such communication be in writing. YOU HEREBY AGREE TO THE USE OF ELECTRONIC SIGNATURES, CONTRACTS, ORDERS, AND OTHER RECORDS, AND TO ELECTRONIC DELIVERY OF NOTICES, POLICIES, AND RECORDS OF TRANSACTIONS INITIATED OR COMPLETED BY US OR VIA THE SITE. You hereby waive any rights or requirements under any statutes, regulations, rules, ordinances, or other laws in any jurisdiction which require an original signature or delivery or retention of non-electronic records, or to payments or the granting of credits by any means other than electronic means to the extent applicable.
If any complaint with us is not satisfactorily resolved, you can contact the Complaint Assistance Unit of the Division of Consumer Services of the California Department of Consumer Affairs in writing at 1625 North Market Blvd., Suite N 112, Sacramento, California 95834 or by telephone at (800) 952-5210 or (916) 445-1254.\
\
These Terms of Use and any policies or operating rules posted by us on the Site or in respect to the Site constitute the entire agreement and understanding between you and us. Our failure to exercise or enforce any right or provision of these Terms of Use shall not operate as a waiver of such right or provision. These Terms of Use operate to the fullest extent permissible by law. We may assign any or all of our rights and obligations to others at any time. If any provision or part of a provision of these Terms of Use is determined to be unlawful, void, or unenforceable, that provision or part of the provision is deemed severable from these Terms of Use and does not affect the validity and enforceability of any remaining provisions. There is no joint venture, partnership, employment or agency relationship created between you and us as a result of these Terms of Use or use of the Site. You agree that these Terms of Use will not be construed against us by virtue of having drafted them. You hereby waive any and all defenses you may have based on the electronic form of these Terms of Use and the lack of signing by the parties hereto to execute these Terms of Use.\
\
In order to resolve a complaint regarding the Site or to receive further information regarding use of the Site, please contact us at:
Axiom, Inc.\
1390 Market Street, Suite 200, San Francisco, CA 94102\
[legal@axiom.co](mailto:legal@axiom.co)
---
# Axiom Go adapter for Apex
Source: https://axiom.co/docs/guides/apex
Use the adapter of the Axiom Go SDK to send logs generated by the [apex/log](https://github.com/apex/log) library to Axiom.
The Axiom Go SDK is an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/axiom-go).
## Set up SDK [#set-up-sdk]
1. Install the Axiom Go SDK and configure your environment as explained in [Send data from Go app to Axiom](/guides/go).
2. In your Go app, import the `apex` package. It’s imported as an `adapter` so that it doesn’t conflict with the `apex/log` package.
```go
import adapter "github.com/axiomhq/axiom-go/adapters/apex"
```
Alternatively, configure the adapter using [options](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/apex#Option) passed to the [New](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/apex#New) function:
```go
handler, err := adapter.New(
adapter.SetDataset("DATASET_NAME"),
)
```
## Configure client [#configure-client]
To configure the underlying client manually, choose one of the following:
* Use [SetClient](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/apex#SetClient) to pass in the client you have previously created with [Send data from Go app to Axiom](/guides/go).
* Use [SetClientOptions](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/apex#SetClientOptions) to pass [client options](https://pkg.go.dev/github.com/axiomhq/axiom-go/axiom#Option) to the adapter.
```go
import (
"github.com/axiomhq/axiom-go/axiom"
adapter "github.com/axiomhq/axiom-go/adapters/apex"
)
// ...
handler, err := adapter.New(
adapter.SetClientOptions(
axiom.SetPersonalTokenConfig("AXIOM_TOKEN"),
),
)
```
### Configure region [#configure-region]
By default, the adapter sends data to `api.axiom.co`. To target a specific edge region, pass `axiom.SetEdge` through `SetClientOptions` with the edge domain that matches the region your dataset lives in:
```go EU Central 1
handler, err := adapter.New(
adapter.SetClientOptions(
axiom.SetAPITokenConfig("xaat-your-api-token"),
axiom.SetEdge("eu-central-1.aws.edge.axiom.co"),
),
)
```
```go US East 1
handler, err := adapter.New(
adapter.SetClientOptions(
axiom.SetAPITokenConfig("xaat-your-api-token"),
axiom.SetEdge("us-east-1.aws.edge.axiom.co"),
),
)
```
The following edge domains are available:
| Edge deployment | Base domain for ingest and query |
| ------------------ | -------------------------------- |
| US East 1 (AWS) | `us-east-1.aws.edge.axiom.co` |
| EU Central 1 (AWS) | `eu-central-1.aws.edge.axiom.co` |
Edge endpoints require an API token (`xaat-`), not a personal token (`xapt-`). Use `axiom.SetAPITokenConfig` when targeting an edge region. You can also set `AXIOM_EDGE` or `AXIOM_EDGE_URL` in the environment instead of `axiom.SetEdge`. For a full edge URL such as a proxy, use `axiom.SetEdgeURL`, which takes precedence over `axiom.SetEdge`.
The adapter uses a buffer to batch events before sending them to Axiom. Flush this buffer explicitly by calling [Close](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/apex#Handler.Close). For more information, see the [example in GitHub](https://github.com/axiomhq/axiom-go/blob/main/examples/apex/main.go).
## Reference [#reference]
For a full reference of the adapter’s functions, see the [Go Packages page](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/apex).
---
# Send data from Flutter app
Source: https://axiom.co/docs/guides/flutter
This page explains how to send structured logs from Flutter apps to Axiom using a custom logging library built with the [Dio HTTP client](https://pub.dev/packages/dio).
* [Install Flutter SDK](https://docs.flutter.dev/install) version 3.0.0 or higher.
## Install required dependencies [#install-required-dependencies]
To install the required Flutter dependencies, add these lines to your `pubspec.yaml` file:
```yaml
dependencies:
dio: ^5.4.0
intl: ^0.19.0
```
Then run the following code in your terminal to install dependencies:
```bash
flutter pub get
```
* **dio**: A powerful HTTP client for Dart that handles network requests to send logs to Axiom.
* **intl**: Provides internationalization and date/time formatting utilities for creating properly formatted timestamps.
## Create axiom\_logger.dart file [#create-axiom_loggerdart-file]
Create a `lib/axiom_logger.dart` file with the following content. This file defines the logger configuration, log levels, and the main logging functionality that sends structured logs to Axiom.
The logger implementation below includes the following key features:
* **Log Levels**: Five severity levels (debug, info, warning, error, critical) for categorizing log entries.
* **Batching**: Logs are buffered and sent in batches to reduce network overhead and improve performance.
* **Immediate Sending**: Critical logs are sent immediately to ensure important events are captured right away.
* **Metadata Support**: Attach custom metadata to logs for richer context and easier filtering.
* **Automatic Timestamps**: Logs are automatically timestamped in ISO 8601 format.
```dart lib/axiom_logger.dart expandable
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:intl/intl.dart';
/// Log level enumeration
enum LogLevel {
debug,
info,
warning,
error,
critical,
}
/// Extension to convert LogLevel to string
extension LogLevelExtension on LogLevel {
String get name {
switch (this) {
case LogLevel.debug:
return 'DEBUG';
case LogLevel.info:
return 'INFO';
case LogLevel.warning:
return 'WARNING';
case LogLevel.error:
return 'ERROR';
case LogLevel.critical:
return 'CRITICAL';
}
}
}
/// Configuration for the Axiom Logger
class AxiomLoggerConfig {
final String domain;
final String dataset;
final String apiToken;
final Duration timeout;
final bool enableDebugLogs;
AxiomLoggerConfig({
required this.domain,
required this.dataset,
required this.apiToken,
this.timeout = const Duration(seconds: 10),
this.enableDebugLogs = false,
});
String get ingestUrl => '$domain/v1/ingest/$dataset';
}
/// Main Axiom Logger class
class AxiomLogger {
final AxiomLoggerConfig config;
late final Dio _dio;
final List> _logBuffer = [];
final int _batchSize;
final Duration _flushInterval;
bool _isInitialized = false;
AxiomLogger({
required this.config,
int batchSize = 10,
Duration flushInterval = const Duration(seconds: 5),
}) : _batchSize = batchSize,
_flushInterval = flushInterval {
_initializeDio();
}
void _initializeDio() {
_dio = Dio(
BaseOptions(
baseUrl: config.domain,
connectTimeout: config.timeout,
receiveTimeout: config.timeout,
headers: {
'Authorization': 'Bearer ${config.apiToken}',
'Content-Type': 'application/json',
},
),
);
if (config.enableDebugLogs) {
_dio.interceptors.add(
LogInterceptor(
requestBody: true,
responseBody: true,
error: true,
requestHeader: true,
responseHeader: false,
),
);
}
_isInitialized = true;
}
/// Log a message with specified level
Future log(
LogLevel level,
String message, {
Map? metadata,
bool sendImmediately = false,
}) async {
if (!_isInitialized) {
print('AxiomLogger: Logger not initialized');
return;
}
final logEntry = _createLogEntry(level, message, metadata);
if (sendImmediately) {
await _sendLogs([logEntry]);
} else {
_logBuffer.add(logEntry);
if (_logBuffer.length >= _batchSize) {
await flush();
}
}
}
/// Create a structured log entry
Map _createLogEntry(
LogLevel level,
String message,
Map? metadata,
) {
final now = DateTime.now().toUtc();
final timestamp = DateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").format(now);
return {
'_time': timestamp,
'level': level.name,
'message': message,
if (metadata != null) ...metadata,
};
}
/// Flush all buffered logs to Axiom
Future flush() async {
if (_logBuffer.isEmpty) {
return true;
}
final logsToSend = List>.from(_logBuffer);
_logBuffer.clear();
return await _sendLogs(logsToSend);
}
/// Send logs to Axiom
Future _sendLogs(List> logs) async {
try {
final response = await _dio.post(
config.ingestUrl,
data: jsonEncode(logs),
);
if (response.statusCode == 200 || response.statusCode == 204) {
if (config.enableDebugLogs) {
print('AxiomLogger: Successfully sent ${logs.length} log(s) to Axiom');
}
return true;
} else {
print('AxiomLogger: Failed to send logs. Status: ${response.statusCode}');
return false;
}
} catch (e) {
print('AxiomLogger: Error sending logs to Axiom: $e');
return false;
}
}
/// Convenience methods for different log levels
Future debug(String message, {Map? metadata}) async {
await log(LogLevel.debug, message, metadata: metadata);
}
Future info(String message, {Map? metadata}) async {
await log(LogLevel.info, message, metadata: metadata);
}
Future warning(String message, {Map? metadata}) async {
await log(LogLevel.warning, message, metadata: metadata);
}
Future error(String message, {Map? metadata}) async {
await log(LogLevel.error, message, metadata: metadata);
}
Future critical(String message, {Map? metadata}) async {
await log(LogLevel.critical, message, metadata: metadata, sendImmediately: true);
}
/// Dispose resources
Future dispose() async {
await flush();
_dio.close();
}
}
```
## Create main.dart file [#create-maindart-file]
Create an `example/main.dart` file with the following content. This file demonstrates how to use the Axiom Logger with different log levels and metadata.
```dart example/main.dart expandable
import 'package:flutter_logging/axiom_logger.dart';
/// Example usage of the Axiom Logger
Future main() async {
print('=== Flutter Axiom Logger Test ===\n');
// Initialize the logger with your Axiom configuration
final config = AxiomLoggerConfig(
domain: 'AXIOM_DOMAIN',
dataset: 'DATASET_NAME',
apiToken: 'API_TOKEN',
enableDebugLogs: true, // Enable to see HTTP requests/responses
);
final logger = AxiomLogger(
config: config,
batchSize: 5, // Send logs in batches of 5
flushInterval: Duration(seconds: 3),
);
print('Logger initialized. Sending test logs to Axiom...\n');
try {
// Test 1: Simple info log
print('Test 1: Sending INFO log...');
await logger.info(
'Application started successfully',
metadata: {
'app_name': 'Flutter Logging Demo',
'version': '1.0.0',
'environment': 'development',
},
);
// Test 2: Debug log with metadata
print('Test 2: Sending DEBUG log with metadata...');
await logger.debug(
'User authentication flow initiated',
metadata: {
'user_id': 'user_12345',
'session_id': 'session_abc123',
'ip_address': '192.168.1.100',
},
);
// Test 3: Warning log
print('Test 3: Sending WARNING log...');
await logger.warning(
'High memory usage detected',
metadata: {
'memory_mb': 512,
'threshold_mb': 400,
'process': 'main_app',
},
);
// Test 4: Error log
print('Test 4: Sending ERROR log...');
await logger.error(
'Failed to connect to database',
metadata: {
'error_code': 'DB_CONN_001',
'database': 'production_db',
'retry_count': 3,
'last_error': 'Connection timeout after 30s',
},
);
// Test 5: Multiple logs to test batching
print('Test 5: Sending multiple logs to test batching...');
for (int i = 1; i <= 3; i++) {
await logger.info(
'Processing batch item $i',
metadata: {
'batch_id': 'batch_001',
'item_number': i,
'status': 'processing',
},
);
}
// Test 6: Critical log (sends immediately)
print('Test 6: Sending CRITICAL log (immediate send)...');
await logger.critical(
'System failure: Out of memory',
metadata: {
'available_memory_mb': 10,
'required_memory_mb': 500,
'action_taken': 'emergency_shutdown',
},
);
// Flush any remaining buffered logs
print('\nFlushing remaining logs...');
await logger.flush();
print('\n✅ All logs sent successfully!');
print('\nYou can now check your Axiom dashboard at:');
print('https://app.axiom.co/DATASET_NAME');
} catch (e) {
print('❌ Error during logging: $e');
} finally {
// Clean up resources
await logger.dispose();
print('\nLogger disposed. Test complete.');
}
}
```
## Run the app and observe logs in Axiom [#run-the-app-and-observe-logs-in-axiom]
1. Run the following code in your terminal to run the Flutter example:
```bash
dart run example/main.dart
```
The app sends logs with different severity levels to Axiom, demonstrating batching, immediate sending for critical logs, and the use of custom metadata.
2. In Axiom, go to the Stream tab, and then click your dataset. This page displays the logs sent to Axiom and enables you to monitor and analyze your app's behavior and performance.
## Send data from an existing Flutter project [#send-data-from-an-existing-flutter-project]
### Basic integration [#basic-integration]
To add Axiom logging to your existing Flutter app, follow these steps:
1. Add the Axiom Logger to your project by copying the `axiom_logger.dart` file to your `lib` directory.
2. Initialize the logger early in your app's lifecycle, typically in your `main()` function.
```dart
import 'package:your_app/axiom_logger.dart';
void main() async {
// Initialize logger
final config = AxiomLoggerConfig(
domain: 'https://your-axiom-domain.com',
dataset: 'your-dataset',
apiToken: 'your-api-token',
enableDebugLogs: false, // Set to true for development
);
final logger = AxiomLogger(config: config);
// Log app startup
await logger.info('App started', metadata: {
'version': '1.0.0',
'platform': 'Flutter',
});
runApp(MyApp(logger: logger));
}
```
3. Use the logger throughout your app to capture important events, errors, and debug information. For example:
```dart
// Log user actions
await logger.info('User logged in', metadata: {
'user_id': userId,
'login_method': 'email',
});
// Log errors with context
try {
await someOperation();
} catch (e, stackTrace) {
await logger.error('Operation failed', metadata: {
'error': e.toString(),
'stack_trace': stackTrace.toString(),
'operation': 'someOperation',
});
}
```
### Integration with Flutter error handling [#integration-with-flutter-error-handling]
Capture Flutter framework errors and send them to Axiom:
```dart
void main() async {
final config = AxiomLoggerConfig(
domain: 'https://your-axiom-domain.com',
dataset: 'your-dataset',
apiToken: 'your-api-token',
);
final logger = AxiomLogger(config: config);
// Capture Flutter framework errors
FlutterError.onError = (FlutterErrorDetails details) async {
await logger.error(
'Flutter framework error',
metadata: {
'exception': details.exception.toString(),
'stack_trace': details.stack.toString(),
'library': details.library ?? 'unknown',
'context': details.context?.toString(),
},
);
};
// Capture async errors
PlatformDispatcher.instance.onError = (error, stack) {
logger.error(
'Uncaught async error',
metadata: {
'error': error.toString(),
'stack_trace': stack.toString(),
},
);
return true;
};
runApp(MyApp(logger: logger));
}
```
### Logging user interactions [#logging-user-interactions]
Track user behavior and navigation patterns:
```dart
class MyHomePage extends StatefulWidget {
final AxiomLogger logger;
const MyHomePage({required this.logger});
@override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
@override
void initState() {
super.initState();
widget.logger.info('User navigated to home page', metadata: {
'timestamp': DateTime.now().toIso8601String(),
'screen': 'home',
});
}
Future _handleButtonPress() async {
await widget.logger.debug('Button pressed', metadata: {
'button': 'submit',
'screen': 'home',
});
// Your button logic here
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: ElevatedButton(
onPressed: _handleButtonPress,
child: Text('Submit'),
),
);
}
}
```
### Performance monitoring [#performance-monitoring]
Log performance metrics to identify bottlenecks:
```dart
Future performExpensiveOperation(AxiomLogger logger) async {
final stopwatch = Stopwatch()..start();
try {
await someExpensiveTask();
stopwatch.stop();
await logger.info('Operation completed', metadata: {
'operation': 'expensive_task',
'duration_ms': stopwatch.elapsedMilliseconds,
'status': 'success',
});
} catch (e) {
stopwatch.stop();
await logger.error('Operation failed', metadata: {
'operation': 'expensive_task',
'duration_ms': stopwatch.elapsedMilliseconds,
'status': 'failed',
'error': e.toString(),
});
}
}
```
### Best practices [#best-practices]
* **Use appropriate log levels**: Reserve `critical` for system failures, `error` for recoverable errors, `warning` for potential issues, `info` for important events, and `debug` for development details.
* **Add contextual metadata**: Include relevant information like user IDs, session IDs, device info, and operation context to make logs more useful.
* **Dispose properly**: Always call `logger.dispose()` when your app closes to ensure buffered logs are sent.
* **Handle errors gracefully**: Wrap logging calls in try-catch blocks to prevent logging failures from crashing your app.
* **Use batching wisely**: Adjust `batchSize` and `flushInterval` based on your app's logging volume and network conditions.
## Reference [#reference]
### List of log fields [#list-of-log-fields]
| Field Category | Field Name | Description |
| ----------------- | ------------- | ----------------------------------------------------------- |
| Core Fields | | |
| | \_time | ISO 8601 formatted timestamp when the log event occurred. |
| | level | Log severity level (DEBUG, INFO, WARNING, ERROR, CRITICAL). |
| | message | The main log message describing the event. |
| Custom Metadata | | |
| | app\_name | Name of the app generating the log. |
| | version | Application version number. |
| | environment | Deployment environment (development, staging, production). |
| | user\_id | Unique identifier for the user associated with the event. |
| | session\_id | Unique identifier for the user session. |
| | error\_code | Application-specific error code. |
| | duration\_ms | Duration of an operation in milliseconds. |
| | status | Status of an operation (success, failed, processing). |
| Device & Platform | | |
| | platform | Operating system or platform (iOS, Android, Web, Desktop). |
| | device\_model | Specific device model generating the log. |
| | os\_version | Operating system version. |
| Custom Fields | | |
| | \* | Any custom fields added via the metadata parameter. |
### Logger configuration options [#logger-configuration-options]
#### AxiomLoggerConfig [#axiomloggerconfig]
The `AxiomLoggerConfig` class configures how the logger connects to Axiom:
* **domain**: The base URL of your Axiom deployment (for example, `https://us-east-1.aws.edge.axiom.co`).
* **dataset**: The name of the Axiom dataset where logs are sent.
* **apiToken**: Your Axiom API token for authentication.
* **timeout**: Maximum time to wait for HTTP requests (default: 10 seconds).
* **enableDebugLogs**: Enable verbose HTTP logging for debugging (default: false).
#### AxiomLogger [#axiomlogger]
The `AxiomLogger` class manages log creation and transmission:
* **batchSize**: Number of logs to buffer before automatically flushing to Axiom (default: 10).
* **flushInterval**: Time interval for automatic flushing (default: 5 seconds). Note: Automatic interval-based flushing isn't yet implemented but can be added.
### Log levels [#log-levels]
The logger supports five log levels in increasing order of severity:
1. **DEBUG**: Detailed information for diagnosing problems, typically used during development.
2. **INFO**: Confirmation that things are working as expected, such as successful operations.
3. **WARNING**: Indication that something unexpected happened, but the app continues to work normally.
4. **ERROR**: A more serious problem that prevented a specific operation from completing.
5. **CRITICAL**: A severe error that may cause the app to fail or require immediate attention. Critical logs are sent immediately, bypassing the buffer.
### Key methods [#key-methods]
#### Logging methods [#logging-methods]
* `log(level, message, {metadata, sendImmediately})`: Core logging method that accepts any log level.
* `debug(message, {metadata})`: Convenience method for DEBUG level logs.
* `info(message, {metadata})`: Convenience method for INFO level logs.
* `warning(message, {metadata})`: Convenience method for WARNING level logs.
* `error(message, {metadata})`: Convenience method for ERROR level logs.
* `critical(message, {metadata})`: Convenience method for CRITICAL level logs (sends immediately).
#### Management methods [#management-methods]
* `flush()`: Manually send all buffered logs to Axiom. Returns a boolean indicating success.
* `dispose()`: Clean up resources, flush remaining logs, and close HTTP connections. Call this when shutting down the logger.
### Dependencies [#dependencies]
#### dio [#dio]
The [Dio package](https://pub.dev/packages/dio) is a powerful HTTP client for Dart that provides:
* Request and response interceptors for debugging and modifying HTTP traffic.
* Support for timeouts, custom headers, and authentication.
* Error handling and retry mechanisms.
* FormData, file uploading, and downloading capabilities.
In this logger, Dio handles all HTTP communication with Axiom's ingest API, including authentication via Bearer tokens and proper JSON serialization of log batches.
#### intl [#intl]
The [intl package](https://pub.dev/packages/intl) provides internationalization and localization support, including:
* Date and time formatting using standard patterns.
* Number formatting for different locales.
* Message translation support.
In this logger, intl is used to format timestamps in ISO 8601 format (`yyyy-MM-dd'T'HH:mm:ss.SSS'Z'`), ensuring consistent and parseable timestamp strings across all log entries.
### Error handling [#error-handling]
The logger includes built-in error handling:
* Network failures are caught and logged to the console without crashing the app.
* Failed log sends return `false` from the `flush()` method, allowing you to implement retry logic.
* Uninitialized logger calls are safely ignored with console warnings.
### Thread safety [#thread-safety]
The logger is designed for use in Flutter's single-threaded Dart environment. All async operations use Dart's `Future` API, ensuring proper sequencing of log operations without race conditions.
---
# Send data from Go app to Axiom
Source: https://axiom.co/docs/guides/go
To send data from a Go app to Axiom, use the Axiom Go SDK.
The Axiom Go SDK is an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/axiom-go).
## Install SDK [#install-sdk]
To install the SDK, run the following:
```shell
go get github.com/axiomhq/axiom-go/axiom
```
Import the package:
```go
import "github.com/axiomhq/axiom-go/axiom"
```
If you use the [Axiom CLI](/reference/cli), run `eval $(axiom config export -f)` to configure your environment variables. Otherwise, [create an API token](/reference/tokens) and export it as `AXIOM_TOKEN`.
Alternatively, configure the client using [options](https://pkg.go.dev/github.com/axiomhq/axiom-go/axiom#Option) passed to the `axiom.NewClient` function:
```go
client, err := axiom.NewClient(
axiom.SetPersonalTokenConfig("AXIOM_TOKEN"),
)
```
## Use client [#use-client]
Create and use a client in the following way:
```go
package main
import (
"context"
"fmt"
"log"
"github.com/axiomhq/axiom-go/axiom"
"github.com/axiomhq/axiom-go/axiom/ingest"
)
func main() {
ctx := context.Background()
client, err := axiom.NewClient()
if err != nil {
log.Fatal(err)
}
if _, err = client.IngestEvents(ctx, "my-dataset", []axiom.Event{
{ingest.TimestampField: time.Now(), "foo": "bar"},
{ingest.TimestampField: time.Now(), "bar": "foo"},
}); err != nil {
log.Fatal(err)
}
res, err := client.Query(ctx, "['my-dataset'] | where foo == 'bar' | limit 100")
if err != nil {
log.Fatal(err)
} else if res.Status.RowsMatched == 0 {
log.Fatal("No matches found")
}
rows := res.Tables[0].Rows()
if err := rows.Range(ctx, func(_ context.Context, row query.Row) error {
_, err := fmt.Println(row)
return err
}); err != nil {
log.Fatal(err)
}
}
```
For more examples, see the [examples in GitHub](https://github.com/axiomhq/axiom-go/tree/main/examples).
## Configure region [#configure-region]
By default, the client sends data to `api.axiom.co`. To target a specific edge region, pass the `SetEdge` option with the edge domain that matches the region your dataset lives in:
```go EU Central 1
client, err := axiom.NewClient(
axiom.SetEdge("eu-central-1.aws.edge.axiom.co"),
)
```
```go US East 1
client, err := axiom.NewClient(
axiom.SetEdge("us-east-1.aws.edge.axiom.co"),
)
```
This relies on `AXIOM_TOKEN` being set in your environment. To set the token in code as well, add `axiom.SetToken("xaat-...")`.
The following edge domains are available:
| Edge deployment | Base domain for ingest and query |
| ------------------ | -------------------------------- |
| US East 1 (AWS) | `us-east-1.aws.edge.axiom.co` |
| EU Central 1 (AWS) | `eu-central-1.aws.edge.axiom.co` |
For more information about edge deployments, see [Edge deployments](/reference/edge-deployments).
You can also configure the region without changing code by exporting environment variables:
```sh
export AXIOM_EDGE="eu-central-1.aws.edge.axiom.co"
# or, for a full URL (takes precedence over AXIOM_EDGE):
export AXIOM_EDGE_URL="https://eu-central-1.aws.edge.axiom.co"
```
To point at a custom edge endpoint such as a proxy or load balancer, use `axiom.SetEdgeURL("https://your-edge-host")`. `SetEdgeURL` takes precedence over `SetEdge` if both are set.
Edge endpoints require an API token (`xaat-`), not a personal token (`xapt-`). Ingest and query calls with a personal token against an edge endpoint return `personal tokens are not supported for edge operations, use an API token (xaat-)`.
## Adapters [#adapters]
To use a logging package, see the following adapters:
* [Apex](/guides/apex)
* [Logrus](/guides/logrus)
* [Zap](/guides/zap)
---
# Send data from JavaScript app to Axiom
Source: https://axiom.co/docs/guides/javascript
JavaScript is a versatile, high-level programming language primarily used for creating dynamic and interactive web content.
To send data from a JavaScript app to Axiom, use one of the following libraries of the Axiom JavaScript SDK:
*
@axiomhq/js
*
@axiomhq/logging
The choice between these options depends on your individual requirements:
| Capabilities | @axiomhq/js | @axiomhq/logging |
| --------------------------------------------------- | ----------- | ---------------- |
| Send data to Axiom | Yes | Yes |
| Query data | Yes | No |
| Capture errors | Yes | No |
| Create annotations | Yes | No |
| Transports | No | Yes |
| Structured logging by default | No | Yes |
| Send data to multiple places from a single function | No | Yes |
The `@axiomhq/logging` library is a logging solution that also serves as the base for other libraries like `@axiomhq/react` and `@axiomhq/nextjs`.
The @axiomhq/js and the @axiomhq/logging libraries are part of the Axiom JavaScript SDK, an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/axiom-js).
## Use @axiomhq/js [#use-axiomhqjs]
### Install @axiomhq/js [#install-axiomhqjs]
In your terminal, go to the root folder of your JavaScript app and run the following command:
```shell
npm install @axiomhq/js
```
### Configure environment variables [#configure-environment-variables]
Configure the environment variables in one of the following ways:
* Export the API token as `AXIOM_TOKEN`.
* Pass the API token to the constructor of the client:
```ts
import { Axiom } from '@axiomhq/js';
const axiom = new Axiom({
token: process.env.AXIOM_TOKEN,
});
```
* Install the [Axiom CLI](/reference/cli), and then run the following command:
```sh
eval $(axiom config export -f)
```
### Configure region [#configure-region]
By default, the client sends data to `api.axiom.co`. To target a specific edge region, set the `edge` option to the edge domain that matches the region your dataset lives in:
```ts EU Central 1
import { Axiom } from '@axiomhq/js';
const axiom = new Axiom({
token: process.env.AXIOM_TOKEN,
edge: 'eu-central-1.aws.edge.axiom.co',
});
```
```ts US East 1
import { Axiom } from '@axiomhq/js';
const axiom = new Axiom({
token: process.env.AXIOM_TOKEN,
edge: 'us-east-1.aws.edge.axiom.co',
});
```
The following edge domains are available:
| Edge deployment | Base domain for ingest and query |
| ------------------ | -------------------------------- |
| US East 1 (AWS) | `us-east-1.aws.edge.axiom.co` |
| EU Central 1 (AWS) | `eu-central-1.aws.edge.axiom.co` |
For more information about edge deployments, see [Edge deployments](/reference/edge-deployments).
Always use the `edge` option to target a region. Don't put a regional hostname in `url` — `url` is reserved for non-ingest API operations and won't route ingest correctly.
Edge endpoints require an API token (`xaat-`). Personal tokens (`xapt-`) are deprecated and aren't supported for edge routing.
### Client options [#client-options]
| Option | Required | Description |
| --------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
| `token` | yes | An Axiom API token with `ingest` permission for the dataset. |
| `orgId` | no | Organization ID. Required when using a personal token. |
| `edge` | no | Edge domain for ingest and query, without scheme. Example: `eu-central-1.aws.edge.axiom.co`. Use this to target a region. |
| `edgeUrl` | no | Full edge URL with scheme. Takes precedence over `edge` if both are set. Useful for self-hosted or proxy setups. |
| `url` | no | Base URL for non-ingest API operations. Only needed if you call other Axiom APIs from the same client. |
| `onError` | no | Callback invoked when sending data fails. Defaults to `console.error`. |
### Send data to Axiom [#send-data-to-axiom]
The following example sends data to Axiom:
```ts
axiom.ingest('DATASET_NAME', [{ foo: 'bar' }]);
await axiom.flush();
```
The client automatically batches events in the background. In most cases, call `flush()` only before your app exits.
### Query data [#query-data]
The following example queries data from Axiom:
```ts
const res = await axiom.query(`['DATASET_NAME'] | where foo == 'bar' | limit 100`);
console.log(res);
```
For more examples, see the [examples in GitHub](https://github.com/axiomhq/axiom-js/tree/main/examples).
### Capture errors [#capture-errors]
To capture errors, pass a method `onError` to the client:
```ts
let client = new Axiom({
token: '',
...,
onError: (err) => {
console.error('ERROR:', err);
}
});
```
By default, `onError` is set to `console.error`.
### Create annotations [#create-annotations]
The following example creates an annotation:
```ts
import { annotations } from '@axiomhq/js';
const client = new annotations.Service({ token: process.env.AXIOM_TOKEN });
await annotations.create({
type: 'deployment',
datasets: ['DATASET_NAME'],
title: 'New deployment',
description: 'Deployed version 1.0.0',
})
```
## Use @axiomhq/logging [#use-axiomhqlogging]
### Install @axiomhq/logging [#install-axiomhqlogging]
In your terminal, go to the root folder of your JavaScript app and run the following command:
```bash
npm install @axiomhq/logging
```
### Send data to Axiom [#send-data-to-axiom-1]
The following example sends data to Axiom:
```ts
import { Logger, AxiomJSTransport, ConsoleTransport } from "@axiomhq/logging";
import { Axiom } from "@axiomhq/js";
const axiom = new Axiom({
token: process.env.AXIOM_TOKEN,
});
const logger = new Logger(
{
transports: [
new AxiomJSTransport({
axiom,
dataset: process.env.AXIOM_DATASET,
}),
new ConsoleTransport(),
],
}
);
logger.info("Hello, world!");
```
Because `AxiomJSTransport` sends data through the `@axiomhq/js` client, set the region by passing the `edge` option to the `Axiom` constructor. For the full list of edge domains, see [Configure region](#configure-region).
#### Transports [#transports]
The `@axiomhq/logging` library includes the following transports:
* `ConsoleTransport`: Logs to the console.
```ts
import { ConsoleTransport } from "@axiomhq/logging";
const transport = new ConsoleTransport({
logLevel: "warn",
prettyPrint: true,
});
```
* `AxiomJSTransport`: Sends logs to Axiom using the @axiomhq/js library.
```ts
import { Axiom } from "@axiomhq/js";
import { AxiomJSTransport } from "@axiomhq/logging";
const axiom = new Axiom({
token: process.env.AXIOM_TOKEN,
});
const transport = new AxiomJSTransport({
axiom,
dataset: process.env.AXIOM_DATASET,
logLevel: "warn",
});
```
* `ProxyTransport`: Sends logs the [proxy server function](/send-data/nextjs#proxy-for-client-side-usage) that acts as a proxy between your app and Axiom. It’s particularly useful when your app runs on top of a server-enabled framework like Next.js or Remix.
```ts
import { ProxyTransport } from "@axiomhq/logging";
const transport = new ProxyTransport({
url: "/proxy",
logLevel: "warn",
autoFlush: { durationMs: 1000 },
});
```
Alternatively, create your own transports by implementing the `Transport` interface:
```ts
import { Transport } from "@axiomhq/logging";
class MyTransport implements Transport {
log(log: Transport['log']) {
console.log(log);
}
flush() {
console.log("Flushing logs");
}
}
```
#### Logging levels [#logging-levels]
The `@axiomhq/logging` library includes the following logging levels:
* `debug`: Debug-level logs.
* `info`: Informational logs.
* `warn`: Warning logs.
* `error`: Error logs.
#### Formatters [#formatters]
Formatters are used to change the content of a log before sending it to a transport. For example:
```ts
import { Logger, LogEvent } from "@axiomhq/logging";
const myCustomFormatter = (event: LogEvent) => {
const upperCaseKeys = {
...event,
fields: Object.fromEntries(
Object.entries(event.fields).map(([key, value]) => [key.toUpperCase(), value])
),
};
return upperCaseKeys;
};
const logger = new Logger({
formatters: [myCustomFormatter],
});
logger.info("Hello, world!");
```
## Related logging options [#related-logging-options]
### Send data from JavaScript libraries and frameworks [#send-data-from-javascript-libraries-and-frameworks]
To send data to Axiom from JavaScript libraries and frameworks, see the following:
* [Send data from React app](/send-data/react)
* [Send data from Next.js app](/send-data/nextjs)
### Send data from Node.js [#send-data-from-nodejs]
While the Axiom JavaScript SDK works on both the backend and the browsers, Axiom provides transports for some of the popular loggers:
* [Pino](/guides/pino)
* [Winston](/guides/winston)
---
# Axiom Go adapter for Logrus
Source: https://axiom.co/docs/guides/logrus
Use the adapter of the Axiom Go SDK to send logs generated by the [sirupsen/logrus](https://github.com/sirupsen/logrus) library to Axiom.
The Axiom Go SDK is an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/axiom-go).
## Set up SDK [#set-up-sdk]
1. Install the Axiom Go SDK and configure your environment as explained in [Send data from Go app to Axiom](/guides/go).
2. In your Go app, import the `logrus` package. It’s imported as an `adapter` so that it doesn’t conflict with the `sirupsen/logrus` package.
```go
import adapter "github.com/axiomhq/axiom-go/adapters/logrus"
```
Alternatively, configure the adapter using [options](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/logrus#Option) passed to the [New](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/logrus#New) function:
```go
hook, err := adapter.New(
adapter.SetDataset("DATASET_NAME"),
)
```
## Configure client [#configure-client]
To configure the underlying client manually, choose one of the following:
* Use [SetClient](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/logrus#SetClient) to pass in the client you have previously created with [Send data from Go app to Axiom](/guides/go).
* Use [SetClientOptions](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/logrus#SetClientOptions) to pass [client options](https://pkg.go.dev/github.com/axiomhq/axiom-go/axiom#Option) to the adapter.
```go
import (
"github.com/axiomhq/axiom-go/axiom"
adapter "github.com/axiomhq/axiom-go/adapters/logrus"
)
// ...
hook, err := adapter.New(
adapter.SetClientOptions(
axiom.SetPersonalTokenConfig("AXIOM_TOKEN"),
),
)
```
### Configure region [#configure-region]
By default, the adapter sends data to `api.axiom.co`. To target a specific edge region, pass `axiom.SetEdge` through `SetClientOptions` with the edge domain that matches the region your dataset lives in:
```go EU Central 1
hook, err := adapter.New(
adapter.SetClientOptions(
axiom.SetAPITokenConfig("xaat-your-api-token"),
axiom.SetEdge("eu-central-1.aws.edge.axiom.co"),
),
)
```
```go US East 1
hook, err := adapter.New(
adapter.SetClientOptions(
axiom.SetAPITokenConfig("xaat-your-api-token"),
axiom.SetEdge("us-east-1.aws.edge.axiom.co"),
),
)
```
The following edge domains are available:
| Edge deployment | Base domain for ingest and query |
| ------------------ | -------------------------------- |
| US East 1 (AWS) | `us-east-1.aws.edge.axiom.co` |
| EU Central 1 (AWS) | `eu-central-1.aws.edge.axiom.co` |
Edge endpoints require an API token (`xaat-`), not a personal token (`xapt-`). Use `axiom.SetAPITokenConfig` when targeting an edge region. You can also set `AXIOM_EDGE` or `AXIOM_EDGE_URL` in the environment instead of `axiom.SetEdge`. For a full edge URL such as a proxy, use `axiom.SetEdgeURL`, which takes precedence over `axiom.SetEdge`.
The adapter uses a buffer to batch events before sending them to Axiom. Flush this buffer explicitly by calling [Close](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/logrus#Hook.Close). For more information, see the [example in GitHub](https://github.com/axiomhq/axiom-go/blob/main/examples/logrus/main.go).
## Reference [#reference]
For a full reference of the adapter’s functions, see the [Go Packages page](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/logrus).
---
# Monitor Claude Code with Axiom
Source: https://axiom.co/docs/guides/opentelemetry-claude-code
Claude Code has built-in [OpenTelemetry support](https://code.claude.com/docs/en/monitoring-usage) that exports metrics and logs. Axiom natively ingests OTLP data, making the two a natural fit. This guide covers the Axiom-specific configuration to connect Claude Code telemetry to Axiom.
## Prerequisites [#prerequisites]
* [Create an Axiom account](https://app.axiom.co/register).
* Create two datasets in Axiom: one for metrics and one for logs. Claude Code doesn't emit traces. For more information, see [Create dataset](/reference/datasets#create-dataset).
* [Create an API token in Axiom](/reference/tokens) with ingest permissions for both datasets.
## How Axiom routes OpenTelemetry data [#how-axiom-routes-opentelemetry-data]
Axiom routes data to datasets via headers, and the header differs by signal type:
* Logs use `x-axiom-dataset`
* Metrics use `x-axiom-metrics-dataset`
This means signal-specific header configuration is required rather than a single shared `OTEL_EXPORTER_OTLP_HEADERS` value.
The `/v1/metrics` endpoint only supports the `application/x-protobuf` content type.
## Configure environment variables [#configure-environment-variables]
Create a file named `setup-otel.sh` with the following content:
Don't execute the script directly. Source the script instead. The exported variables must exist in the current shell where Claude inherits them.
```bash setup-otel.sh
#!/bin/bash
# Claude Code OpenTelemetry configuration for Axiom
# Usage: source ./setup-otel.sh && claude
# Replace these with your own values
AXIOM_API_TOKEN="API_TOKEN"
AXIOM_HOST="AXIOM_DOMAIN"
AXIOM_METRICS_DATASET="METRICS_DATASET_NAME"
AXIOM_LOGS_DATASET="LOGS_DATASET_NAME"
# Enable telemetry and configure both exporters
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
# Axiom's metrics endpoint requires protobuf (no JSON support)
export OTEL_EXPORTER_OTLP_METRICS_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/protobuf
# Separate endpoints per signal type
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://${AXIOM_HOST}/v1/metrics
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://${AXIOM_HOST}/v1/logs
# Separate headers per signal type (Axiom routes to datasets via different headers)
export OTEL_EXPORTER_OTLP_METRICS_HEADERS="Authorization=Bearer ${AXIOM_API_TOKEN},x-axiom-metrics-dataset=${AXIOM_METRICS_DATASET}"
export OTEL_EXPORTER_OTLP_LOGS_HEADERS="Authorization=Bearer ${AXIOM_API_TOKEN},x-axiom-dataset=${AXIOM_LOGS_DATASET}"
# Shorter intervals for testing (default: 60s metrics, 5s logs)
export OTEL_METRIC_EXPORT_INTERVAL=10000
export OTEL_LOGS_EXPORT_INTERVAL=5000
# Optional: log full prompt content and tool/MCP details
export OTEL_LOG_USER_PROMPTS=1
export OTEL_LOG_TOOL_DETAILS=1
echo "Axiom OTel configured: metrics -> ${AXIOM_METRICS_DATASET}, logs -> ${AXIOM_LOGS_DATASET}"
```
Replace `METRICS_DATASET_NAME` with the name of the Axiom dataset for metrics.
Replace `LOGS_DATASET_NAME` with the name of the Axiom dataset for logs.
## Verify the integration [#verify-the-integration]
Run the following command in your terminal:
```bash
source ./setup-otel.sh && claude
```
Use Claude Code for 15 to 20 seconds to generate telemetry data. Ask questions, run commands, or perform any typical tasks.
In Axiom, go to your datasets and observe the telemetry data:
* The logs dataset shows events like `claude_code.user_prompt`, `claude_code.tool_result`, and `claude_code.api_request`.
* The metrics dataset shows counters like `claude_code.session.count` and `claude_code.token.usage`, updating on the 10-second interval configured above.
If one signal arrives but the other doesn't, double-check the headers. The most common mistake is using `x-axiom-dataset` for metrics instead of `x-axiom-metrics-dataset`.
## Production considerations [#production-considerations]
When moving from testing to production, consider these adjustments:
* **Disable prompt logging**: Remove `OTEL_LOG_USER_PROMPTS=1` unless prompt content is needed in your observability backend. This reduces the volume of sensitive data stored.
* **Use managed settings for teams**: For team deployments, administrators can set these variables in Claude Code's [managed settings file](https://code.claude.com/docs/en/monitoring-usage#administrator-configuration) instead of relying on each developer to source a script.
* **Add resource attributes**: Use `OTEL_RESOURCE_ATTRIBUTES` to tag all telemetry with department, team, or cost center identifiers for filtering and alerting in Axiom.
```bash
export OTEL_RESOURCE_ATTRIBUTES="team=platform,cost_center=engineering,environment=production"
```
* **Adjust export intervals**: For production, consider using longer intervals to reduce overhead:
```bash
export OTEL_METRIC_EXPORT_INTERVAL=60000 # 60 seconds
export OTEL_LOGS_EXPORT_INTERVAL=30000 # 30 seconds
```
## Reference [#reference]
### Environment variables [#environment-variables]
| Variable | Description |
| ------------------------------------- | -------------------------------------------------------- |
| `CLAUDE_CODE_ENABLE_TELEMETRY` | Set to `1` to enable OpenTelemetry export. |
| `OTEL_METRICS_EXPORTER` | Set to `otlp` to enable OTLP metrics export. |
| `OTEL_LOGS_EXPORTER` | Set to `otlp` to enable OTLP logs export. |
| `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL` | Protocol for metrics. Use `http/protobuf` for Axiom. |
| `OTEL_EXPORTER_OTLP_LOGS_PROTOCOL` | Protocol for logs. Use `http/protobuf` for Axiom. |
| `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | Axiom metrics endpoint URL. |
| `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | Axiom logs endpoint URL. |
| `OTEL_EXPORTER_OTLP_METRICS_HEADERS` | Headers for metrics including authorization and dataset. |
| `OTEL_EXPORTER_OTLP_LOGS_HEADERS` | Headers for logs including authorization and dataset. |
| `OTEL_METRIC_EXPORT_INTERVAL` | Metrics export interval in milliseconds. Default: 60000. |
| `OTEL_LOGS_EXPORT_INTERVAL` | Logs export interval in milliseconds. Default: 5000. |
| `OTEL_LOG_USER_PROMPTS` | Set to `1` to log full prompt content. |
| `OTEL_LOG_TOOL_DETAILS` | Set to `1` to log tool and MCP details. |
| `OTEL_RESOURCE_ATTRIBUTES` | Comma-separated key=value pairs for resource attributes. |
### Expected telemetry data [#expected-telemetry-data]
Claude Code exports the following telemetry:
**Logs:**
* `claude_code.user_prompt`: User prompts sent to Claude Code.
* `claude_code.tool_result`: Results from tool executions.
* `claude_code.api_request`: API requests made by Claude Code.
**Metrics:**
* `claude_code.session.count`: Number of Claude Code sessions.
* `claude_code.token.usage`: Token usage counters.
---
# OpenTelemetry using Cloudflare Workers
Source: https://axiom.co/docs/guides/opentelemetry-cloudflare-workers
This guide demonstrates how to configure OpenTelemetry in Cloudflare Workers to send telemetry data to Axiom using the [OTel CF Worker package](https://github.com/evanderkoogh/otel-cf-workers).
This guide also applies to web frameworks that run on Cloudflare Workers. For example, you can use the same OpenTelemetry configuration described below for [Hono](https://hono.dev/) apps deployed to Cloudflare Workers.
* Create a Cloudflare account.
* [Install Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/), the CLI tool for Cloudflare.
## Setting up your Cloudflare Workers environment [#setting-up-your-cloudflare-workers-environment]
Create a new directory for your project and navigate into it:
```bash
mkdir my-axiom-worker && cd my-axiom-worker
```
Initialize a new Wrangler project using this command:
```bash
wrangler init --type="javascript"
```
## Cloudflare Workers Script Configuration (index.ts) [#cloudflare-workers-script-configuration-indexts]
Configure and implement your Workers script by integrating OpenTelemetry with the `@microlabs/otel-cf-workers` package to send telemetry data to Axiom, as illustrated in the example `index.ts` below:
```js
// index.ts
import { trace } from '@opentelemetry/api';
import { instrument, ResolveConfigFn } from '@microlabs/otel-cf-workers';
export interface Env {
AXIOM_TOKEN: string,
AXIOM_DATASET: string
}
const handler = {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise {
await fetch('https://cloudflare.com');
const greeting = "Welcome to Axiom Cloudflare instrumentation";
trace.getActiveSpan()?.setAttribute('greeting', greeting);
ctx.waitUntil(fetch('https://workers.dev'));
return new Response(`${greeting}!`);
},
};
const config: ResolveConfigFn = (env: Env, _trigger) => {
return {
exporter: {
url: 'https://AXIOM_DOMAIN/v1/traces',
headers: {
'Authorization': `Bearer ${env.AXIOM_TOKEN}`,
'X-Axiom-Dataset': `${env.AXIOM_DATASET}`
},
},
service: { name: 'axiom-cloudflare-workers' },
};
};
export default instrument(handler, config);
```
## Wrangler Configuration (`wrangler.toml`) [#wrangler-configuration-wranglertoml]
Configure **`wrangler.toml`** with your Cloudflare account details and set environment variables for the Axiom API token and dataset.
```toml
name = "my-axiom-worker"
type = "javascript"
account_id = "$YOUR_CLOUDFLARE_ACCOUNT_ID" # Replace with your actual Cloudflare account ID
workers_dev = true
compatibility_date = "2023-03-27"
compatibility_flags = ["nodejs_compat"]
main = "index.ts"
# Define environment variables here
[vars]
AXIOM_TOKEN = "API_TOKEN"
AXIOM_DATASET = "DATASET_NAME"
```
## Install Dependencies [#install-dependencies]
Navigate to the root directory of your project and add `@microlabs/otel-cf-workers` and other OTel packages to the `package.json` file.
```json
{
"name": "my-axiom-worker",
"version": "1.0.0",
"description": "A template for kick-starting a Cloudflare Workers project",
"main": "index.ts",
"scripts": {
"start": "wrangler dev",
"deploy": "wrangler publish"
},
"dependencies": {
"@microlabs/otel-cf-workers": "^1.0.0-rc.20",
"@opentelemetry/api": "^1.6.0",
"@opentelemetry/core": "^1.17.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.43.0",
"@opentelemetry/otlp-exporter-base": "^0.43.0",
"@opentelemetry/otlp-transformer": "^0.43.0",
"@opentelemetry/resources": "^1.17.1",
"@opentelemetry/sdk-trace-base": "^1.17.1",
"@opentelemetry/semantic-conventions": "^1.17.1",
"deepmerge": "^4.3.1",
"husky": "^8.0.3",
"lint-staged": "^15.0.2",
"ts-checked-fsm": "^1.1.0"
},
"devDependencies": {
"@changesets/cli": "^2.26.2",
"@cloudflare/workers-types": "^4.20231016.0",
"prettier": "^3.0.3",
"rimraf": "^4.4.1",
"typescript": "^5.2.2",
"wrangler": "2.13.0"
},
"private": true
}
```
Run `npm install` to install the packages. This command will install all the necessary packages listed in your `package.json` file.
## Running the instrumented app [#running-the-instrumented-app]
To run your Cloudflare Workers app with OpenTelemetry instrumentation, ensure your API token and dataset are correctly set in your `wrangler.toml` file. As outlined in the `package.json` file, you have two primary scripts to manage your app’s lifecycle.
### In development mode [#in-development-mode]
For local development and testing, you can start a local development server by running:
```bash
npm run start
```
This command runs `wrangler dev` allowing you to preview and test your app locally.
### Deploying to production [#deploying-to-production]
Deploy your app to the Cloudflare Workers environment by running:
```bash
npm run deploy
```
This command runs **`wrangler publish`**, deploying your project to Cloudflare Workers.
### Alternative: Use Wrangler directly [#alternative-use-wrangler-directly]
If you prefer not to use **`npm`** commands or want more direct control over the deployment process, you can use Wrangler commands directly in your terminal.
For local development:
```bash
wrangler dev
```
For deploying to Cloudflare Workers:
```bash
wrangler deploy
```
## View your app in Cloudflare Workers [#view-your-app-in-cloudflare-workers]
Once you’ve deployed your app using Wrangler, view and manage it through the Cloudflare dashboard. To see your Cloudflare Workers app, follow these steps:
* In your [Cloudflare dashboard](https://dash.cloudflare.com/), click **Workers & Pages** to access the Workers section. You see a list of your deployed apps.
* Locate your app by its name. For this tutorial, look for `my-axiom-worker`.
* Click your app’s name to view its details. Within the app’s page, select the triggers tab to review the triggers associated with your app.
* Under the routes section of the triggers tab, you will find the URL route assigned to your Worker. This is where your Cloudflare Worker responds to incoming requests. Vist the [Cloudflare Workers documentation](https://developers.cloudflare.com/workers/get-started/guide/) to learn how to configure routes
## Observe the telemetry data in Axiom [#observe-the-telemetry-data-in-axiom]
As you interact with your app, traces will be collected and exported to Axiom, allowing you to monitor, analyze, and gain insights into your app’s performance and behavior.
## Dynamic OpenTelemetry traces dashboard [#dynamic-opentelemetry-traces-dashboard]
This data can then be further viewed and analyzed in Axiom’s dashboard, offering a deeper understanding of your app’s performance and behavior.
**Working with Cloudflare Pages Functions:** Integration with OpenTelemetry is similar to Workers but uses the Cloudflare Dashboard for configuration, bypassing **`wrangler.toml`**. This simplifies setup through the Cloudflare dashboard web interface.
## Manual Instrumentation [#manual-instrumentation]
Manual instrumentation requires adding code into your Worker’s script to create and manage spans around the code blocks you want to trace.
1. Initialize Tracer:
Use the OpenTelemetry API to create a tracer instance at the beginning of your script using the **`@microlabs/otel-cf-workers`** package.
```js
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('your-service-name');
```
2. Create start and end Spans:
Manually start spans before the operations or events you want to trace and ensure you end them afterward to complete the tracing lifecycle.
```js
const span = tracer.startSpan('operationName');
try {
// Your operation code here
} finally {
span.end();
}
```
3. Annotate Spans:
Add important metadata to spans to provide additional context. This can include setting attributes or adding events within the span.
```js
span.setAttribute('key', 'value');
span.addEvent('eventName', { 'eventAttribute': 'value' });
```
## Automatic Instrumentation [#automatic-instrumentation]
Automatic instrumentation uses the **`@microlabs/otel-cf-workers`** package to automatically trace incoming requests and outbound fetch calls without manual span management.
1. Instrument your Worker:
Wrap your Cloudflare Workers script with the `instrument` function from the **`@microlabs/otel-cf-workers`** package. This automatically instruments incoming requests and outbound fetch calls.
```js
import { instrument } from '@microlabs/otel-cf-workers';
export default instrument(yourHandler, yourConfig);
```
2. Configuration: Provide configuration details, including how to export telemetry data and service metadata to Axiom as part of the `instrument` function call.
```js
const config = (env) => ({
exporter: {
url: 'https://AXIOM_DOMAIN/v1/traces',
headers: {
'Authorization': `Bearer ${env.AXIOM_TOKEN}`,
'X-Axiom-Dataset': `${env.AXIOM_DATASET}`
},
},
service: { name: 'axiom-cloudflare-workers' },
});
```
After instrumenting your Worker script, the `@microlabs/otel-cf-workers` package takes care of tracing automatically.
## Reference [#reference]
### List of OpenTelemetry trace fields [#list-of-opentelemetry-trace-fields]
| Field Category | Field Name | Description |
| ---------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------- |
| **Unique Identifiers** | | |
| | \_rowid | Unique identifier for each row in the trace data. |
| | span\_id | Unique identifier for the span within the trace. |
| | trace\_id | Unique identifier for the entire trace. |
| **Timestamps** | | |
| | \_systime | System timestamp when the trace data was recorded. |
| | \_time | Timestamp when the actual event being traced occurred. |
| **HTTP Attributes** | | |
| | attributes.custom\["http.host"] | Host information where the HTTP request was sent. |
| | attributes.custom\["http.server\_name"] | Server name for the HTTP request. |
| | attributes.http.flavor | HTTP protocol version used. |
| | attributes.http.method | HTTP method used for the request. |
| | attributes.http.route | Route accessed during the HTTP request. |
| | attributes.http.scheme | Protocol scheme (HTTP/HTTPS). |
| | attributes.http.status\_code | HTTP response status code. |
| | attributes.http.target | Specific target of the HTTP request. |
| | attributes.http.user\_agent | User agent string of the client. |
| | attributes.custom.user\_agent.original | Original user agent string, providing client software and OS. |
| | attributes.custom\["http.accepts"] | Accepted content types for the HTTP request. |
| | attributes.custom\["http.mime\_type"] | MIME type of the HTTP response. |
| | attributes.custom.http.wrote\_bytes | Number of bytes written in the HTTP response. |
| | attributes.http.request.method | HTTP request method used. |
| | attributes.http.response.status\_code | HTTP status code returned in response. |
| **Network Attributes** | | |
| | attributes.net.host.port | Port number on the host receiving the request. |
| | attributes.net.peer.port | Port number on the peer (client) side. |
| | attributes.custom\["net.peer.ip"] | IP address of the peer in the network interaction. |
| | attributes.net.sock.peer.addr | Socket peer address, indicating the IP version used. |
| | attributes.net.sock.peer.port | Socket peer port number. |
| | attributes.custom.net.protocol.version | Protocol version used in the network interaction. |
| | attributes.network.protocol.name | Name of the network protocol used. |
| | attributes.network.protocol.version | Version of the network protocol used. |
| | attributes.server.address | Address of the server handling the request. |
| | attributes.url.full | Full URL accessed in the request. |
| | attributes.url.path | Path component of the URL accessed. |
| | attributes.url.query | Query component of the URL accessed. |
| | attributes.url.scheme | Scheme component of the URL accessed. |
| **Operational Details** | | |
| | duration | Time taken for the operation. |
| | kind | Type of span (for example,, server, client). |
| | name | Name of the span. |
| | scope | Instrumentation scope. |
| | scope.name | Name of the scope for the operation. |
| | service.name | Name of the service generating the trace. |
| | service.version | Version of the service generating the trace. |
| **Resource Attributes** | | |
| | resource.environment | Environment where the trace was captured, for example,, production. |
| | resource.cloud.platform | Platform of the cloud provider, for example,, cloudflare.workers. |
| | resource.cloud.provider | Name of the cloud provider, for example,, cloudflare. |
| | resource.cloud.region | Cloud region where the service is located, for example,, earth. |
| | resource.faas.max\_memory | Maximum memory allocated for the function as a service (FaaS). |
| **Telemetry SDK Attributes** | | |
| | telemetry.sdk.language | Language of the telemetry SDK, for example,, js. |
| | telemetry.sdk.name | Name of the telemetry SDK, for example,, @microlabs/otel-workers-sdk. |
| | telemetry.sdk.version | Version of the telemetry SDK. |
| **Custom Attributes** | | |
| | attributes.custom.greeting | Custom greeting message, for example,, "Welcome to Axiom Cloudflare instrumentation." |
| | attributes.custom\["http.accepts"] | Specifies acceptable response formats for HTTP request. |
| | attributes.custom\["net.asn"] | Autonomous System Number representing the hosting entity. |
| | attributes.custom\["net.colo"] | Colocation center where the request was processed. |
| | attributes.custom\["net.country"] | Country where the request was processed. |
| | attributes.custom\["net.request\_priority"] | Priority of the request processing. |
| | attributes.custom\["net.tcp\_rtt"] | Round Trip Time of the TCP connection. |
| | attributes.custom\["net.tls\_cipher"] | TLS cipher suite used for the connection. |
| | attributes.custom\["net.tls\_version"] | Version of the TLS protocol used for the connection. |
| | attributes.faas.coldstart | Indicates if the function execution was a cold start. |
| | attributes.faas.invocation\_id | Unique identifier for the function invocation. |
| | attributes.faas.trigger | Trigger that initiated the function execution. |
### List of imported libraries [#list-of-imported-libraries]
**`@microlabs/otel-cf-workers`**
This package is designed for integrating OpenTelemetry within Cloudflare Workers. It provides automatic instrumentation capabilities, making it easier to collect telemetry data from your Workers apps without extensive manual instrumentation. This package simplifies tracing HTTP requests and other asynchronous operations within Workers.
**`@opentelemetry/api`**
The core API for OpenTelemetry in JavaScript, providing the necessary interfaces and utilities for tracing, metrics, and context propagation. In the context of Cloudflare Workers, it allows developers to manually instrument custom spans, manipulate context, and access the active span if needed.
**`@opentelemetry/exporter-trace-otlp-http`**
This exporter enables your Cloudflare Workers app to send trace data over HTTP to any backend that supports the OTLP (OpenTelemetry Protocol), such as Axiom. Using OTLP ensures compatibility with a wide range of observability tools and standardizes the data export process.
**`@opentelemetry/otlp-exporter-base`**, **`@opentelemetry/otlp-transformer`**
These packages provide the foundational elements for OTLP exporters, including the transformation of telemetry data into the OTLP format and base classes for implementing OTLP exporters. They're important for ensuring that the data exported from Cloudflare Workers adheres to the OTLP specification.
**`@opentelemetry/resources`**
Defines the Resource, which represents the entity producing telemetry. In Cloudflare Workers, Resources can be used to describe the worker (for example,, service name, version) and are attached to all exported telemetry, aiding in identifying data in backend systems.
---
# Send OpenTelemetry data from a Django app to Axiom
Source: https://axiom.co/docs/guides/opentelemetry-django
* [Install Python version 3.7 or higher](https://www.python.org/downloads/).
## Install required dependencies [#install-required-dependencies]
Install the necessary Python dependencies by running the following command in your terminal:
```bash
pip install django opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-http opentelemetry-instrumentation-django
```
Alternatively, you can add these dependencies to your `requirements.txt` file:
```bash
django
opentelemetry-api
opentelemetry-sdk
opentelemetry-exporter-otlp-proto-http
opentelemetry-instrumentation-django
```
Then, install them using the command:
```bash
pip install -r requirements.txt
```
## Get started with a Django project [#get-started-with-a-django-project]
1. Create a new Django project if you don’t have one already:
```bash
django-admin startproject your_project_name
```
2. Go to your project directory:
```bash
cd your_project_name
```
3. Create a Django app:
```bash
python manage.py startapp your_app_name
```
## Set up OpenTelemetry Tracing [#set-up-opentelemetry-tracing]
### Update `manage.py` to initialize tracing [#update-managepy-to-initialize-tracing]
This code initializes OpenTelemetry instrumentation for Django when the project is run. Adding `DjangoInstrumentor().instrument()` ensures that all incoming HTTP requests are automatically traced, which helps in monitoring the app’s performance and behavior without manually adding trace points in every view.
```py
# manage.py
#!/usr/bin/env python
import os
import sys
from opentelemetry.instrumentation.django import DjangoInstrumentor
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project_name.settings')
# Initialize OpenTelemetry instrumentation
DjangoInstrumentor().instrument()
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
```
### Create `exporter.py` for tracer configuration [#create-exporterpy-for-tracer-configuration]
This file configures the OpenTelemetry tracing provider and exporter. By setting up a `TracerProvider` and configuring the `OTLPSpanExporter`, you define how and where the trace data is sent. The `BatchSpanProcessor` is used to batch and send trace spans efficiently. The tracer created at the end is used throughout the app to create new spans.
```py
# exporter.py
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.resources import Resource, SERVICE_NAME
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
# Define the service name resource
resource = Resource(attributes={
SERVICE_NAME: "your-service-name" # Replace with your actual service name
})
# Create a TracerProvider with the defined resource
provider = TracerProvider(resource=resource)
# Configure the OTLP/HTTP Span Exporter with necessary headers and endpoint
otlp_exporter = OTLPSpanExporter(
endpoint="https://AXIOM_DOMAIN/v1/traces",
headers={
"Authorization": "Bearer API_TOKEN", # Replace with your actual API token
"X-Axiom-Dataset": "DATASET_NAME" # Replace with your dataset name
}
)
# Create a BatchSpanProcessor with the OTLP exporter
processor = BatchSpanProcessor(otlp_exporter)
provider.add_span_processor(processor)
# Set the TracerProvider as the global tracer provider
trace.set_tracer_provider(provider)
# Define a tracer for external use
tracer = trace.get_tracer("your-service-name")
```
### Use the tracer in your views [#use-the-tracer-in-your-views]
In this step, modify the Django views to use the tracer defined in `exporter.py`. By wrapping the view logic within `tracer.start_as_current_span`, you create spans that capture the execution of these views. This provides detailed insights into the performance of individual request handlers, helping to identify slow operations or errors.
```py
# views.py
from django.http import HttpResponse
from .exporter import tracer # Import the tracer
def roll_dice(request):
with tracer.start_as_current_span("roll_dice_span"):
# Your logic here
return HttpResponse("Dice rolled!")
def home(request):
with tracer.start_as_current_span("home_span"):
return HttpResponse("Welcome to the homepage!")
```
### Update `settings.py` for OpenTelemetry instrumentation [#update-settingspy-for-opentelemetry-instrumentation]
In your Django project’s `settings.py`, add the OpenTelemetry Django instrumentation. This setup automatically creates spans for HTTP requests handled by Django:
```py
# settings.py
from pathlib import Path
from opentelemetry.instrumentation.django import DjangoInstrumentor
DjangoInstrumentor().instrument()
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
```
### Update the app’s `urls.py` to include the views [#update-the-apps-urlspy-to-include-the-views]
Include your views in the URL routing by updating `urls.py`. Updating `urls.py` with these entries sets up the URL routing for the Django app. It connects the URL paths to the corresponding view functions. This ensures that when users visit the specified paths, the corresponding views are executed, and their spans are created and sent to Axiom for monitoring.
```python urls.py
from django.urls import path
from .views import roll_dice, home
urlpatterns = [
path('', home, name='home'),
path('rolldice/', roll_dice, name='roll_dice'),
]
```
## Run the project [#run-the-project]
Run the command to start the Django project:
```bash
python3 manage.py runserver
```
In your browser, go to `http://127.0.0.1:8000/rolldice` to interact with your Django app. Each time you load the page, the app displays a message and sends the collected traces to Axiom.
## Send data from an existing Django project [#send-data-from-an-existing-django-project]
### Manual instrumentation [#manual-instrumentation]
Manual instrumentation in Python with OpenTelemetry involves adding code to create and manage spans around the blocks of code you want to trace. This approach allows for precise control over the trace data.
1. Install necessary OpenTelemetry packages to enable manual tracing capabilities in your Django app.
```py
pip install django opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-http opentelemetry-instrumentation-django
```
2. Set up OpenTelemetry in your Django project to manually trace app activities.
```py
# otel_config.py
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
def configure_opentelemetry():
resource = Resource(attributes={"service.name": "your-django-app"})
trace.set_tracer_provider(TracerProvider(resource=resource))
otlp_exporter = OTLPSpanExporter(
endpoint="https://AXIOM_DOMAIN/v1/traces",
headers={"Authorization": "Bearer API_TOKEN", "X-Axiom-Dataset": "DATASET_NAME"}
)
span_processor = BatchSpanProcessor(otlp_exporter)
trace.get_tracer_provider().add_span_processor(span_processor)
return trace.get_tracer(__name__)
tracer = configure_opentelemetry()
```
3. Configure OpenTelemetry to your Django settings to capture telemetry data upon app startup.
```py
# settings.py
from otel_config import configure_opentelemetry
configure_opentelemetry()
```
4. Manually instrument views to create custom spans that trace specific operations within your Django app.
```py
# views.py
from django.http import HttpResponse
from otel_config import tracer
def home_view(request):
with tracer.start_as_current_span("home_view") as span:
span.set_attribute("http.method", request.method)
span.set_attribute("http.url", request.build_absolute_uri())
response = HttpResponse("Welcome to the home page!")
span.set_attribute("http.status_code", response.status_code)
return response
```
5. Apply manual tracing to database operations by wrapping database cursor executions with OpenTelemetry spans.
```py
# db_tracing.py
from django.db import connections
from otel_config import tracer
class TracingCursorWrapper:
def __init__(self, cursor):
self.cursor = cursor
def execute(self, sql, params=None):
with tracer.start_as_current_span("database_query") as span:
span.set_attribute("db.statement", sql)
span.set_attribute("db.type", "sql")
return self.cursor.execute(sql, params)
def __getattr__(self, attr):
return getattr(self.cursor, attr)
def patch_database():
for connection in connections.all():
connection.cursor_wrapper = TracingCursorWrapper
# settings.py
from db_tracing import patch_database
patch_database()
```
### Automatic instrumentation [#automatic-instrumentation]
Automatic instrumentation in Django with OpenTelemetry simplifies the process of adding telemetry data to your app. It uses pre-built libraries that automatically instrument the frameworks and libraries.
1. Install required packages that support automatic instrumentation.
```bash
pip install django opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-http opentelemetry-instrumentation-django
```
2. Automatically configure OpenTelemetry to trace Django app operations without manual span management.
```py
# otel_config.py
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.django import DjangoInstrumentor
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
def configure_opentelemetry():
resource = Resource(attributes={"service.name": "your-django-app"})
trace.set_tracer_provider(TracerProvider(resource=resource))
otlp_exporter = OTLPSpanExporter(
endpoint="https://AXIOM_DOMAIN/v1/traces",
headers={"Authorization": "Bearer API_TOKEN", "X-Axiom-Dataset": "DATASET_NAME"}
)
span_processor = BatchSpanProcessor(otlp_exporter)
trace.get_tracer_provider().add_span_processor(span_processor)
DjangoInstrumentor().instrument()
```
3. Initialize OpenTelemetry in Django to capture telemetry data from all HTTP requests automatically.
```py
# settings.py
from otel_config import configure_opentelemetry
configure_opentelemetry()
```
4. Update `manage.py` to include OpenTelemetry initialization, ensuring that tracing is active before the Django app fully starts.
```py
#!/usr/bin/env python
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project.settings')
from otel_config import configure_opentelemetry
configure_opentelemetry()
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError("Couldn't import Django.") from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
```
5. (Optional) Combine automatic and custom manual spans in Django views to enhance trace details for specific complex operations.
```py
# views.py
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
def complex_view(request):
with tracer.start_as_current_span("complex_operation"):
result = perform_complex_operation()
return HttpResponse(result)
```
## Reference [#reference]
### List of OpenTelemetry trace fields [#list-of-opentelemetry-trace-fields]
| Field Category | Field Name | Description |
| ------------------------- | --------------------------------------- | ----------------------------------------------------------------------------------- |
| General Trace Information | | |
| | \_rowId | Unique identifier for each row in the trace data. |
| | \_sysTime | System timestamp when the trace data was recorded. |
| | \_time | Timestamp when the actual event being traced occurred. |
| | trace\_id | Unique identifier for the entire trace. |
| | span\_id | Unique identifier for the span within the trace. |
| | parent\_span\_id | Unique identifier for the parent span within the trace. |
| HTTP Attributes | | |
| | attributes.http.method | HTTP method used for the request. |
| | attributes.http.status\_code | HTTP status code returned in response. |
| | attributes.http.route | Route accessed during the HTTP request. |
| | attributes.http.scheme | Protocol scheme (HTTP/HTTPS). |
| | attributes.http.url | Full URL accessed during the HTTP request. |
| User Agent | | |
| | attributes.http.user\_agent | User agent string, providing client software and OS. |
| Custom Attributes | | |
| | attributes.custom\["http.host"] | Host information where the HTTP request was sent. |
| | attributes.custom\["http.server\_name"] | Server name for the HTTP request. |
| | attributes.custom\["net.peer.ip"] | IP address of the peer in the network interaction. |
| Network Attributes | | |
| | attributes.net.host.port | Port number on the host receiving the request. |
| Operational Details | | |
| | duration | Time taken for the operation, typically in microseconds or milliseconds. |
| | kind | Type of span (For example, server, internal). |
| | name | Name of the span, often a high-level title for the operation. |
| Scope and Instrumentation | | |
| | scope | Instrumentation scope, (For example., opentelemetry.instrumentation.django.) |
| Service Attributes | | |
| | service.name | Name of the service generating the trace, typically set as the app or service name. |
| Telemetry SDK Attributes | | |
| | telemetry.sdk.language | Programming language of the SDK used for telemetry, typically 'python' for Django. |
| | telemetry.sdk.name | Name of the telemetry SDK, for example., OpenTelemetry. |
| | telemetry.sdk.version | Version of the telemetry SDK used in the tracing setup. |
### List of imported libraries [#list-of-imported-libraries]
The `exporter.py` file and other relevant parts of the Django OpenTelemetry setup import the following libraries:
### `exporter.py` [#exporterpy]
This module creates and manages trace data in your app. It creates spans and tracers which track the execution flow and performance of your app.
```py
from opentelemetry import trace
```
TracerProvider acts as a container for the configuration of your app’s tracing behavior. It allows you to define how spans are generated and processed, essentially serving as the central point for managing trace creation and propagation in your app.
```py
from opentelemetry.sdk.trace import TracerProvider
```
BatchSpanProcessor is responsible for batching spans before they’re exported. This is an important aspect of efficient trace data management as it aggregates multiple spans into fewer network requests, reducing the overhead on your app’s performance and the tracing backend.
```py
from opentelemetry.sdk.trace.export import BatchSpanProcessor
```
The Resource class is used to describe your app’s service attributes, such as its name, version, and environment. This contextual information is attached to the traces and helps in identifying and categorizing trace data, making it easier to filter and analyze in your monitoring setup.
```py
from opentelemetry.sdk.resources import Resource, SERVICE_NAME
```
The OTLPSpanExporter is responsible for sending your app’s trace data to a backend that supports the OTLP such as Axiom. It formats the trace data according to the OTLP standards and transmits it over HTTP, ensuring compatibility and standardization in how telemetry data is sent across different systems and services.
```py
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
```
### `manage.py` [#managepy]
The DjangoInstrumentor module is used to automatically instrument Django applications. It integrates OpenTelemetry with Django, enabling automatic creation of spans for incoming HTTP requests handled by Django, and simplifying the process of adding telemetry to your app.
```py
from opentelemetry.instrumentation.django import DjangoInstrumentor
```
### `views.py` [#viewspy]
This import brings in the tracer instance defined in `exporter.py`, which is used to create spans for tracing the execution of Django views. By wrapping view logic within `tracer.start_as_current_span`, it captures detailed insights into the performance of individual request handlers.
```py
from .exporter import tracer
```
---
# OpenTelemetry using .NET
Source: https://axiom.co/docs/guides/opentelemetry-dotnet
OpenTelemetry provides a [unified approach to collecting telemetry data](https://opentelemetry.io/docs/languages/net/) from your .NET apps. This guide explains how to configure OpenTelemetry in a .NET app to send telemetry data to Axiom using the OpenTelemetry SDK.
* Install the .NET 6.0 SDK on your development machine.
* Use your existing .NET app or start with the sample provided in the `program.cs` below.
## Install dependencies [#install-dependencies]
Run the following command in your terminal to install the necessary NuGet packages:
```bash
dotnet add package OpenTelemetry --version 1.7.0
dotnet add package OpenTelemetry.Exporter.Console --version 1.7.0
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol --version 1.7.0
dotnet add package OpenTelemetry.Extensions.Hosting --version 1.7.0
dotnet add package OpenTelemetry.Instrumentation.AspNetCore --version 1.7.1
dotnet add package OpenTelemetry.Instrumentation.Http --version 1.6.0-rc.1
```
Replace the `dotnet.csproj` file in your project with the following:
```csharp
net6.0
enable
enable
```
The `dotnet.csproj` file is important for defining your project’s settings, including target framework, nullable reference types, and package references. It informs the .NET SDK and build tools about the components and configurations your project requires.
## Core app [#core-app]
`program.cs` is the core of the .NET app. It uses ASP.NET to create a simple web server. The server has an endpoint `/rolldice` that returns a random number, simulating a basic API.
```csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Globalization;
// Set up the web app builder
var builder = WebApplication.CreateBuilder(args);
// Configure OpenTelemetry for detailed tracing information
TracingConfiguration.ConfigureOpenTelemetry();
var app = builder.Build();
// Map the GET request for '/rolldice/{player?}' to a handler
app.MapGet("/rolldice/{player?}", (ILogger logger, string? player) =>
{
// Start a manual tracing activity
using var activity = TracingConfiguration.StartActivity("HandleRollDice");
// Call the RollDice function to get a dice roll result
var result = RollDice();
if (activity != null)
{
// Add detailed information to the tracing activity for debugging and monitoring
activity.SetTag("player.name", player ?? "anonymous"); // Tag the player’s name, default to 'anonymous' if not provided
activity.SetTag("dice.rollResult", result); // Tag the result of the dice roll
activity.SetTag("operation.success", true); // Flag the operation as successful
activity.SetTag("custom.attribute", "Additional detail here"); // Add a custom attribute for potential further detail
}
// Log the dice roll event
LogRollDice(logger, player, result);
// Retur the dice roll result as a string
return result.ToString(CultureInfo.InvariantCulture);
});
// Start the web app
app.Run();
// Log function to log the result of a dice roll
void LogRollDice(ILogger logger, string? player, int result)
{
// Log message varies based on whether a player’s name is provided
if (string.IsNullOrEmpty(player))
{
// Log for an anonymous player
logger.LogInformation("Anonymous player is rolling the dice: {result}", result);
}
else
{
// Log for a named player
logger.LogInformation("{player} is rolling the dice: {result}", player, result);
}
}
// Function to roll a dice and return a random number between 1 and 6
int RollDice()
{
// Use the shared instance of Random for thread safety
return Random.Shared.Next(1, 7);
}
```
## Exporter [#exporter]
The `tracing.cs` file sets up the OpenTelemetry instrumentation. It configures the OTLP (OpenTelemetry Protocol) exporters for traces and initializes the ASP.NET SDK with automatic instrumentation capabilities.
```csharp
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
using System.Diagnostics;
using System.Reflection;
// Class to configure OpenTelemetry tracing
public static class TracingConfiguration
{
// Declare an ActivitySource for creating tracing activities
private static readonly ActivitySource ActivitySource = new("MyCustomActivitySource");
// Configure OpenTelemetry with custom settings and instrumentation
public static void ConfigureOpenTelemetry()
{
// Retrieve the service name and version from the executing assembly metadata
var serviceName = Assembly.GetExecutingAssembly().GetName().Name ?? "UnknownService";
var serviceVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "UnknownVersion";
// Set up the tracer provider with various configurations
Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(
// Set resource attributes including service name and version
ResourceBuilder.CreateDefault().AddService(serviceName, serviceVersion: serviceVersion)
.AddAttributes(new[] { new KeyValuePair("environment", "development") }) // Additional attributes
.AddTelemetrySdk() // Add telemetry SDK information to the traces
.AddEnvironmentVariableDetector()) // Detect resource attributes from environment variables
.AddSource(ActivitySource.Name) // Add the ActivitySource defined above
.AddAspNetCoreInstrumentation() // Add automatic instrumentation for ASP.NET Core
.AddHttpClientInstrumentation() // Add automatic instrumentation for HttpClient requests
.AddOtlpExporter(options => // Configure the OTLP exporter
{
options.Endpoint = new Uri("https://AXIOM_DOMAIN/v1/traces"); // Set the endpoint for the exporter
options.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf; // Set the protocol
options.Headers = "Authorization=Bearer API_TOKEN, X-Axiom-Dataset=DATASET_NAME"; // Update API token and dataset
})
.Build(); // Build the tracer provider
}
// Method to start a new tracing activity with an optional activity kind
public static Activity? StartActivity(string activityName, ActivityKind kind = ActivityKind.Internal)
{
// Starts and returns a new activity if sampling allows it, otherwise returns null
return ActivitySource.StartActivity(activityName, kind);
}
}
```
Replace the value of the `serviceName` variable with the name of the service you want to trace. This is used for identifying and categorizing trace data, particularly in systems with multiple services.
## Run the instrumented app [#run-the-instrumented-app]
1. Run in local development mode using the development settings in `appsettings.development.json`. Ensure your Axiom API token and dataset name are correctly set in `tracing.cs`.
2. Before deploying, run in production mode by switching to `appsettings.json` for production settings. Ensure your Axiom API token and dataset name are correctly set in `tracing.cs`.
3. Run your app with `dotnet run`. Your app starts and you can interact with it by sending requests to the `/rolldice` endpoint.
For example, if you are using port `8080`, your app is accessible locally at `http://localhost:8080/rolldice`. This URL will direct your requests to the `/rolldice` endpoint of your server running on your local machine.
## Observe the telemetry data [#observe-the-telemetry-data]
As you interact with your app, traces are collected and exported to Axiom where you can monitor and analyze your app’s performance and behavior.
1. Log into your Axiom account and click the **Datasets** or **Stream** tab.
2. Select your dataset from the list.
3. From the list of fields, click the **trace\_id**, to view your spans.
## Dynamic OpenTelemetry Traces dashboard [#dynamic-opentelemetry-traces-dashboard]
The data can then be further viewed and analyzed in the traces dashboard, providing insights into the performance and behavior of your app.
1. Log into your Axiom account, select **Dashboards**, and click the traces dashboard named after your dataset.
2. View the dashboard which displays your total traces, incoming spans, average span duration, errors, slowest operations, and top 10 span errors across services.
## Send data from an existing .NET project [#send-data-from-an-existing-net-project]
### Manual Instrumentation [#manual-instrumentation]
Manual instrumentation involves adding code to create, configure, and manage telemetry data, such as traces and spans, providing control over what data is collected.
1. Initialize ActivitySource. Define an `ActivitySource` to create activities (spans) for tracing specific operations within your app.
```csharp
private static readonly ActivitySource MyActivitySource = new ActivitySource("MyActivitySourceName");
```
2. Start and stop activities. Manually start activities (spans) at the beginning of the operations you want to trace and stop them when the operations complete. You can add custom attributes to these activities for more detailed tracing.
```csharp
using var activity = MyActivitySource.StartActivity("MyOperationName");
activity?.SetTag("key", "value");
// Perform the operation here
activity?.Stop();
```
3. Add custom attributes. Enhance activities with custom attributes to provide additional context, making it easier to analyze telemetry data.
```csharp
activity?.SetTag("UserId", userId);
activity?.SetTag("OperationDetail", "Detail about the operation");
```
### Automatic Instrumentation [#automatic-instrumentation]
Automatic instrumentation uses the OpenTelemetry SDK and additional libraries to automatically generate telemetry data for certain operations, such as incoming HTTP requests and database queries.
1. Configure OpenTelemetry SDK. Use the OpenTelemetry SDK to configure automatic instrumentation in your app. This typically involves setting up a `TracerProvider` in your `program.cs` or startup configuration, which automatically captures telemetry data from supported libraries.
```csharp
Sdk.CreateTracerProviderBuilder()
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("https://AXIOM_DOMAIN/v1/traces");
options.Headers = $"Authorization=Bearer API_TOKEN, X-Axiom-Dataset=DATASET_NAME";
})
.Build();
```
2. Install and configure additional OpenTelemetry instrumentation packages as needed, based on the technologies your app uses. For example, to automatically trace SQL database queries, you might add the corresponding database instrumentation package.
3. With automatic instrumentation set up, no further code changes are required for tracing basic operations. The OpenTelemetry SDK and its instrumentation packages handle the creation and management of traces for supported operations.
## Reference [#reference]
### List of OpenTelemetry trace fields [#list-of-opentelemetry-trace-fields]
| Field Category | Field Name | Description |
| ----------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------- |
| **General Trace Information** | | |
| | \_rowId | Unique identifier for each row in the trace data. |
| | \_sysTime | System timestamp when the trace data was recorded. |
| | \_time | Timestamp when the actual event being traced occurred. |
| | trace\_id | Unique identifier for the entire trace. |
| | span\_id | Unique identifier for the span within the trace. |
| | parent\_span\_id | Unique identifier for the parent span within the trace. |
| **HTTP Attributes** | | |
| | attributes.http.request.method | HTTP method used for the request. |
| | attributes.http.response.status\_code | HTTP status code returned in response. |
| | attributes.http.route | Route accessed during the HTTP request. |
| | attributes.url.path | Path component of the URL accessed. |
| | attributes.url.scheme | Scheme component of the URL accessed. |
| | attributes.server.address | Address of the server handling the request. |
| | attributes.server.port | Port number on the server handling the request. |
| **Network Attributes** | | |
| | attributes.network.protocol.version | Version of the network protocol used. |
| **User Agent** | | |
| | attributes.user\_agent.original | Original user agent string, providing client software and OS. |
| **Custom Attributes** | | |
| | attributes.custom\["custom.attribute"] | Custom attribute provided in the trace. |
| | attributes.custom\["dice.rollResult"] | Result of a dice roll operation. |
| | attributes.custom\["operation.success"] | Indicates if the operation was successful. |
| | attributes.custom\["player.name"] | Name of the player in the operation. |
| **Operational Details** | | |
| | duration | Time taken for the operation. |
| | kind | Type of span (for example, server, client, internal). |
| | name | Name of the span. |
| **Resource Attributes** | | |
| | resource.custom.environment | Environment where the trace was captured, for example, development. |
| **Telemetry SDK Attributes** | | |
| | telemetry.sdk.language | Language of the telemetry SDK, for example, dotnet. |
| | telemetry.sdk.name | Name of the telemetry SDK, for example, opentelemetry. |
| | telemetry.sdk.version | Version of the telemetry SDK, for example, 1.7.0. |
| **Service Attributes** | | |
| | service.instance.id | Unique identifier for the instance of the service. |
| | service.name | Name of the service generating the trace, for example, dotnet. |
| | service.version | Version of the service generating the trace, for example, 1.0.0.0. |
| **Scope Attributes** | | |
| | scope.name | Name of the scope for the operation, for example, `OpenTelemetry.Instrumentation.AspNetCore`. |
| | scope.version | Version of the scope, for example, 1.0.0.0. |
### List of imported libraries [#list-of-imported-libraries]
### OpenTelemetry [#opentelemetry]
` `
This is the core SDK for OpenTelemetry in .NET. It provides the foundational tools needed to collect and manage telemetry data within your .NET apps. It’s the base upon which all other OpenTelemetry instrumentation and exporter packages build.
### `OpenTelemetry.Exporter.Console` [#opentelemetryexporterconsole]
` `
This package allows apps to export telemetry data to the console. It’s primarily useful for development and testing purposes, offering a simple way to view the telemetry data your app generates in real time.
### `OpenTelemetry.Exporter.OpenTelemetryProtocol` [#opentelemetryexporteropentelemetryprotocol]
` `
This package enables your app to export telemetry data using the OpenTelemetry Protocol (OTLP) over gRPC or HTTP. It’s vital for sending data to observability platforms that support OTLP, ensuring your telemetry data can be easily analyzed and monitored across different systems.
### `OpenTelemetry.Extensions.Hosting` [#opentelemetryextensionshosting]
` `
Designed for .NET apps, this package integrates OpenTelemetry with the .NET Generic Host. It simplifies the process of configuring and managing the lifecycle of OpenTelemetry resources such as TracerProvider, making it easier to collect telemetry data in apps that use the hosting model.
### `OpenTelemetry.Instrumentation.AspNetCore` [#opentelemetryinstrumentationaspnetcore]
` `
This package is designed for instrumenting ASP.NET Core apps. It automatically collects telemetry data about incoming requests and responses. This is important for monitoring the performance and reliability of web apps and APIs built with ASP.NET Core.
### `OpenTelemetry.Instrumentation.Http` [#opentelemetryinstrumentationhttp]
` `
This package provides automatic instrumentation for HTTP clients in .NET apps. It captures telemetry data about outbound HTTP requests, including details such as request and response headers, duration, success status, and more. It’s key for understanding external dependencies and interactions in your app.
---
# OpenTelemetry using Golang
Source: https://axiom.co/docs/guides/opentelemetry-go
OpenTelemetry offers a [single set of APIs and libraries](https://opentelemetry.io/docs/languages/go/instrumentation/) that standardize how you collect and transfer telemetry data. This guide focuses on setting up OpenTelemetry in a Go app to send traces to Axiom.
## Prerequisites [#prerequisites]
* Go 1.19 or higher: Ensure you have Go version 1.19 or higher installed in your environment.
* Go app: Use your own app written in Go or start with the provided `main.go` sample below.
* [Create an Axiom account](https://app.axiom.co/).
* [Create a dataset in Axiom](/reference/datasets) where you send your data.
* [Create an API token in Axiom](/reference/tokens) with permissions to create, read, update, and delete datasets.
## Installing Dependencies [#installing-dependencies]
First, run the following in your terminal to install the necessary Go packages:
```go
go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
go get go.opentelemetry.io/otel/sdk/resource
go get go.opentelemetry.io/otel/sdk/trace
go get go.opentelemetry.io/otel/semconv/v1.24.0
go get go.opentelemetry.io/otel/trace
go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
go get go.opentelemetry.io/otel/propagation
```
This installs the OpenTelemetry Go SDK, the OTLP (OpenTelemetry Protocol) trace exporter, and other necessary packages for instrumentation and resource definition.
## Initializing a Go module and managing dependencies [#initializing-a-go-module-and-managing-dependencies]
Before installing the OpenTelemetry dependencies, ensure your Go project is properly initialized as a module and all dependencies are correctly managed. This step is important for resolving import issues and managing your project’s dependencies effectively.
### Initialize a Go module [#initialize-a-go-module]
If your project isn’t already initialized as a Go module, run the following command in your project’s root directory. This step creates a `go.mod` file which tracks your project’s dependencies.
```bash
go mod init
```
Replace `` with your project’s name or the GitHub repository path if you plan to push the code to GitHub. For example, `go mod init github.com/yourusername/yourprojectname`.
### Manage dependencies [#manage-dependencies]
After initializing your Go module, tidy up your project’s dependencies. This ensures that your `go.mod` file accurately reflects the packages your project depends on, including the correct versions of the OpenTelemetry libraries you’ll be using.
Run the following command in your project’s root directory:
```bash
go mod tidy
```
This command will download the necessary dependencies and update your `go.mod` and `go.sum` files accordingly. It’s a good practice to run `go mod tidy` after adding new imports to your project or periodically to keep dependencies up to date.
## HTTP server configuration (main.go) [#http-server-configuration-maingo]
`main.go` is the entry point of the app. It invokes `InstallExportPipeline` from `exporter.go` to set up the tracing exporter. It also sets up a basic HTTP server with OpenTelemetry instrumentation to demonstrate how telemetry data can be collected and exported in a simple web app context. It also demonstrates the usage of span links to establish relationships between spans across different traces.
```go
// main.go
package main
import (
"context"
"fmt"
"log"
"math/rand"
"net"
"net/http"
"os"
"os/signal"
"time"
// OpenTelemetry imports for tracing and observability.
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
)
// main function starts the application and handles run function errors.
func main() {
if err := run(); err != nil {
log.Fatalln(err)
}
}
// run sets up signal handling, tracer initialization, and starts an HTTP server.
func run() error {
// Creating a context that listens for the interrupt signal from the OS.
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
// Initializes tracing and returns a function to shut down OpenTelemetry cleanly.
otelShutdown, err := SetupTracer()
if err != nil {
return err
}
defer func() {
if shutdownErr := otelShutdown(ctx); shutdownErr != nil {
log.Printf("failed to shutdown OpenTelemetry: %v", shutdownErr) // Log fatal errors during server shutdown
}
}()
// Configuring the HTTP server settings.
srv := &http.Server{
Addr: ":8080", // Server address
BaseContext: func(_ net.Listener) context.Context { return ctx },
ReadTimeout: 5 * time.Second, // Server read timeout
WriteTimeout: 15 * time.Second, // Server write timeout
Handler: newHTTPHandler(), // HTTP handler
}
// Starting the HTTP server in a new goroutine.
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("HTTP server ListenAndServe: %v", err)
}
}()
// Wait for interrupt signal to gracefully shut down the server with a timeout context.
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() // Ensures cancel function is called on exit
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Fatalf("HTTP server Shutdown: %v", err) // Log fatal errors during server shutdown
}
return nil
}
// newHTTPHandler configures the HTTP routes and integrates OpenTelemetry.
func newHTTPHandler() http.Handler {
mux := http.NewServeMux() // HTTP request multiplexer
// Wrapping the handler function with OpenTelemetry instrumentation.
handleFunc := func(pattern string, handlerFunc func(http.ResponseWriter, *http.Request)) {
handler := otelhttp.WithRouteTag(pattern, http.HandlerFunc(handlerFunc))
mux.Handle(pattern, handler) // Associate pattern with handler
}
// Registering route handlers with OpenTelemetry instrumentation
handleFunc("/rolldice", rolldice)
handleFunc("/roll_with_link", rollWithLink)
handler := otelhttp.NewHandler(mux, "/")
return handler
}
// rolldice handles the /rolldice route by generating a random dice roll.
func rolldice(w http.ResponseWriter, r *http.Request) {
_, span := otel.Tracer("example-tracer").Start(r.Context(), "rolldice")
defer span.End()
// Generating a random dice roll.
randGen := rand.New(rand.NewSource(time.Now().UnixNano()))
roll := 1 + randGen.Intn(6)
// Writing the dice roll to the response.
fmt.Fprintf(w, "Rolled a dice: %d\n", roll)
}
// rollWithLink handles the /roll_with_link route by creating a new span with a link to the parent span.
func rollWithLink(w http.ResponseWriter, r *http.Request) {
ctx, span := otel.Tracer("example-tracer").Start(r.Context(), "roll_with_link")
defer span.End()
/**
* Create a new span for rolldice with a link to the parent span.
* This link helps correlate events that are related but not directly a parent-child relationship.
*/
rollDiceCtx, rollDiceSpan := otel.Tracer("example-tracer").Start(ctx, "rolldice",
trace.WithLinks(trace.Link{
SpanContext: span.SpanContext(),
Attributes: nil,
}),
)
defer rollDiceSpan.End()
// Generating a random dice roll linked to the parent context.
randGen := rand.New(rand.NewSource(time.Now().UnixNano()))
roll := 1 + randGen.Intn(6)
// Writing the linked dice roll to the response.
fmt.Fprintf(w, "Dice roll result (with link): %d\n", roll)
// Use the rollDiceCtx if needed.
_ = rollDiceCtx
}
```
## Exporter configuration (exporter.go) [#exporter-configuration-exportergo]
`exporter.go` is responsible for setting up the OpenTelemetry tracing exporter. It defines the `resource attributes`, `initializes` the `tracer`, and configures the OTLP (OpenTelemetry Protocol) exporter with appropriate endpoints and headers, allowing your app to send telemetry data to Axiom.
```go
package main
import (
"context" // For managing request-scoped values, cancellation signals, and deadlines.
"crypto/tls" // For configuring TLS options, like certificates.
// OpenTelemetry imports for setting up tracing and exporting telemetry data.
"go.opentelemetry.io/otel" // Core OpenTelemetry APIs for managing tracers.
"go.opentelemetry.io/otel/attribute" // For creating and managing trace attributes.
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" // HTTP trace exporter for OpenTelemetry Protocol (OTLP).
"go.opentelemetry.io/otel/propagation" // For managing context propagation formats.
"go.opentelemetry.io/otel/sdk/resource" // For defining resources that describe an entity producing telemetry.
"go.opentelemetry.io/otel/sdk/trace" // For configuring tracing, like sampling and processors.
semconv "go.opentelemetry.io/otel/semconv/v1.24.0" // Semantic conventions for resource attributes.
)
const (
serviceName = "axiom-go-otel" // Name of the service for tracing.
serviceVersion = "0.1.0" // Version of the service.
otlpEndpoint = "AXIOM_DOMAIN" // OTLP collector endpoint.
bearerToken = "Bearer API_TOKEN" // Authorization token.
deploymentEnvironment = "production" // Deployment environment.
)
func SetupTracer() (func(context.Context) error, error) {
ctx := context.Background()
return InstallExportPipeline(ctx) // Setup and return the export pipeline for telemetry data.
}
func Resource() *resource.Resource {
// Defines resource with service name, version, and environment.
return resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String(serviceName),
semconv.ServiceVersionKey.String(serviceVersion),
attribute.String("environment", deploymentEnvironment),
)
}
func InstallExportPipeline(ctx context.Context) (func(context.Context) error, error) {
// Sets up OTLP HTTP exporter with endpoint, headers, and TLS config.
exporter, err := otlptracehttp.New(ctx,
otlptracehttp.WithEndpoint(otlpEndpoint),
otlptracehttp.WithHeaders(map[string]string{
"Authorization": bearerToken,
"X-AXIOM-DATASET": "DATASET_NAME",
}),
otlptracehttp.WithTLSClientConfig(&tls.Config{}),
)
if err != nil {
return nil, err
}
// Configures the tracer provider with the exporter and resource.
tracerProvider := trace.NewTracerProvider(
trace.WithBatcher(exporter),
trace.WithResource(Resource()),
)
otel.SetTracerProvider(tracerProvider)
// Sets global propagator to W3C Trace Context and Baggage.
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))
return tracerProvider.Shutdown, nil // Returns a function to shut down the tracer provider.
}
```
## Run the app [#run-the-app]
To run the app, execute both `exporter.go` and `main.go`. Use the command `go run main.go exporter.go` to start the app. Once your app is running, traces collected by your app are exported to Axiom. The server starts on the specified port, and you can interact with it by sending requests to the `/rolldice` endpoint.
For example, if you are using port `8080`, your app will be accessible locally at `http://localhost:8080/rolldice`. This URL will direct your requests to the `/rolldice` endpoint of your server running on your local machine.
## Observe the telemetry data in Axiom [#observe-the-telemetry-data-in-axiom]
After deploying your app, you can log into your Axiom account to view and analyze the telemetry data. As you interact with your app, traces will be collected and exported to Axiom, where you can monitor and analyze your app’s performance and behavior.
## Dynamic OpenTelemetry traces dashboard [#dynamic-opentelemetry-traces-dashboard]
This data can then be further viewed and analyzed in Axiom’s dashboard, providing insights into the performance and behavior of your app.
## Send data from an existing Golang project [#send-data-from-an-existing-golang-project]
### Manual Instrumentation [#manual-instrumentation]
Manual instrumentation in Go involves managing spans within your code to track operations and events. This method offers precise control over what is instrumented and how spans are configured.
1. Initialize the tracer:
Use the OpenTelemetry API to obtain a tracer instance. This tracer will be used to start and manage spans.
```go
tracer := otel.Tracer("serviceName")
```
2. Create and manage spans:
Manually start spans before the operations you want to trace and ensure they're ended after the operations complete.
```go
ctx, span := tracer.Start(context.Background(), "operationName")
defer span.End()
// Perform the operation here
```
3. Annotate spans:
Enhance spans with additional information using attributes or events to provide more context about the traced operation.
```go
span.SetAttributes(attribute.String("key", "value"))
span.AddEvent("eventName", trace.WithAttributes(attribute.String("key", "value")))
```
### Automatic Instrumentation [#automatic-instrumentation]
Automatic instrumentation in Go uses libraries and integrations that automatically create spans for operations, simplifying the addition of observability to your app.
1. Instrumentation libraries:
Use `OpenTelemetry-contrib` libraries designed for automatic instrumentation of standard Go frameworks and libraries, such as `net/http`.
```go
import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
```
2. Wrap handlers and clients:
Automatically instrument HTTP servers and clients by wrapping them with OpenTelemetry’s instrumentation. For HTTP servers, wrap your handlers with `otelhttp.NewHandler`.
```go
http.Handle("/path", otelhttp.NewHandler(handler, "operationName"))
```
3. Minimal code changes:
After setting up automatic instrumentation, no further changes are required for tracing standard operations. The instrumentation takes care of starting, managing, and ending spans.
## Reference [#reference]
### List of OpenTelemetry trace fields [#list-of-opentelemetry-trace-fields]
| Field Category | Field Name | Description |
| ---------------------------- | --------------------------------------- | ------------------------------------------------------------------- |
| **Unique Identifiers** | | |
| | \_rowid | Unique identifier for each row in the trace data. |
| | span\_id | Unique identifier for the span within the trace. |
| | trace\_id | Unique identifier for the entire trace. |
| **Timestamps** | | |
| | \_systime | System timestamp when the trace data was recorded. |
| | \_time | Timestamp when the actual event being traced occurred. |
| **HTTP Attributes** | | |
| | attributes.custom\["http.host"] | Host information where the HTTP request was sent. |
| | attributes.custom\["http.server\_name"] | Server name for the HTTP request. |
| | attributes.http.flavor | HTTP protocol version used. |
| | attributes.http.method | HTTP method used for the request. |
| | attributes.http.route | Route accessed during the HTTP request. |
| | attributes.http.scheme | Protocol scheme (HTTP/HTTPS). |
| | attributes.http.status\_code | HTTP response status code. |
| | attributes.http.target | Specific target of the HTTP request. |
| | attributes.http.user\_agent | User agent string of the client. |
| | attributes.custom.user\_agent.original | Original user agent string, providing client software and OS. |
| **Network Attributes** | | |
| | attributes.net.host.port | Port number on the host receiving the request. |
| | attributes.net.peer.port | Port number on the peer (client) side. |
| | attributes.custom\["net.peer.ip"] | IP address of the peer in the network interaction. |
| | attributes.net.sock.peer.addr | Socket peer address, indicating the IP version used. |
| | attributes.net.sock.peer.port | Socket peer port number. |
| | attributes.custom.net.protocol.version | Protocol version used in the network interaction. |
| **Operational Details** | | |
| | duration | Time taken for the operation. |
| | kind | Type of span (for example,, server, client). |
| | name | Name of the span. |
| | scope | Instrumentation scope. |
| | service.name | Name of the service generating the trace. |
| | service.version | Version of the service generating the trace. |
| **Resource Attributes** | | |
| | resource.environment | Environment where the trace was captured, for example,, production. |
| | attributes.custom.http.wrote\_bytes | Number of bytes written in the HTTP response. |
| **Telemetry SDK Attributes** | | |
| | telemetry.sdk.language | Language of the telemetry SDK (if previously not included). |
| | telemetry.sdk.name | Name of the telemetry SDK (if previously not included). |
| | telemetry.sdk.version | Version of the telemetry SDK (if previously not included). |
### List of imported libraries [#list-of-imported-libraries]
### OpenTelemetry Go SDK [#opentelemetry-go-sdk]
**`go.opentelemetry.io/otel`**
This is the core SDK for OpenTelemetry in Go. It provides the necessary tools to create and manage telemetry data (traces, metrics, and logs).
### OTLP Trace Exporter [#otlp-trace-exporter]
**`go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`**
This package allows your app to export telemetry data over HTTP using the OpenTelemetry Protocol (OTLP). It’s important for sending data to Axiom or any other backend that supports OTLP.
### Resource and Trace Packages [#resource-and-trace-packages]
**`go.opentelemetry.io/otel/sdk/resource`** and **`go.opentelemetry.io/otel/sdk/trace`**
These packages help define the properties of your telemetry data, such as service name and version, and manage trace data within your app.
### Semantic Conventions [#semantic-conventions]
**`go.opentelemetry.io/otel/semconv/v1.24.0`**
This package provides standardized schema URLs and attributes, ensuring consistency across different OpenTelemetry implementations.
### Tracing API [#tracing-api]
**`go.opentelemetry.io/otel/trace`**
This package offers the API for tracing. It enables you to create spans, record events, and manage context propagation in your app.
### HTTP Instrumentation [#http-instrumentation]
**`go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`**
Used for instrumenting HTTP clients and servers. It automatically records data about HTTP requests and responses, which is essential for web apps.
### Propagators [#propagators]
**`go.opentelemetry.io/otel/propagation`**
This package provides the ability to propagate context and trace information across service boundaries.
---
# Send data from Java app using OpenTelemetry
Source: https://axiom.co/docs/guides/opentelemetry-java
OpenTelemetry provides a unified approach to collecting telemetry data from your Java applications. This page demonstrates how to configure OpenTelemetry in a Java app to send telemetry data to Axiom using the OpenTelemetry SDK.
* [Install JDK 11](https://www.oracle.com/java/technologies/java-se-glance.html) or later
* [Install Maven](https://maven.apache.org/download.cgi)
* Use your own app written in Java or the provided `DiceRollerApp.java` sample.
## Create project [#create-project]
To create a Java project, run the Maven archetype command in the terminal:
```bash
mvn archetype:generate -DgroupId=com.example -DartifactId=MyProject -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
```
This command creates a new project in a directory named `MyProject` with a standard directory structure.
## Create core app [#create-core-app]
`DiceRollerApp.java` is the core of the sample app. It simulates rolling a dice and demonstrates the usage of OpenTelemetry for tracing. The app includes two methods: one for a simple dice roll and another that demonstrates the usage of span links to establish relationships between spans across different traces.
Create the `DiceRollerApp.java` in the `src/main/java/com/example` directory with the following content:
```java
package com.example;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import java.util.Random;
public class DiceRollerApp {
private static final Tracer tracer;
static {
OpenTelemetry openTelemetry = OtelConfiguration.initializeOpenTelemetry();
tracer = openTelemetry.getTracer(DiceRollerApp.class.getName());
}
public static void main(String[] args) {
rollDice();
rollDiceWithLink();
}
private static void rollDice() {
Span span = tracer.spanBuilder("rollDice").startSpan();
try (Scope scope = span.makeCurrent()) {
int roll = 1 + new Random().nextInt(6);
System.out.println("Rolled a dice: " + roll);
} finally {
span.end();
}
}
private static void rollDiceWithLink() {
Span parentSpan = tracer.spanBuilder("rollWithLink").startSpan();
try (Scope parentScope = parentSpan.makeCurrent()) {
Span childSpan = tracer.spanBuilder("rolldice")
.addLink(parentSpan.getSpanContext())
.startSpan();
try (Scope childScope = childSpan.makeCurrent()) {
int roll = 1 + new Random().nextInt(6);
System.out.println("Dice roll result (with link): " + roll);
} finally {
childSpan.end();
}
} finally {
parentSpan.end();
}
}
}
```
## Configure OpenTelemetry [#configure-opentelemetry]
`OtelConfiguration.java` sets up the OpenTelemetry SDK and configures the exporter to send data to Axiom. It initializes the tracer provider, sets up the Axiom exporter, and configures the resource attributes.
Create the `OtelConfiguration.java` file in the `src/main/java/com/example` directory with the following content:
```java
package com.example;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import java.util.concurrent.TimeUnit;
public class OtelConfiguration {
private static final String SERVICE_NAME = "YOUR_SERVICE_NAME";
private static final String SERVICE_VERSION = "YOUR_SERVICE_VERSION";
private static final String OTLP_ENDPOINT = "https://AXIOM_DOMAIN/v1/traces";
private static final String BEARER_TOKEN = "Bearer API_TOKEN";
private static final String AXIOM_DATASET = "DATASET_NAME";
public static OpenTelemetry initializeOpenTelemetry() {
Resource resource = Resource.getDefault()
.merge(Resource.create(Attributes.of(
AttributeKey.stringKey("service.name"), SERVICE_NAME,
AttributeKey.stringKey("service.version"), SERVICE_VERSION
)));
OtlpHttpSpanExporter spanExporter = OtlpHttpSpanExporter.builder()
.setEndpoint(OTLP_ENDPOINT)
.addHeader("Authorization", BEARER_TOKEN)
.addHeader("X-Axiom-Dataset", AXIOM_DATASET)
.build();
SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter)
.setScheduleDelay(100, TimeUnit.MILLISECONDS)
.build())
.setResource(resource)
.build();
OpenTelemetrySdk openTelemetry = OpenTelemetrySdk.builder()
.setTracerProvider(sdkTracerProvider)
.buildAndRegisterGlobal();
Runtime.getRuntime().addShutdownHook(new Thread(sdkTracerProvider::close));
return openTelemetry;
}
}
```
## Configure project [#configure-project]
The `pom.xml` file defines the project structure and dependencies for Maven. It includes the necessary OpenTelemetry libraries and configures the build process.
Update the `pom.xml` file in the root of your project directory with the following content:
```xml
4.0.0
com.example
axiom-otel-java
1.0-SNAPSHOT
UTF-8
11
11
1.18.0
io.opentelemetry
opentelemetry-api
${opentelemetry.version}
io.opentelemetry
opentelemetry-sdk
${opentelemetry.version}
io.opentelemetry
opentelemetry-exporter-otlp
${opentelemetry.version}
junit
junit
4.13.2
test
org.apache.maven.plugins
maven-compiler-plugin
3.8.1
11
11
org.apache.maven.plugins
maven-surefire-plugin
3.0.0-M5
true
org.apache.maven.plugins
maven-shade-plugin
3.2.4
package
shade
com.example.DiceRollerApp
```
## Run the instrumented app [#run-the-instrumented-app]
To run your Java app with OpenTelemetry instrumentation, follow these steps:
1. Clean the project and download dependencies:
```bash
mvn clean
```
2. Compile the code:
```bash
mvn compile
```
3. Package the app:
```bash
mvn package
```
4. Run the app:
```bash
java -jar target/axiom-otel-java-1.0-SNAPSHOT.jar
```
The app executes the `rollDice()` and `rollDiceWithLink()` methods, generates telemetry data, and sends the data to Axiom.
## Observe telemetry data in Axiom [#observe-telemetry-data-in-axiom]
As the app runs, it sends traces to Axiom. To view the traces:
1. In Axiom, click the **Stream** tab.
2. Click your dataset.
Axiom provides a dynamic dashboard for visualizing and analyzing your OpenTelemetry traces. This dashboard offers insights into the performance and behavior of your app. To view the dashboard:
1. In Axiom, click the **Dashboards** tab.
2. Look for the OpenTelemetry traces dashboard or create a new one.
3. Customize the dashboard to show the event data and visualizations most relevant to the app.
## Send data from an existing Java project [#send-data-from-an-existing-java-project]
### Manual instrumentation [#manual-instrumentation]
Manual instrumentation gives fine-grained control over which parts of the app are traced and what information is included in the traces. It requires adding OpenTelemetry-specific code to the app.
Set up OpenTelemetry. Create a configuration class to initialize OpenTelemetry with necessary settings, exporters, and span processors.
```java
// OtelConfiguration.java
package com.example;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
public class OtelConfiguration {
public static OpenTelemetry initializeOpenTelemetry() {
OtlpHttpSpanExporter spanExporter = OtlpHttpSpanExporter.builder()
.setEndpoint("https://AXIOM_DOMAIN/v1/traces")
.addHeader("Authorization", "Bearer API_TOKEN")
.addHeader("X-Axiom-Dataset", "DATASET_NAME")
.build();
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
.build();
return OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.buildAndRegisterGlobal();
}
}
```
Spans represent units of work in the app. They have a start time and duration and can be nested.
```java
// DiceRollerApp.java
package com.example;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
public class DiceRollerApp {
private static final Tracer tracer;
static {
OpenTelemetry openTelemetry = OtelConfiguration.initializeOpenTelemetry();
tracer = openTelemetry.getTracer("com.example.DiceRollerApp");
}
public static void main(String[] args) {
try (Scope scope = tracer.spanBuilder("Main").startScopedSpan()) {
rollDice();
}
}
private static void rollDice() {
Span span = tracer.spanBuilder("rollDice").startSpan();
try (Scope scope = span.makeCurrent()) {
// Simulate dice roll
int result = new Random().nextInt(6) + 1;
System.out.println("Rolled a dice: " + result);
} finally {
span.end();
}
}
}
```
Custom spans are manually managed to provide detailed insights into specific functions or methods within the app.
Spans can be annotated with attributes and events to provide more context about the operation being performed.
```java
private static void rollDice() {
Span span = tracer.spanBuilder("rollDice").startSpan();
try (Scope scope = span.makeCurrent()) {
int roll = 1 + new Random().nextInt(6);
span.setAttribute("roll.value", roll);
span.addEvent("Dice rolled");
System.out.println("Rolled a dice: " + roll);
} finally {
span.end();
}
}
```
Span links allow association of spans that aren’t in a parent-child relationship.
```java
private static void rollDiceWithLink() {
Span parentSpan = tracer.spanBuilder("rollWithLink").startSpan();
try (Scope parentScope = parentSpan.makeCurrent()) {
Span childSpan = tracer.spanBuilder("rolldice")
.addLink(parentSpan.getSpanContext())
.startSpan();
try (Scope childScope = childSpan.makeCurrent()) {
int roll = 1 + new Random().nextInt(6);
System.out.println("Dice roll result (with link): " + roll);
} finally {
childSpan.end();
}
} finally {
parentSpan.end();
}
}
```
### Automatic instrumentation [#automatic-instrumentation]
Automatic instrumentation simplifies adding telemetry to a Java app by automatically capturing data from supported libraries and frameworks.
Ensure all necessary OpenTelemetry libraries are included in your Maven `pom.xml`.
```xml
io.opentelemetry
opentelemetry-api
{opentelemetry_version}
io.opentelemetry
opentelemetry-sdk
{opentelemetry_version}
io.opentelemetry.instrumentation
opentelemetry-instrumentation-httpclient
{instrumentation_version}
```
Dependencies include the OpenTelemetry SDK and instrumentation libraries that automatically capture data from common Java libraries.
Implement an initialization class to configure the OpenTelemetry SDK along with auto-instrumentation for frameworks used by the app.
```java
// AutoInstrumentationSetup.java
package com.example;
import io.opentelemetry.instrumentation.httpclient.HttpClientInstrumentation;
import io.opentelemetry.api.OpenTelemetry;
public class AutoInstrumentationSetup {
public static void setup() {
OpenTelemetry openTelemetry = OtelConfiguration.initializeOpenTelemetry();
HttpClientInstrumentation.instrument(openTelemetry);
}
}
```
Auto-instrumentation is initialized early in the app lifecycle to ensure all relevant activities are automatically captured.
```java
// Main.java
package com.example;
public class Main {
public static void main(String[] args) {
AutoInstrumentationSetup.setup(); // Initialize OpenTelemetry auto-instrumentation
DiceRollerApp.main(args); // Start the application logic
}
}
```
## Reference [#reference]
### List of OpenTelemetry trace fields [#list-of-opentelemetry-trace-fields]
| Field category | Field name | Description |
| ------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------- |
| General trace information | | |
| | \_rowId | Unique identifier for each row in the trace data. |
| | \_sysTime | System timestamp when the trace data was recorded. |
| | \_time | Timestamp when the actual event being traced occurred. |
| | trace\_id | Unique identifier for the entire trace. |
| | span\_id | Unique identifier for the span within the trace. |
| | parent\_span\_id | Unique identifier for the parent span within the trace. |
| Operational details | | |
| | duration | Time taken for the operation, typically in microseconds or milliseconds. |
| | kind | Type of span. For example, `server`, `internal`. |
| | name | Name of the span, often a high-level title for the operation. |
| Scope and instrumentation | | |
| | scope.name | Instrumentation scope, typically the Java package or app component. For example, `com.example.DiceRollerApp`. |
| Service attributes | | |
| | service.name | Name of the service generating the trace. For example, `axiom-java-otel`. |
| | service.version | Version of the service generating the trace. For example, `0.1.0`. |
| Telemetry SDK attributes | | |
| | telemetry.sdk.language | Programming language of the SDK used for telemetry, typically `java`. |
| | telemetry.sdk.name | Name of the telemetry SDK. For example, `opentelemetry`. |
| | telemetry.sdk.version | Version of the telemetry SDK used in the tracing setup. For example, `1.18.0`. |
### List of imported libraries [#list-of-imported-libraries]
The Java implementation of OpenTelemetry uses the following key libraries.
`io.opentelemetry:opentelemetry-api`
This package provides the core OpenTelemetry API for Java. It defines the interfaces and classes that developers use to instrument their apps manually. This includes the `Tracer`, `Span`, and `Context` classes, which are fundamental to creating and managing traces in your app. The API is designed to be stable and consistent, allowing developers to instrument their code without tying it to a specific implementation.
`io.opentelemetry:opentelemetry-sdk`
The opentelemetry-sdk package is the reference implementation of the OpenTelemetry API for Java. It provides the actual capability behind the API interfaces, including span creation, context propagation, and resource management. This SDK is highly configurable and extensible, allowing developers to customize how telemetry data is collected, processed, and exported. It’s the core component that brings OpenTelemetry to life in a Java app.
`io.opentelemetry:opentelemetry-exporter-otlp`
This package provides an exporter that sends telemetry data using the OpenTelemetry Protocol (OTLP). OTLP is the standard protocol for transmitting telemetry data in the OpenTelemetry ecosystem. This exporter allows Java applications to send their collected traces, metrics, and logs to any backend that supports OTLP, such as Axiom. The use of OTLP ensures broad compatibility and a standardized way of transmitting telemetry data across different systems and platforms.
`io.opentelemetry:opentelemetry-sdk-extension-autoconfigure`
This extension package provides auto-configuration capabilities for the OpenTelemetry SDK. It allows developers to configure the SDK using environment variables or system properties, making it easier to set up and deploy OpenTelemetry-instrumented applications in different environments. This is particularly useful for containerized applications or those running in cloud environments where configuration through environment variables is common.
`io.opentelemetry:opentelemetry-sdk-trace`
This package is part of the OpenTelemetry SDK and focuses specifically on tracing capability. It includes important classes like `SdkTracerProvider` and `BatchSpanProcessor`. The `SdkTracerProvider` is responsible for creating and managing tracers, while the `BatchSpanProcessor` efficiently processes and exports spans in batches, similar to its Node.js counterpart. This batching mechanism helps optimize the performance of trace data export in OpenTelemetry-instrumented Java applications.
`io.opentelemetry:opentelemetry-sdk-common`
This package provides common capability used across different parts of the OpenTelemetry SDK. It includes utilities for working with attributes, resources, and other shared concepts in OpenTelemetry. This package helps ensure consistency across the SDK and simplifies the implementation of cross-cutting concerns in telemetry data collection and processing.
---
# OpenTelemetry using Next.js
Source: https://axiom.co/docs/guides/opentelemetry-nextjs
OpenTelemetry provides a standardized way to collect and export telemetry data from your Next.js apps. This guide walks you through the process of configuring OpenTelemetry in a Next.js app to send traces to Axiom using the OpenTelemetry SDK.
## Prerequisites [#prerequisites]
* [Create an Axiom account](https://app.axiom.co/).
* [Create a dataset in Axiom](/reference/datasets) where you send your data.
* [Create an API token in Axiom](/reference/tokens) with permissions to create, read, update, and delete datasets.
* [Install Node.js version 14](https://nodejs.org/en/download/package-manager) or newer.
* An existing Next.js app. Alternatively, create a new app with the default settings using the [Next.js documentation](https://nextjs.org/learn/pages-router/create-nextjs-app-setup).
## Send data from new project [#send-data-from-new-project]
### Initial setup [#initial-setup]
1. Create a new app with the default settings using the [Next.js documentation](https://nextjs.org/learn/pages-router/create-nextjs-app-setup).
2. Run the following command to install the dependencies:
```bash
npm install @opentelemetry/exporter-trace-otlp-http @opentelemetry/sdk-trace-node @opentelemetry/resources
```
3. Create an `instrumentation.ts` file in the `src` folder of your project with the following content:
```ts /src/instrumentation.ts
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-node";
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
export function register() {
const provider = new NodeTracerProvider({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: "nextjs-otel-example",
}, {
// Use the latest schema version
// Info: https://opentelemetry.io/docs/specs/semconv/
schemaUrl: 'https://opentelemetry.io/schemas/1.37.0',
}),
spanProcessors: [new SimpleSpanProcessor(
new OTLPTraceExporter({
url: `https://${process.env.AXIOM_HOST}/v1/traces`,
headers: {
Authorization: `Bearer ${process.env.AXIOM_TOKEN}`,
"X-Axiom-Dataset": `${process.env.AXIOM_DATASET}`,
},
})
)],
});
provider.register();
}
```
4. Add the `AXIOM_DOMAIN`, `API_TOKEN`, and `DATASET_NAME` environment variables to your `.env` file. For example:
```bash
AXIOM_HOST="AXIOM_DOMAIN"
AXIOM_TOKEN="API_TOKEN"
AXIOM_DATASET="DATASET_NAME"
```
### Update root layout [#update-root-layout]
In the `/src/app/layout.tsx` file, import and call the `register` function from the `instrumentation` module:
```tsx /src/app/layout.tsx
import { register } from '../instrumentation';
register();
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
This file sets up the root layout for your Next.js app and initializes the OpenTelemetry instrumentation by calling the `register` function.
### Update compiler options [#update-compiler-options]
Add the following options to your `tsconfig.json` file to ensure compatibility with OpenTelemetry and Next.js:
```json /tsconfig.json
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
```
This file configures the TypeScript compiler options for your Next.js app.
### Observe traces in Axiom [#observe-traces-in-axiom]
Use the following command to run your Next.js app with OpenTelemetry instrumentation in development mode:
```bash
npm run dev
```
This command starts the Next.js development server, and the OpenTelemetry instrumentation automatically collects traces. As you interact with your app, traces are sent to Axiom where you can monitor and analyze your app’s performance and behavior.
In Axiom, go to the **Stream** tab and click your dataset. This page displays the traces sent to Axiom and lets you monitor and analyze your app’s performance and behavior.
Go to the **Dashboards** tab and click **OpenTelemetry Traces**. This pre-built traces dashboard provides further insights into the performance and behavior of your app.
## Send data from existing project [#send-data-from-existing-project]
### Manual instrumentation [#manual-instrumentation]
Manual instrumentation allows you to create, configure, and manage spans and traces, providing detailed control over telemetry data collection at specific points within the app.
1. Set up and retrieve a tracer from the OpenTelemetry API. This tracer starts and manages spans within your app components or API routes.
```js
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('nextjs-app');
```
2. Manually start a span at the beginning of significant operations or transactions within your Next.js app and ensure you end it appropriately. This approach is for tracing specific custom events or operations not automatically captured by instrumentations.
```js
const span = tracer.startSpan('operationName');
try {
// Perform your operation here
} finally {
span.end();
}
```
3. Enhance the span with additional information such as user details or operation outcomes, which can provide deeper insights when analyzing telemetry data.
```js
span.setAttribute('user_id', userId);
span.setAttribute('operation_status', 'success');
```
### Automatic instrumentation [#automatic-instrumentation]
Automatic instrumentation uses the capabilities of OpenTelemetry to automatically capture telemetry data for standard operations such as HTTP requests and responses.
1. Use the OpenTelemetry Node SDK to configure your app to automatically instrument supported libraries and frameworks. Set up `NodeSDK` in an `instrumentation.ts` file in your project.
```ts /src/instrumentation.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
export function register() {
const sdk = new NodeSDK({
resource: new Resource({ [SEM_RESOURCE_ATTRIBUTES.SERVICE_NAME]: 'nextjs-app' }),
spanProcessor: new BatchSpanProcessor(
new OTLPTraceExporter({
url: `https://${process.env.AXIOM_HOST}/v1/traces`,
headers: {
Authorization: `Bearer ${process.env.AXIOM_TOKEN}`,
'X-Axiom-Dataset': `${process.env.AXIOM_DATASET}`,
},
})
),
});
sdk.start();
}
```
2. Include necessary OpenTelemetry instrumentation packages to automatically capture telemetry from Node.js libraries like HTTP and any other middlewares used by Next.js.
3. Call the `register` function from the `instrumentation.ts` within your app startup file or before your app starts handling traffic to initialize the OpenTelemetry instrumentation.
```js
// In pages/_app.js or an equivalent entry point
import { register } from '../instrumentation';
register();
```
## Reference [#reference]
### List of OpenTelemetry trace fields [#list-of-opentelemetry-trace-fields]
| Field Category | Field Name | Description |
| --------------------------- | ------------------------------------- | ------------------------------------------------------------------ |
| General Trace Information | | |
| | \_rowId | Unique identifier for each row in the trace data. |
| | \_sysTime | System timestamp when the trace data was recorded. |
| | \_time | Timestamp when the actual event being traced occurred. |
| | trace\_id | Unique identifier for the entire trace. |
| | span\_id | Unique identifier for the span within the trace. |
| | parent\_span\_id | Unique identifier for the parent span within the trace. |
| HTTP Attributes | | |
| | attributes.http.method | HTTP method used for the request. |
| | attributes.http.status\_code | HTTP status code returned in response. |
| | attributes.http.route | Route accessed during the HTTP request. |
| | attributes.http.target | Specific target of the HTTP request. |
| Custom Attributes | | |
| | attributes.custom\["next.route"] | Custom attribute defining the Next.js route. |
| | attributes.custom\["next.rsc"] | Indicates if React Server Components are used. |
| | attributes.custom\["next.span\_name"] | Custom name of the span within Next.js context. |
| | attributes.custom\["next.span\_type"] | Type of the Next.js span, describing the operation context. |
| Resource Process Attributes | | |
| | resource.process.pid | Process ID of the Node.js app. |
| | resource.process.runtime.description | Description of the runtime environment. For example, Node.js. |
| | resource.process.runtime.name | Name of the runtime environment. For example, nodejs. |
| | resource.process.runtime.version | Version of the runtime environment For example, 18.17.0. |
| | resource.process.executable.name | Executable name running the process. For example, next-server. |
| Resource Host Attributes | | |
| | resource.host.arch | Architecture of the host machine. For example, arm64. |
| | resource.host.name | Name of the host machine. For example, MacBook-Pro.local. |
| Operational Details | | |
| | duration | Time taken for the operation. |
| | kind | Type of span (for example, server, internal). |
| | name | Name of the span, often a high-level title for the operation. |
| Scope Attributes | | |
| | scope.name | Name of the scope for the operation. For example, next.js. |
| | scope.version | Version of the scope. For example, 0.0.1. |
| Service Attributes | | |
| | service.name | Name of the service generating the trace. For example, nextjs-app. |
| Telemetry SDK Attributes | | |
| | telemetry.sdk.language | Language of the telemetry SDK. For example, nodejs. |
| | telemetry.sdk.name | Name of the telemetry SDK. For example, opentelemetry. |
| | telemetry.sdk.version | Version of the telemetry SDK. For example, 1.23.0. |
### List of imported libraries [#list-of-imported-libraries]
`@opentelemetry/api`
The core API for OpenTelemetry in JavaScript, providing the necessary interfaces and utilities for tracing, metrics, and context propagation. In the context of Next.js, it allows developers to manually instrument custom spans, manipulate context, and access the active span if needed.
`@opentelemetry/exporter-trace-otlp-http`
This exporter enables your Next.js app to send trace data over HTTP to any backend that supports the OTLP (OpenTelemetry Protocol), such as Axiom. Using OTLP ensures compatibility with a wide range of observability tools and standardizes the data export process.
`@opentelemetry/resources`
This defines the Resource which represents the entity producing telemetry. In Next.js, Resources can be used to describe the app (for example, service name, version) and are attached to all exported telemetry, aiding in identifying data in backend systems.
`@opentelemetry/sdk-node`
The OpenTelemetry SDK for Node.js which provides a comprehensive set of tools for instrumenting Node.js apps. It includes automatic instrumentation for popular libraries and frameworks, as well as APIs for manual instrumentation. In the Next.js setup, it’s used to configure and initialize the OpenTelemetry SDK.
`@opentelemetry/sdk-trace-node`
This package provides the Node.js-specific implementation of the OpenTelemetry Tracing SDK. It includes the core components needed to create and manage spans, as well as utilities for automatic and manual instrumentation in Node.js environments. In a Next.js app, it works alongside `@opentelemetry/sdk-node` to give finer control over the tracing pipeline. For example, configuring span processors, sampling strategies, or custom span exporters directly for trace data. It’s useful when you need more granular control over tracing behavior beyond the default SDK configuration.
`@opentelemetry/semantic-conventions`
A set of standard attributes and conventions for describing resources, spans, and metrics in OpenTelemetry. By adhering to these conventions, your Next.js app’s telemetry data becomes more consistent and interoperable with other OpenTelemetry-compatible tools and systems.
---
# OpenTelemetry using Node.js
Source: https://axiom.co/docs/guides/opentelemetry-nodejs
OpenTelemetry provides a [unified approach to collecting telemetry data](https://opentelemetry.io/docs/languages/js/instrumentation/) from your Node.js and TypeScript apps. This guide demonstrates how to configure OpenTelemetry in a Node.js app to send telemetry data to Axiom using OpenTelemetry SDK.
## Prerequisites [#prerequisites]
To configure OpenTelemetry in a Node.js app for sending telemetry data to Axiom, certain prerequisites are necessary. These include:
* Node:js: Node.js version 14 or newer.
* Node.js app: Use your own app written in Node.js, or you can start with the provided **`app.ts`** sample.
* [Create an Axiom account](https://app.axiom.co/).
* [Create a dataset in Axiom](/reference/datasets) where you send your data.
* [Create an API token in Axiom](/reference/tokens) with permissions to create, read, update, and delete datasets.
## Core Application (app.ts) [#core-application-appts]
`app.ts` is the core of the app. It uses Express.js to create a simple web server. The server has an endpoint `/rolldice` that returns a random number, simulating a basic API. It also demonstrates the usage of span links to establish relationships between spans across different traces.
```js
/*app.ts*/
// Importing OpenTelemetry instrumentation for tracing
import './instrumentation';
import { trace, context } from '@opentelemetry/api';
// Importing Express.js: A minimal and flexible Node.js web app framework
import express from 'express';
// Setting up the server port: Use the PORT environment variable or default to 8080
const PORT = parseInt(process.env.PORT || '8080');
const app = express();
// Get the tracer from the global tracer provider
const tracer = trace.getTracer('node-traces');
/**
* Function to generate a random number between min and max (inclusive).
* @param min - The minimum number (inclusive).
* @param max - The maximum number (exclusive).
* @returns A random number between min and max.
*/
function getRandomNumber(min: number, max: number): number {
return Math.floor(Math.random() * (max - min) + min);
}
// Defining a route handler for '/rolldice' that returns a random dice roll
app.get('/rolldice', (req, res) => {
const span = trace.getSpan(context.active());
/**
* Spans can be created with zero or more Links to other Spans that are related.
* Links allow creating connections between different traces
*/
const rollDiceSpan = tracer.startSpan('roll_dice_span', {
links: span ? [{ context: span.spanContext() }] : [],
});
// Set the rollDiceSpan as the currently active span
context.with(trace.setSpan(context.active(), rollDiceSpan), () => {
const diceRoll = getRandomNumber(1, 6).toString();
res.send(diceRoll);
rollDiceSpan.end();
});
});
// Defining a route handler for '/roll_with_link' that creates a parent span and calls '/rolldice'
app.get('/roll_with_link', (req, res) => {
/**
* A common scenario is to correlate one or more traces with the current span.
* This can help in tracing and debugging complex interactions across different parts of the app.
*/
const parentSpan = tracer.startSpan('parent_span');
// Set the parentSpan as the currently active span
context.with(trace.setSpan(context.active(), parentSpan), () => {
const diceRoll = getRandomNumber(1, 6).toString();
res.send(`Dice roll result (with link): ${diceRoll}`);
parentSpan.end();
});
});
// Starting the server on the specified PORT and logging the listening message
app.listen(PORT, () => {
console.log(`Listening for requests on http://localhost:${PORT}`);
});
```
## Exporter (instrumentation.ts) [#exporter-instrumentationts]
`instrumentation.ts` sets up the OpenTelemetry instrumentation. It configures the OTLP (OpenTelemetry Protocol) exporters for traces and initializes the Node SDK with automatic instrumentation capabilities.
```js
/*instrumentation.ts*/
// Importing necessary OpenTelemetry packages including the core SDK, auto-instrumentations, OTLP trace exporter, and batch span processor
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
// Initialize OTLP trace exporter with the endpoint URL and headers
const traceExporter = new OTLPTraceExporter({
url: 'https://AXIOM_DOMAIN/v1/traces',
headers: {
'Authorization': 'Bearer API_TOKEN',
'X-Axiom-Dataset': 'DATASET_NAME'
},
});
// Creating a resource to identify your service in traces
const resource = new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'node traces',
});
// Configuring the OpenTelemetry Node SDK
const sdk = new NodeSDK({
// Adding a BatchSpanProcessor to batch and send traces
spanProcessor: new BatchSpanProcessor(traceExporter),
// Registering the resource to the SDK
resource: resource,
// Adding auto-instrumentations to automatically collect trace data
instrumentations: [getNodeAutoInstrumentations()],
});
// Starting the OpenTelemetry SDK to begin collecting telemetry data
sdk.start();
```
## Installing the Dependencies [#installing-the-dependencies]
Navigate to the root directory of your project and run the following command to install the required dependencies:
```bash
npm install
```
This command will install all the necessary packages listed in your `package.json` [below](/guides/opentelemetry-nodejs#setting-up-typescript-development-environment)
## Setting Up TypeScript Development Environment [#setting-up-typescript-development-environment]
To run the TypeScript app, you need to set up a TypeScript development environment. This includes adding a `package.json` file to manage your project’s dependencies and scripts, and a `tsconfig.json` file to manage TypeScript compiler options.
### Add `package.json` [#add-packagejson]
Create a `package.json` file in the root of your project with the following content:
```json
{
"name": "typescript-traces",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"build": "tsc",
"start": "ts-node app.ts",
"dev": "ts-node-dev --respawn app.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@opentelemetry/api": "^1.6.0",
"@opentelemetry/api-logs": "^0.46.0",
"@opentelemetry/auto-instrumentations-node": "^0.39.4",
"@opentelemetry/exporter-metrics-otlp-http": "^0.45.0",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.45.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.45.0",
"@opentelemetry/sdk-logs": "^0.46.0",
"@opentelemetry/sdk-metrics": "^1.20.0",
"@opentelemetry/sdk-node": "^0.45.1",
"express": "^4.18.2"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^16.18.71",
"ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0",
"tsc-watch": "^4.6.2",
"typescript": "^4.9.5"
}
}
```
### Add `tsconfig.json` [#add-tsconfigjson]
Create a `tsconfig.json` file in the root of your project with the following content:
```json
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
```
This configuration file specifies how the TypeScript compiler should transpile TypeScript files into JavaScript.
## Running the Instrumented Application [#running-the-instrumented-application]
To run your Node.js app with OpenTelemetry instrumentation, make sure your API token, and dataset is set in the `instrumentation.ts` file.
### In Development Mode [#in-development-mode]
For development purposes, especially when you need automatic restarts upon file changes, use:
```bash
npm run dev
```
This command will start the OpenTelemetry instrumentation in development mode using `ts-node-dev`. It sets up the exporter for tracing and restarts the server automatically whenever you make changes to the files.
### In Production Mode [#in-production-mode]
To run the app in production mode, you need to first build the TypeScript files into JavaScript. Run the following command to build your app:
```bash
npm run build
```
This command compiles the TypeScript files to JavaScript based on the settings specified in `tsconfig.json`. Once the build process is complete, you can start your app in production mode with:
```bash
npm start
```
The server will start on the specified port, and you can interact with it by sending requests to the `/rolldice` endpoint.
## Observe the telemetry data in Axiom [#observe-the-telemetry-data-in-axiom]
As you interact with your app, traces will be collected and exported to Axiom, where you can monitor and analyze your app’s performance and behavior.
## Dynamic OpenTelemetry traces dashboard [#dynamic-opentelemetry-traces-dashboard]
This data can then be further viewed and analyzed in Axiom’s dashboard, providing insights into the performance and behaviour of your app.
## Send data from an existing Node project [#send-data-from-an-existing-node-project]
### Manual Instrumentation [#manual-instrumentation]
Manual instrumentation in Node.js requires adding code to create and manage spans around the code blocks you want to trace.
1. Initialize Tracer:
Import and configure a tracer in your Node.js app. Use the tracer configured in your instrumentation setup (instrumentation.ts).
```js
// Assuming OpenTelemetry SDK is already configured
const { trace } = require('@opentelemetry/api');
const tracer = trace.getTracer('example-tracer');
```
2. Create Spans:
Wrap the code blocks that you want to trace with spans. Start and end these spans within your code.
```js
const span = tracer.startSpan('operation_name');
try {
// Your code here
span.end();
} catch (error) {
span.recordException(error);
span.end();
}
```
3. Annotate Spans:
Add metadata and logs to your spans for the trace data.
```js
span.setAttribute('key', 'value');
span.addEvent('event name', { eventKey: 'eventValue' });
```
### Automatic Instrumentation [#automatic-instrumentation]
Automatic instrumentation in Node.js simplifies adding telemetry data to your app. It uses pre-built libraries to automatically instrument common frameworks and libraries.
1. Install Instrumentation Libraries:
Use OpenTelemetry packages that automatically instrument common Node.js frameworks and libraries.
```bash
npm install @opentelemetry/auto-instrumentations-node
```
2. Instrument Application:
Configure your app to use these libraries, which will automatically generate spans for standard operations.
```js
// In your instrumentation setup (instrumentation.ts)
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const sdk = new NodeSDK({
// ... other configurations ...
instrumentations: [getNodeAutoInstrumentations()]
});
```
After you set them up, these libraries automatically trace relevant operations without additional code changes in your app.
## Reference [#reference]
### List of OpenTelemetry trace fields [#list-of-opentelemetry-trace-fields]
| Field Category | Field Name | Description |
| ------------------------------- | --------------------------------------- | ------------------------------------------------------------ |
| **Unique Identifiers** | | |
| | \_rowid | Unique identifier for each row in the trace data. |
| | span\_id | Unique identifier for the span within the trace. |
| | trace\_id | Unique identifier for the entire trace. |
| **Timestamps** | | |
| | \_systime | System timestamp when the trace data was recorded. |
| | \_time | Timestamp when the actual event being traced occurred. |
| **HTTP Attributes** | | |
| | attributes.custom\["http.host"] | Host information where the HTTP request was sent. |
| | attributes.custom\["http.server\_name"] | Server name for the HTTP request. |
| | attributes.http.flavor | HTTP protocol version used. |
| | attributes.http.method | HTTP method used for the request. |
| | attributes.http.route | Route accessed during the HTTP request. |
| | attributes.http.scheme | Protocol scheme (HTTP/HTTPS). |
| | attributes.http.status\_code | HTTP response status code. |
| | attributes.http.target | Specific target of the HTTP request. |
| | attributes.http.user\_agent | User agent string of the client. |
| **Network Attributes** | | |
| | attributes.net.host.port | Port number on the host receiving the request. |
| | attributes.net.peer.port | Port number on the peer (client) side. |
| | attributes.custom\["net.peer.ip"] | IP address of the peer in the network interaction. |
| **Operational Details** | | |
| | duration | Time taken for the operation. |
| | kind | Type of span (for example,, server, client). |
| | name | Name of the span. |
| | scope | Instrumentation scope. |
| | service.name | Name of the service generating the trace. |
| **Resource Process Attributes** | | |
| | resource.process.command | Command line string used to start the process. |
| | resource.process.command\_args | List of command line arguments used in starting the process. |
| | resource.process.executable.name | Name of the executable running the process. |
| | resource.process.executable.path | Path to the executable running the process. |
| | resource.process.owner | Owner of the process. |
| | resource.process.pid | Process ID. |
| | resource.process.runtime.description | Description of the runtime environment. |
| | resource.process.runtime.name | Name of the runtime environment. |
| | resource.process.runtime.version | Version of the runtime environment. |
| **Telemetry SDK Attributes** | | |
| | telemetry.sdk.language | Language of the telemetry SDK. |
| | telemetry.sdk.name | Name of the telemetry SDK. |
| | telemetry.sdk.version | Version of the telemetry SDK. |
### List of imported libraries [#list-of-imported-libraries]
The `instrumentation.ts` file imports the following libraries:
### **`@opentelemetry/sdk-node`** [#opentelemetrysdk-node]
This package is the core SDK for OpenTelemetry in Node.js. It provides the primary interface for configuring and initializing OpenTelemetry in a Node.js app. It includes functionalities for managing traces and context propagation. The SDK is designed to be extensible, allowing for custom configurations and integration with different telemetry backends like Axiom.
### **`@opentelemetry/auto-instrumentations-node`** [#opentelemetryauto-instrumentations-node]
This package offers automatic instrumentation for Node.js apps. It simplifies the process of instrumenting various common Node.js libraries and frameworks. By using this package, developers can automatically collect telemetry data (such as traces) from their apps without needing to manually instrument each library or API call. This is important for apps with complex dependencies, as it ensures comprehensive and consistent telemetry collection across the app.
### **`@opentelemetry/exporter-trace-otlp-proto`** [#opentelemetryexporter-trace-otlp-proto]
The **`@opentelemetry/exporter-trace-otlp-proto`** package provides an exporter that sends trace data using the OpenTelemetry Protocol (OTLP). OTLP is the standard protocol for transmitting telemetry data in the OpenTelemetry ecosystem. This exporter allows Node.js apps to send their collected traces to any backend that supports OTLP, such as Axiom. The use of OTLP ensures broad compatibility and a standardized way of transmitting telemetry data.
### **`@opentelemetry/sdk-trace-base`** [#opentelemetrysdk-trace-base]
Contained within this package is the **`BatchSpanProcessor`**, among other foundational elements for tracing in OpenTelemetry. The **`BatchSpanProcessor`** is a component that collects and processes spans (individual units of trace data). As the name suggests, it batches these spans before sending them to the configured exporter (in this case, the `OTLPTraceExporter`). This batching mechanism is efficient as it reduces the number of outbound requests by aggregating multiple spans into fewer batches. It helps in the performance and scalability of trace data export in an OpenTelemetry-instrumented app.
---
# OpenTelemetry using Nuxt.js
Source: https://axiom.co/docs/guides/opentelemetry-nuxtjs
OpenTelemetry provides a [unified approach to collecting telemetry data](https://opentelemetry.io/docs/languages/js/instrumentation/) from your Nuxt.js and TypeScript apps. This page demonstrates how to configure OpenTelemetry in a Nuxt.js app to send telemetry data to Axiom using OpenTelemetry SDK.
* [Install Node.js](https://nodejs.org/en/download/package-manager) version 18 or newer.
* Use your own app written in Nuxt.js, or follow this guide to create a new one.
## Create new Nuxt.js app [#create-new-nuxtjs-app]
Run the following command to create a new Nuxt.js app. Accept the default options during the initialization.
```bash
npx nuxi@latest init my-nuxt-app
cd my-nuxt-app
```
## Install dependencies [#install-dependencies]
Install the required OpenTelemetry packages:
```bash
npm install @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-proto @opentelemetry/sdk-trace-base @opentelemetry/resources @opentelemetry/semantic-conventions
```
## Configure environment variables [#configure-environment-variables]
Create a `.env` file in the root of your project to store your Axiom credentials:
```dotenv
AXIOM_TOKEN=API_TOKEN
AXIOM_DATASET=DATASET_NAME
```
## Configure Nuxt.js app [#configure-nuxtjs-app]
Configure your Nuxt app in `nuxt.config.ts` to expose the environment variables to the runtime:
```typescript nuxt.config.ts
export default defineNuxtConfig({
compatibilityDate: '2026-01-05',
devtools: { enabled: true },
runtimeConfig: {
axiomToken: process.env.AXIOM_TOKEN,
axiomDataset: process.env.AXIOM_DATASET,
},
nitro: {
experimental: {
openAPI: true
}
}
});
```
## Server setup [#server-setup]
### Create server directories [#create-server-directories]
Create the necessary directories for your server-side code:
```bash
mkdir -p server/api
mkdir -p server/plugins
```
### Instrumentation plugin [#instrumentation-plugin]
Create the OpenTelemetry instrumentation plugin in `server/plugins/instrumentation.ts`. This file sets up the OpenTelemetry SDK and configures it to send traces to Axiom.
In Nuxt.js, you must initialize OpenTelemetry in a Nitro plugin, not in the `nuxt.config.ts` hooks, because API endpoints run in the Nitro server runtime.
This example uses the `ATTR_SERVICE_NAME` constant instead of the deprecated `SemanticResourceAttributes.SERVICE_NAME`, and `resourceFromAttributes()` instead of `new Resource()` for compatibility with newer OpenTelemetry versions.
```ts server/plugins/instrumentation.ts expandable
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
export default defineNitroPlugin((nitroApp) => {
console.log('Initializing OpenTelemetry in Nitro server...');
console.log('- AXIOM_TOKEN:', process.env.AXIOM_TOKEN ? `${process.env.AXIOM_TOKEN.substring(0, 10)}...` : 'NOT SET');
console.log('- AXIOM_DATASET:', process.env.AXIOM_DATASET || 'NOT SET');
const traceExporter = new OTLPTraceExporter({
url: 'https://api.axiom.co/v1/traces',
headers: {
'Authorization': `Bearer ${process.env.AXIOM_TOKEN}`,
'X-Axiom-Dataset': process.env.AXIOM_DATASET || ''
},
});
// Add export success/error logging for debugging
const originalExport = traceExporter.export.bind(traceExporter);
traceExporter.export = (spans, resultCallback) => {
console.log(`Exporting ${spans.length} span(s) to Axiom...`);
originalExport(spans, (result) => {
if (result.code === 0) {
console.log(' Spans exported successfully to Axiom');
} else {
console.error(' Export failed:', result.error);
}
resultCallback(result);
});
};
const resource = resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'my-nuxt-app',
});
const sdk = new NodeSDK({
spanProcessor: new BatchSpanProcessor(traceExporter),
resource: resource,
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
console.log('OpenTelemetry started in Nitro server');
});
```
### Example API endpoint [#example-api-endpoint]
Create an example API endpoint in `server/api/hello.ts` to test your OpenTelemetry setup.
The example endpoint below demonstrates manual span creation. Each request to `/api/hello` creates a trace span and sends it to Axiom.
```ts server/api/hello.ts
import { trace } from '@opentelemetry/api';
export default defineEventHandler((event) => {
console.log('API endpoint hit: /api/hello');
// Get the tracer from the global tracer provider
const tracer = trace.getTracer('my-nuxt-app');
// Create a manual span for this API request
const span = tracer.startSpan('api.hello');
span.setAttribute('http.method', 'GET');
span.setAttribute('http.path', '/api/hello');
const response = {
message: 'Hello from Nuxt API!',
timestamp: new Date().toISOString(),
path: event.path
};
span.end();
console.log('Manual span created and ended');
return response;
});
```
## Run instrumented app [#run-instrumented-app]
### In development mode [#in-development-mode]
To run your Nuxt app with OpenTelemetry instrumentation in development mode:
```bash
npm run dev
```
You see the following output in the console:
```
Nuxt 4.2.2 (with Nitro 2.12.9, Vite 7.3.0 and Vue 3.5.26)
➜ Local: http://localhost:3000/
Initializing OpenTelemetry in Nitro server...
- AXIOM_TOKEN: xaat-xxxxx...
- AXIOM_DATASET: nuxt-traces
OpenTelemetry started in Nitro server
```
### Test setup [#test-setup]
Test your API endpoint to generate traces:
```bash
curl http://localhost:3000/api/hello
```
You get the following response from the API endpoint:
```json
{
"message": "Hello from Nuxt API!",
"timestamp": "2026-01-05T22:30:00.000Z",
"path": "/api/hello"
}
```
After making 5-10 requests, you see the following output in the console:
```
Exporting 5 span(s) to Axiom...
Spans exported successfully to Axiom
```
### In production mode [#in-production-mode]
To build and run in production:
```bash
# Build the application
npm run build
# Start the production server
npm run preview
```
After generating some traces by visiting your API endpoints, go to the **Stream** tab in Axiom, and then click your dataset. You see the traces appearing in the stream.
## Project directory structure [#project-directory-structure]
Your Nuxt.js project has the following structure:
```
my-nuxt-app/
├── server/
│ ├── api/
│ │ └── hello.ts # Example API endpoint
│ └── plugins/
│ └── instrumentation.ts # OpenTelemetry configuration
├── .env # Environment variables
├── nuxt.config.ts # Nuxt configuration
├── package.json # Dependencies and scripts
└── tsconfig.json # TypeScript configuration (auto-generated)
```
### Root-level files [#root-level-files]
* **`.env`**: Stores environment variables (API token, dataset name)
* **`nuxt.config.ts`**: Nuxt configuration file that exposes environment variables to the runtime
* **`package.json`**: Lists dependencies and npm scripts
* **`tsconfig.json`**: TypeScript configuration (auto-generated by Nuxt)
### Server directory [#server-directory]
The `server/` directory contains all server-side code that runs in Nitro, Nuxt's server engine.
* `server/api/` directory contains API route handlers. Each file becomes an API endpoint:
* `server/api/hello.ts` → `/api/hello`
* `server/api/users.ts` → `/api/users`
* `server/api/posts/[id].ts` → `/api/posts/:id`
* `server/plugins/` directory contains Nitro plugins that run when the server starts:
* `server/plugins/instrumentation.ts`: Initializes OpenTelemetry SDK
## Manual instrumentation [#manual-instrumentation]
Manual instrumentation in Nuxt.js allows you to create custom spans for specific operations.
### Initialize tracer [#initialize-tracer]
Import the OpenTelemetry API in any server file, such as `server/api/hello.ts`:
```ts server/api/hello.ts
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('my-nuxt-app');
```
### Create spans [#create-spans]
Wrap operations you want to trace:
```ts
export default defineEventHandler(async (event) => {
const span = tracer.startSpan('database.query');
try {
// Your code here
const data = await fetchDataFromDatabase();
span.setStatus({ code: 0 }); // Success
span.end();
return data;
} catch (error) {
span.recordException(error);
span.setStatus({ code: 2, message: error.message }); // Error
span.end();
throw error;
}
});
```
### Annotate spans [#annotate-spans]
Add metadata to your spans, such as `order.id`, `user.id`, and `order.amount`:
```typescript
const span = tracer.startSpan('process.order');
span.setAttribute('order.id', orderId);
span.setAttribute('user.id', userId);
span.setAttribute('order.amount', amount);
span.addEvent('payment_processed', {
paymentMethod: 'credit_card',
processorResponse: 'approved'
});
span.end();
```
### Create nested spans [#create-nested-spans]
Create parent-child relationships between spans:
```ts
import { trace, context } from '@opentelemetry/api';
export default defineEventHandler(async (event) => {
const tracer = trace.getTracer('my-nuxt-app');
const parentSpan = tracer.startSpan('process.checkout');
// Make parentSpan the active span
await context.with(trace.setSpan(context.active(), parentSpan), async () => {
// This span will be a child of parentSpan
const childSpan = tracer.startSpan('validate.payment');
await validatePayment();
childSpan.end();
// Another child span
const childSpan2 = tracer.startSpan('create.order');
await createOrder();
childSpan2.end();
});
parentSpan.end();
});
```
## Automatic instrumentation [#automatic-instrumentation]
Automatic instrumentation is already set up in the `server/plugins/instrumentation.ts` file:
```ts server/plugins/instrumentation.ts
instrumentations: [getNodeAutoInstrumentations()]
```
This automatically traces:
* HTTP requests and responses
* Database queries (MySQL, PostgreSQL, MongoDB, etc.)
* Redis operations
* File system operations
* DNS lookups
* And many more Node.js operations
## Reference [#reference]
### List of OpenTelemetry Trace Fields [#list-of-opentelemetry-trace-fields]
| Field Category | Field Name | Description |
| ---------------------------- | -------------------------------- | ------------------------------------------------------------ |
| **Unique Identifiers** | | |
| | \_rowid | Unique identifier for each row in the trace data. |
| | span\_id | Unique identifier for the span within the trace. |
| | trace\_id | Unique identifier for the entire trace. |
| **Timestamps** | | |
| | \_systime | System timestamp when the trace data was recorded. |
| | \_time | Timestamp when the actual event being traced occurred. |
| **HTTP Attributes** | | |
| | attributes.http.method | HTTP method used for the request. |
| | attributes.http.path | The API path accessed during the request. |
| | attributes.http.status\_code | HTTP response status code. |
| | attributes.http.route | Route pattern (for example, /api/:id). |
| | attributes.http.scheme | Protocol scheme (HTTP/HTTPS). |
| | attributes.http.user\_agent | User agent string of the client. |
| **Network Attributes** | | |
| | attributes.net.host.port | Port number on the host receiving the request. |
| | attributes.net.peer.ip | IP address of the peer in the network interaction. |
| **Operational Details** | | |
| | duration | Time taken for the operation in nanoseconds. |
| | kind | Type of span (server, client, internal, producer, consumer). |
| | name | Name of the span (for example, 'api.hello'). |
| | scope | Instrumentation scope. |
| | service.name | Name of the service generating the trace. |
| **Resource Attributes** | | |
| | resource.process.pid | Process ID of the Nitro server. |
| | resource.process.runtime.name | Runtime name (for example, 'nodejs'). |
| | resource.process.runtime.version | Node.js version. |
| **Telemetry SDK Attributes** | | |
| | telemetry.sdk.language | Language of the telemetry SDK (javascript). |
| | telemetry.sdk.name | Name of the telemetry SDK (opentelemetry). |
| | telemetry.sdk.version | Version of the telemetry SDK. |
### List of Imported Libraries [#list-of-imported-libraries]
#### `@opentelemetry/sdk-node` [#opentelemetrysdk-node]
The core SDK for OpenTelemetry in Node.js. Provides the primary interface for configuring and initializing OpenTelemetry in a Node.js/Nuxt app. It includes functionalities for managing traces, metrics, and context propagation.
#### `@opentelemetry/auto-instrumentations-node` [#opentelemetryauto-instrumentations-node]
Offers automatic instrumentation for Node.js apps. Automatically collects telemetry data from common Node.js libraries and frameworks without manual instrumentation. Essential for Nuxt/Nitro server operations.
#### `@opentelemetry/exporter-trace-otlp-proto` [#opentelemetryexporter-trace-otlp-proto]
Provides an exporter that sends trace data using the OpenTelemetry Protocol (OTLP). Allows Nuxt apps to send collected traces to Axiom or any OTLP-compatible backend.
#### `@opentelemetry/sdk-trace-base` [#opentelemetrysdk-trace-base]
Contains the `BatchSpanProcessor` and other foundational elements for tracing. The `BatchSpanProcessor` batches spans before sending them to the exporter, improving performance and reducing network overhead.
#### `@opentelemetry/resources` [#opentelemetryresources]
Provides the `resourceFromAttributes()` function to create resource objects that identify your service in traces. Resources contain service metadata like service name, version, and environment.
#### `@opentelemetry/semantic-conventions` [#opentelemetrysemantic-conventions]
Provides standard attribute names like `ATTR_SERVICE_NAME` for consistent telemetry data. Ensures your traces follow OpenTelemetry semantic conventions for better interoperability.
#### `@opentelemetry/api` [#opentelemetryapi]
The OpenTelemetry API package that provides the `trace` and `context` APIs for manual instrumentation. This is a peer dependency that other OpenTelemetry packages rely on.
## Advanced configurations [#advanced-configurations]
### Custom span processor [#custom-span-processor]
For more control over span processing, use the `SimpleSpanProcessor` instead of the `BatchSpanProcessor`:
```typescript
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
const sdk = new NodeSDK({
spanProcessor: new SimpleSpanProcessor(traceExporter), // Exports immediately
resource: resource,
instrumentations: [getNodeAutoInstrumentations()],
});
```
### Sampling [#sampling]
To reduce the volume of traces, use the `TraceIdRatioBasedSampler` to sample a percentage of traces:
```typescript
import { TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base';
const sdk = new NodeSDK({
spanProcessor: new BatchSpanProcessor(traceExporter),
sampler: new TraceIdRatioBasedSampler(0.5), // Sample 50% of traces
resource: resource,
instrumentations: [getNodeAutoInstrumentations()],
});
```
### Custom resource attributes [#custom-resource-attributes]
Add custom attributes to all traces, such as `service.version` and `deployment.environment`:
```typescript
const resource = resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'my-nuxt-app',
[ATTR_SERVICE_VERSION]: '1.0.0',
'deployment.environment': process.env.NODE_ENV || 'development',
'service.namespace': 'production',
});
```
---
# Send OpenTelemetry data from a Python app to Axiom
Source: https://axiom.co/docs/guides/opentelemetry-python
This guide explains how to send OpenTelemetry data from a Python app to Axiom using the [Python OpenTelemetry SDK](https://opentelemetry.io/docs/languages/python/instrumentation/).
## Prerequisites [#prerequisites]
* Install Python version 3.7 or higher.
## Install required dependencies [#install-required-dependencies]
To install the required Python dependencies, run the following code in your terminal:
```bash
pip install opentelemetry-api opentelemetry-sdk opentelemetry-instrumentation-flask opentelemetry-exporter-otlp Flask
```
### Install dependencies with requirements file [#install-dependencies-with-requirements-file]
Alternatively, if you use a `requirements.txt` file in your Python project, add these lines:
```txt
opentelemetry-api
opentelemetry-sdk
opentelemetry-instrumentation-flask
opentelemetry-exporter-otlp
Flask
```
Then run the following code in your terminal to install dependencies:
```bash
pip install -r requirements.txt
```
## Create an app.py file [#create-an-apppy-file]
Create an `app.py` file with the following content. This file creates a basic HTTP server using Flask. It also demonstrates the usage of span links to establish relationships between spans across different traces.
```python
# app.py
from flask import Flask
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry import trace
from random import randint
import exporter
# Creating a Flask app instance
app = Flask(__name__)
# Automatically instruments Flask app to enable tracing
FlaskInstrumentor().instrument_app(app)
# Retrieving a tracer from the custom exporter
tracer = exporter.service1_tracer
@app.route("/rolldice")
def roll_dice(parent_span=None):
# Starting a new span for the dice roll. If a parent span is provided, link to its span context.
with tracer.start_as_current_span("roll_dice_span",
links=[trace.Link(parent_span.get_span_context())] if parent_span else None) as span:
# Spans can be created with zero or more Links to other Spans that are related.
# Links allow creating connections between different traces
return str(roll())
@app.route("/roll_with_link")
def roll_with_link():
# Starting a new 'parent_span' which may later link to other spans
with tracer.start_as_current_span("parent_span") as parent_span:
# A common scenario is to correlate one or more traces with the current span.
# This can help in tracing and debugging complex interactions across different parts of the app.
result = roll_dice(parent_span)
return f"Dice roll result (with link): {result}"
def roll():
# Function to generate a random number between 1 and 6
return randint(1, 6)
if __name__ == "__main__":
# Starting the Flask server on the specified PORT and enabling debug mode
app.run(port=8080, debug=True)
```
## Create an exporter.py file [#create-an-exporterpy-file]
Create an `exporter.py` file with the following content. This file establishes an OpenTelemetry configuration and sets up an exporter that sends trace data to Axiom.
```python
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.resources import Resource, SERVICE_NAME
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
# Define the service name resource for the tracer.
resource = Resource(attributes={
SERVICE_NAME: "NAME_OF_SERVICE" # Replace `NAME_OF_SERVICE` with the name of the service you want to trace.
})
# Create a TracerProvider with the defined resource for creating tracers.
provider = TracerProvider(resource=resource)
# Configure the OTLP/HTTP Span Exporter with Axiom headers and endpoint.
otlp_exporter = OTLPSpanExporter(
endpoint="https://AXIOM_DOMAIN/v1/traces",
headers={
"Authorization": "Bearer API_TOKEN",
"X-Axiom-Dataset": "DATASET_NAME"
}
)
# Create a BatchSpanProcessor with the OTLP exporter to batch and send trace spans.
processor = BatchSpanProcessor(otlp_exporter)
provider.add_span_processor(processor)
# Set the TracerProvider as the global tracer provider.
trace.set_tracer_provider(provider)
# Define a tracer for external use in different parts of the app.
service1_tracer = trace.get_tracer("service1")
```
Replace `NAME_OF_SERVICE` with the name of the service you want to trace. This is important for identifying and categorizing trace data, particularly in systems with multiple services.
For more information on the libraries imported by the `exporter.py` file, see the [Reference](#reference) below.
## Run the app [#run-the-app]
Run the following code in your terminal to run the Python project:
macOS/Linux
```bash
python3 app.py
```
Windows
```
py -3 app.py
```
In your browser, go to `http://127.0.0.1:8080/rolldice` to interact with your Python app. Each time you load the page, the app displays a random number and sends the collected traces to Axiom.
## Observe the telemetry data in Axiom [#observe-the-telemetry-data-in-axiom]
In Axiom, go the **Stream** tab and click your dataset. This page displays the traces sent to Axiom and enables you to monitor and analyze your app’s performance and behavior.
## Dynamic OpenTelemetry traces dashboard [#dynamic-opentelemetry-traces-dashboard]
In Axiom, go the **Dashboards** tab and click **OpenTelemetry Traces (python)**. This pre-built traces dashboard provides further insights into the performance and behavior of your app.
## Send data from an existing Python project [#send-data-from-an-existing-python-project]
### Manual instrumentation [#manual-instrumentation]
Manual instrumentation in Python with OpenTelemetry involves adding code to create and manage spans around the blocks of code you want to trace. This approach allows for precise control over the trace data.
1. Import and configure a tracer at the start of your main Python file. For example, use the tracer from the `exporter.py` configuration.
```python
import exporter
tracer = exporter.service1_tracer
```
2. Enclose the code blocks in your app that you want to trace within spans. Start and end these spans in your code.
```python
with tracer.start_as_current_span("operation_name"):
```
3. Add relevant metadata and logs to your spans to enrich the trace data, providing more context for your data.
```python
with tracer.start_as_current_span("operation_name") as span:
span.set_attribute("key", "value")
```
### Automatic instrumentation [#automatic-instrumentation]
Automatic instrumentation in Python with OpenTelemetry simplifies the process of adding telemetry data to your app. It uses pre-built libraries that automatically instrument the frameworks and libraries.
1. Install the OpenTelemetry packages designed for specific frameworks like Flask or Django.
```bash
pip install opentelemetry-instrumentation-flask
```
2. Configure your app to use these libraries that automatically generate spans for standard operations.
```python
from opentelemetry.instrumentation.flask import FlaskInstrumentor
# This assumes `app` is your Flask app.
FlaskInstrumentor().instrument_app(app)
```
After you set them up, these libraries automatically trace relevant operations without additional code changes in your app.
## Reference [#reference]
### List of OpenTelemetry trace fields [#list-of-opentelemetry-trace-fields]
| Field Category | Field Name | Description |
| ------------------- | --------------------------------------- | ------------------------------------------------------ |
| Unique Identifiers | | |
| | \_rowid | Unique identifier for each row in the trace data. |
| | span\_id | Unique identifier for the span within the trace. |
| | trace\_id | Unique identifier for the entire trace. |
| Timestamps | | |
| | \_systime | System timestamp when the trace data was recorded. |
| | \_time | Timestamp when the actual event being traced occurred. |
| HTTP Attributes | | |
| | attributes.custom\["http.host"] | Host information where the HTTP request was sent. |
| | attributes.custom\["http.server\_name"] | Server name for the HTTP request. |
| | attributes.http.flavor | HTTP protocol version used. |
| | attributes.http.method | HTTP method used for the request. |
| | attributes.http.route | Route accessed during the HTTP request. |
| | attributes.http.scheme | Protocol scheme (HTTP/HTTPS). |
| | attributes.http.status\_code | HTTP response status code. |
| | attributes.http.target | Specific target of the HTTP request. |
| | attributes.http.user\_agent | User agent string of the client. |
| Network Attributes | | |
| | attributes.net.host.port | Port number on the host receiving the request. |
| | attributes.net.peer.port | Port number on the peer (client) side. |
| | attributes.custom\["net.peer.ip"] | IP address of the peer in the network interaction. |
| Operational Details | | |
| | duration | Time taken for the operation. |
| | kind | Type of span (for example,, server, client). |
| | name | Name of the span. |
| | scope | Instrumentation scope. |
| | service.name | Name of the service generating the trace. |
### List of imported libraries [#list-of-imported-libraries]
The `exporter.py` file imports the following libraries:
from opentelemetry import trace
This module creates and manages trace data in your app. It creates spans and tracers which track the execution flow and performance of your app.
from opentelemetry.sdk.trace import TracerProvider
`TracerProvider` acts as a container for the configuration of your app’s tracing behavior. It allows you to define how spans are generated and processed, essentially serving as the central point for managing trace creation and propagation in your app.
from opentelemetry.sdk.trace.export import BatchSpanProcessor
`BatchSpanProcessor` is responsible for batching spans before they're exported. This is an important aspect of efficient trace data management as it aggregates multiple spans into fewer network requests, reducing the overhead on your app’s performance and the tracing backend.
from opentelemetry.sdk.resources import Resource, SERVICE\_NAME
The `Resource` class is used to describe your app’s service attributes, such as its name, version, and environment. This contextual information is attached to the traces and helps in identifying and categorizing trace data, making it easier to filter and analyze in your monitoring setup.
from opentelemetry.exporter.otlp.proto.http.trace\_exporter import OTLPSpanExporter
The `OTLPSpanExporter` is responsible for sending your app’s trace data to a backend that supports the OTLP such as Axiom. It formats the trace data according to the OTLP standards and transmits it over HTTP, ensuring compatibility and standardization in how telemetry data is sent across different systems and services.
---
# Send OpenTelemetry data from a Ruby on Rails app to Axiom
Source: https://axiom.co/docs/guides/opentelemetry-ruby
This guide provides detailed steps on how to configure OpenTelemetry in a Ruby app to send telemetry data to Axiom using the [OpenTelemetry Ruby SDK](https://opentelemetry.io/docs/languages/ruby/).
* Install a [Ruby version manager](https://www.ruby-lang.org/en/documentation/installation/) like `rbenv` and use it to install the latest Ruby version.
* Install [Rails](https://guides.rubyonrails.org/v5.0/getting_started.html) using the `gem install rails` command.
## Set up the Ruby on Rails app [#set-up-the-ruby-on-rails-app]
1. Create a new Rails app using the `rails new myapp` command.
2. Go to the app directory with the `cd myapp` command.
3. Open the `Gemfile` and add the following OpenTelemetry packages:
```ruby
gem 'opentelemetry-api'
gem 'opentelemetry-sdk'
gem 'opentelemetry-exporter-otlp'
gem 'opentelemetry-instrumentation-rails'
gem 'opentelemetry-instrumentation-http'
gem 'opentelemetry-instrumentation-active_record', require: false
gem 'opentelemetry-instrumentation-all'
```
Install the dependencies by running `bundle install`.
## Configure the OpenTelemetry exporter [#configure-the-opentelemetry-exporter]
In the `initializers` folder of your Rails app, create a new file called `opentelemetry.rb`, and then add the following OpenTelemetry exporter configuration:
```ruby
require 'opentelemetry/sdk'
require 'opentelemetry/exporter/otlp'
require 'opentelemetry/instrumentation/all'
OpenTelemetry::SDK.configure do |c|
c.service_name = 'ruby-traces' # Set your service name
c.use_all # Or specify individual instrumentation you need
c.add_span_processor(
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
OpenTelemetry::Exporter::OTLP::Exporter.new(
endpoint: 'https://AXIOM_DOMAIN/v1/traces',
headers: {
'Authorization' => 'Bearer API_TOKEN',
'X-AXIOM-DATASET' => 'DATASET_NAME'
}
)
)
)
end
```
## Run the instrumented app [#run-the-instrumented-app]
Run your Ruby on Rails app with OpenTelemetry instrumentation.
### In development mode [#in-development-mode]
Start the Rails server using the `rails server` command. The server will start on the default port (usually 3000), and you can access your app by visiting `http://localhost:3000` in your web browser.
As you interact with your app, OpenTelemetry automatically collects telemetry data and sends it to Axiom using the configured OTLP exporter.
### In production mode [#in-production-mode]
For production, ensure to precompile assets and run migrations if necessary. Start the server with `RAILS_ENV=production bin/rails server`. This setup ensures your Ruby app is instrumented to send traces to Axiom, using OpenTelemetry for observability.
## Observe the telemetry data in Axiom [#observe-the-telemetry-data-in-axiom]
As you interact with your app, traces are collected and exported to Axiom, allowing you to monitor, analyze, and gain insights into your app’s performance and behavior.
1. In your Axiom account and click the **Datasets** or **Stream** tab.
2. Select your dataset from the list.
3. From the list of fields, click the **trace\_id** to view your spans.
## Dynamic OpenTelemetry Traces dashboard [#dynamic-opentelemetry-traces-dashboard]
This data can then be further viewed and analyzed in Axiom’s dashboard, offering a deeper understanding of your app’s performance and behavior.
1. In your Axiom account, select **Dashboards**, and click the traces dashboard named after your dataset.
2. View the dashboard which displays your total traces, incoming spans, average span duration, errors, slowest operations, and top 10 span errors across services.
## Send data from an existing Ruby app [#send-data-from-an-existing-ruby-app]
### Manual instrumentation [#manual-instrumentation]
Manual instrumentation allows users to define and manage telemetry data collection points within their Ruby apps, providing granular control over what is traced.
1. Initialize Tracer. Use the OpenTelemetry API to obtain a tracer from the global tracer provider. This tracer will be used to start and manage spans.
```ruby
tracer = OpenTelemetry.tracer_provider.tracer('my-tracer')
```
2. Manually start a span at the beginning of the block of code you want to trace and ensure to end it when your operations complete. This is useful for gathering detailed data about specific operations.
```ruby
span = tracer.start_span('operation_name')
begin
# Perform operation
rescue => e
span.record_exception(e)
span.status = OpenTelemetry::Trace::Status.error("Operation failed")
ensure
span.finish
end
```
3. Enhance spans with custom attributes to provide additional context about the traced operations, helping in debugging and monitoring performance.
```ruby
span.set_attribute("user_id", user.id)
span.add_event("query_executed", attributes: { "query" => sql_query })
```
### Automatic instrumentation [#automatic-instrumentation]
Automatic instrumentation in Ruby uses OpenTelemetry’s libraries to automatically generate telemetry data for common operations, such as HTTP requests and database queries.
1. Set up the OpenTelemetry SDK with the necessary instrumentation libraries in your Ruby app. This typically involves modifying the Gemfile and an initializer to set up the SDK and auto-instrumentation.
```ruby
# In config/initializers/opentelemetry.rb
OpenTelemetry::SDK.configure do |c|
c.service_name = 'ruby-traces'
c.use_all # Automatically use all available instrumentation
end
```
2. Ensure your Gemfile includes gems for the automatic instrumentation of the frameworks and libraries your app uses.
```ruby
gem 'opentelemetry-instrumentation-rails'
gem 'opentelemetry-instrumentation-http'
gem 'opentelemetry-instrumentation-active_record'
```
After setting up, no additional manual changes are required for basic telemetry data collection. The instrumentation libraries handle the creation and management of telemetry data automatically.
## Reference [#reference]
### List of OpenTelemetry trace fields [#list-of-opentelemetry-trace-fields]
| Field Category | Field Name | Description |
| ------------------------------- | ------------------------------------ | ------------------------------------------------------------- |
| **General Trace Information** | | |
| | \_rowId | Unique identifier for each row in the trace data. |
| | \_sysTime | System timestamp when the trace data was recorded. |
| | \_time | Timestamp when the actual event being traced occurred. |
| | trace\_id | Unique identifier for the entire trace. |
| | span\_id | Unique identifier for the span within the trace. |
| | parent\_span\_id | Unique identifier for the parent span within the trace. |
| **HTTP Attributes** | | |
| | attributes.http.method | HTTP method used for the request. |
| | attributes.http.status\_code | HTTP status code returned in response. |
| | attributes.http.target | Specific target of the HTTP request. |
| | attributes.http.scheme | Protocol scheme (HTTP/HTTPS). |
| **User Agent** | | |
| | attributes.http.user\_agent | User agent string, providing client software and OS. |
| **Custom Attributes** | | |
| | attributes.custom\["http.host"] | Host information where the HTTP request was sent. |
| | attributes.custom.identifier | Path to a file or identifier in the trace context. |
| | attributes.custom.layout | Layout used in the rendering process of a view or template. |
| **Resource Process Attributes** | | |
| | resource.process.command | Command line string used to start the process. |
| | resource.process.pid | Process ID. |
| | resource.process.runtime.description | Description of the runtime environment. |
| | resource.process.runtime.name | Name of the runtime environment. |
| | resource.process.runtime.version | Version of the runtime environment. |
| **Operational Details** | | |
| | duration | Time taken for the operation. |
| | kind | Type of span (for example, server, client, internal). |
| | name | Name of the span, often a high-level title for the operation. |
| **Code Attributes** | | |
| | attributes.code.function | Function or method being executed. |
| | attributes.code.namespace | Namespace or module that includes the function. |
| **Scope Attributes** | | |
| | scope.name | Name of the scope for the operation. |
| | scope.version | Version of the scope. |
| **Service Attributes** | | |
| | service.name | Name of the service generating the trace. |
| | service.version | Version of the service generating the trace. |
| | service.instance.id | Unique identifier for the instance of the service. |
| **Telemetry SDK Attributes** | | |
| | telemetry.sdk.language | Language of the telemetry SDK, for example, ruby. |
| | telemetry.sdk.name | Name of the telemetry SDK, for example, opentelemetry. |
| | telemetry.sdk.version | Version of the telemetry SDK, for example, 1.4.1. |
### List of imported libraries [#list-of-imported-libraries]
`gem 'opentelemetry-api'`
The `opentelemetry-api` gem provides the core OpenTelemetry API for Ruby. It defines the basic concepts and interfaces for distributed tracing, such as spans, tracers, and context propagation. This gem is essential for instrumenting your Ruby app with OpenTelemetry.
`gem 'opentelemetry-sdk'`
The `opentelemetry-sdk` gem is the OpenTelemetry SDK for Ruby. It provides the implementation of the OpenTelemetry API, including the tracer provider, span processors, and exporters. This gem is responsible for managing the lifecycle of spans and sending them to the specified backend.
`gem 'opentelemetry-exporter-otlp'`
The `opentelemetry-exporter-otlp` gem is an exporter that sends trace data to a backend that supports the OpenTelemetry Protocol (OTLP), such as Axiom. It formats the trace data according to the OTLP standards and transmits it over HTTP or gRPC, ensuring compatibility and standardization in how telemetry data is sent across different systems and services.
`gem 'opentelemetry-instrumentation-rails'`
The `opentelemetry-instrumentation-rails` gem provides automatic instrumentation for Ruby on Rails apps. It integrates with various aspects of a Rails app, such as controllers, views, and database queries, to capture relevant trace data without requiring manual instrumentation. This gem simplifies the process of adding tracing to your Rails app.
`gem 'opentelemetry-instrumentation-http'`
The `opentelemetry-instrumentation-http` gem provides automatic instrumentation for HTTP requests made using the `Net::HTTP` library. It captures trace data for outgoing HTTP requests, including request headers, response status, and timing information. This gem helps in tracing the external dependencies of your app.
`gem 'opentelemetry-instrumentation-active_record', require: false`
The `opentelemetry-instrumentation-active_record` gem provides automatic instrumentation for ActiveRecord, the Object-Relational Mapping (ORM) library used in Ruby on Rails. It captures trace data for database queries, including the SQL statements executed and their duration. This gem helps in identifying performance bottlenecks related to database interactions.
`gem 'opentelemetry-instrumentation-all'`
The `opentelemetry-instrumentation-all` gem is a meta-gem that includes all the available instrumentation libraries for OpenTelemetry in Ruby. It provides a convenient way to install and configure multiple instrumentation libraries at once, covering various aspects of your app, such as HTTP requests, database queries, and external libraries. This gem simplifies the setup process and ensures comprehensive tracing coverage for your Ruby app.
---
# Axiom transport for Pino logger
Source: https://axiom.co/docs/guides/pino
## Install SDK [#install-sdk]
To install the SDK, run the following:
```shell
npm install @axiomhq/pino
```
## Create Pino logger [#create-pino-logger]
The example below creates a Pino logger with Axiom configured. Set the `edge` option to the edge domain that matches the region your dataset lives in — see [Configure region](#configure-region) for the full list.
```ts EU Central 1
import pino from 'pino';
const logger = pino(
{ level: 'info' },
pino.transport({
target: '@axiomhq/pino',
options: {
dataset: process.env.AXIOM_DATASET,
token: process.env.AXIOM_TOKEN,
edge: 'eu-central-1.aws.edge.axiom.co',
},
}),
);
```
```ts US East 1
import pino from 'pino';
const logger = pino(
{ level: 'info' },
pino.transport({
target: '@axiomhq/pino',
options: {
dataset: process.env.AXIOM_DATASET,
token: process.env.AXIOM_TOKEN,
edge: 'us-east-1.aws.edge.axiom.co',
},
}),
);
```
After setting up the Axiom transport for Pino, use the logger as usual:
```js
logger.info('Hello from Pino!');
```
## Configure region [#configure-region]
Set the `edge` option on the transport to the edge domain for your Axiom deployment.
| Edge deployment | Base domain for ingest and query |
| ------------------ | -------------------------------- |
| US East 1 (AWS) | `us-east-1.aws.edge.axiom.co` |
| EU Central 1 (AWS) | `eu-central-1.aws.edge.axiom.co` |
Always use the `edge` option to target a region. Don't put a regional hostname in `url` — `url` is reserved for non-ingest API operations and will not route ingest correctly.
## Transport options [#transport-options]
| Option | Required | Description |
| --------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `dataset` | yes | The Axiom dataset to ingest logs into. |
| `token` | yes | An Axiom API or personal token with `ingest` permission for the dataset. |
| `edge` | no | Edge domain for ingest, without scheme. Example: `eu-central-1.aws.edge.axiom.co`. Use this to target a region. |
| `edgeUrl` | no | Full edge URL (with scheme). Takes precedence over `edge` if both are set. Useful for self-hosted or proxy setups. |
| `url` | no | Base URL for non-ingest API operations. Only needed if you call other Axiom APIs from the same client. |
## Examples [#examples]
For more examples, see the [examples in GitHub](https://github.com/axiomhq/axiom-js/tree/main/examples/pino).
---
# Send data from Python app to Axiom
Source: https://axiom.co/docs/guides/python
To send data from a Python app to Axiom, use the Axiom Python SDK.
The Axiom Python SDK is an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/axiom-py).
## Install SDK [#install-sdk]
```shell Linux / MacOS
python3 -m pip install axiom-py
```
```shell Windows
py -m pip install axiom-py
```
```shell pip
pip3 install axiom-py
```
If you use the [Axiom CLI](/reference/cli), run `eval $(axiom config export -f)` to configure your environment variables. Otherwise, [create an API token](/reference/tokens) and export it as `AXIOM_TOKEN`.
You can also configure the client using options passed to the client constructor:
```py
import axiom_py
client = axiom_py.Client("API_TOKEN")
```
## Use client [#use-client]
```py
import axiom_py
import rfc3339
from datetime import datetime,timedelta
client = axiom_py.Client()
client.ingest_events(
dataset="DATASET_NAME",
events=[
{"foo": "bar"},
{"bar": "baz"},
])
client.query(r"['DATASET_NAME'] | where foo == 'bar' | limit 100")
```
For more examples, see the [examples in GitHub](https://github.com/axiomhq/axiom-py/tree/main/examples/client_example.py).
## Configure region [#configure-region]
By default, the client sends data to `api.axiom.co`. To target a specific edge region, pass the `edge` argument with the edge domain that matches the region your dataset lives in:
```py EU Central 1
import axiom_py
client = axiom_py.Client(
token="xaat-your-api-token",
edge="eu-central-1.aws.edge.axiom.co",
)
```
```py US East 1
import axiom_py
client = axiom_py.Client(
token="xaat-your-api-token",
edge="us-east-1.aws.edge.axiom.co",
)
```
The following edge domains are available:
| Edge deployment | Base domain for ingest and query |
| ------------------ | -------------------------------- |
| US East 1 (AWS) | `us-east-1.aws.edge.axiom.co` |
| EU Central 1 (AWS) | `eu-central-1.aws.edge.axiom.co` |
For more information about edge deployments, see [Edge deployments](/reference/edge-deployments).
To point at a custom edge endpoint such as a proxy or load balancer, use `edge_url="https://your-edge-host"` instead. `edge_url` takes precedence over `edge` if both are set.
Edge endpoints require an API token (`xaat-`), not a personal token (`xapt-`). Passing a personal token with edge configuration raises an error.
Edge configuration must be passed explicitly when you create the client. Unlike `token` and `org_id`, the `edge` and `edge_url` arguments are not read from environment variables.
## Example with `AxiomHandler` [#example-with-axiomhandler]
The example below uses `AxiomHandler` to send logs from the `logging` module to Axiom:
```python
import axiom_py
from axiom_py.logging import AxiomHandler
import logging
def setup_logger():
client = axiom_py.Client()
handler = AxiomHandler(client, "DATASET_NAME")
logging.getLogger().addHandler(handler)
```
For a full example, see [GitHub](https://github.com/axiomhq/axiom-py/tree/main/examples/logger_example.py).
## Example with `structlog` [#example-with-structlog]
The example below uses [structlog](https://github.com/hynek/structlog) to send logs to Axiom:
```python
from axiom_py import Client
from axiom_py.structlog import AxiomProcessor
def setup_logger():
client = Client()
structlog.configure(
processors=[
# ...
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso", key="_time"),
AxiomProcessor(client, "DATASET_NAME"),
# ...
]
)
```
For a full example, see [GitHub](https://github.com/axiomhq/axiom-py/tree/main/examples/structlog_example.py).
---
# Send data from Rust app to Axiom
Source: https://axiom.co/docs/guides/rust
To send data from a Rust app to Axiom, use the Axiom Rust SDK.
The Axiom Rust SDK is an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/axiom-rs).
## Install SDK [#install-sdk]
Add the following to your `Cargo.toml`:
```toml
[dependencies]
axiom-rs = "VERSION"
```
Replace `VERSION` with the latest version number specified on the [GitHub Releases](https://github.com/axiomhq/axiom-rs/releases) page. For example, `0.11.0`.
If you use the [Axiom CLI](/reference/cli), run `eval $(axiom config export -f)` to configure your environment variables. Otherwise, [create an API token](/reference/tokens) and export it as `AXIOM_TOKEN`.
## Use client [#use-client]
```rust
use axiom_rs::Client;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box> {
// Build your client by providing a personal token and an org id:
let client = Client::builder()
.with_token("API_TOKEN")
.build()?;
// Alternatively, auto-configure the client from the environment variable AXIOM_TOKEN:
let client = Client::new()?;
client.datasets().create("DATASET_NAME", "").await?;
client
.ingest(
"DATASET_NAME",
vec![json!({
"foo": "bar",
})],
)
.await?;
let res = client
.query(r#"['DATASET_NAME'] | where foo == "bar" | limit 100"#, None)
.await?;
println!("{:?}", res);
client.datasets().delete("DATASET_NAME").await?;
Ok(())
}
```
For more examples, see the [examples in GitHub](https://github.com/axiomhq/axiom-rs/tree/main/examples).
## Configure region [#configure-region]
By default, the client sends data to `api.axiom.co`. To target a specific edge region, add the `with_edge` method to the builder with the edge domain that matches the region your dataset lives in:
```rust EU Central 1
let client = Client::builder()
.with_token("xaat-your-api-token")
.with_edge("eu-central-1.aws.edge.axiom.co")
.build()?;
```
```rust US East 1
let client = Client::builder()
.with_token("xaat-your-api-token")
.with_edge("us-east-1.aws.edge.axiom.co")
.build()?;
```
The following edge domains are available:
| Edge deployment | Base domain for ingest and query |
| ------------------ | -------------------------------- |
| US East 1 (AWS) | `us-east-1.aws.edge.axiom.co` |
| EU Central 1 (AWS) | `eu-central-1.aws.edge.axiom.co` |
For more information about edge deployments, see [Edge deployments](/reference/edge-deployments).
You can also configure the region without changing code by exporting environment variables. When you build the client with `Client::new()`, these are read automatically:
```sh
export AXIOM_EDGE="eu-central-1.aws.edge.axiom.co"
# or, for a full URL (takes precedence over AXIOM_EDGE):
export AXIOM_EDGE_URL="https://eu-central-1.aws.edge.axiom.co"
```
To point at a custom edge endpoint such as a proxy or load balancer, use `.with_edge_url("https://your-edge-host")`. `with_edge_url` takes precedence over `with_edge` if both are set.
Edge endpoints require an API token (`xaat-`), not a personal token (`xapt-`). Ingesting with a personal token against an edge endpoint returns a `PersonalTokenNotSupportedForEdge` error.
## Optional features [#optional-features]
You can use the [Cargo features](https://doc.rust-lang.org/stable/cargo/reference/features.html#the-features-section):
* `default-tls`: Provides TLS support to connect over HTTPS. Enabled by default.
* `native-tls`: Enables TLS functionality provided by `native-tls`.
* `rustls-tls`: Enables TLS functionality provided by `rustls`.
* `tokio`: Enables usage with the `tokio` runtime. Enabled by default.
* `async-std`: Enables usage with the `async-std` runtime.
---
# Send logs from Apache Log4j to Axiom
Source: https://axiom.co/docs/guides/send-logs-from-apache-log4j
Log4j is a Java logging framework developed by the Apache Software Foundation and widely used in the Java community. This page covers how to get started with Log4j, configure it to forward log messages to Fluentd, and send logs to Axiom.
* [Install JDK 11](https://www.oracle.com/java/technologies/java-se-glance.html) or later
* [Install Maven](https://maven.apache.org/download.cgi)
* [Install Fluentd](https://www.fluentd.org/download)
* [Install Docker](https://docs.docker.com/get-docker/)
## Configure Log4j [#configure-log4j]
Log4j is a flexible and powerful logging framework for Java applications. To use Log4j in your project, add the necessary dependencies to your `pom.xml` file. The dependencies required for Log4j include `log4j-core`, `log4j-api`, and `log4j-slf4j2-impl` for logging capability, and `jackson-databind` for JSON support.
1. Create a new Maven project:
```bash
mvn archetype:generate -DgroupId=com.example -DartifactId=log4j-axiom-test -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
cd log4j-axiom-test
```
2. Open the `pom.xml` file and replace its contents with the following:
```xml
4.0.0
com.example
log4j-axiom-test
jar
1.0-SNAPSHOT
log4j-axiom-test
http://maven.apache.org
11
11
2.19.0
junit
junit
4.12
test
org.apache.logging.log4j
log4j-core
${log4j.version}
org.apache.logging.log4j
log4j-api
${log4j.version}
org.apache.logging.log4j
log4j-slf4j2-impl
${log4j.version}
com.fasterxml.jackson.core
jackson-databind
2.13.0
org.apache.maven.plugins
maven-shade-plugin
3.2.4
package
shade
com.example.App
false
```
This `pom.xml` file includes the necessary Log4j dependencies and configures the Maven Shade plugin to create an executable JAR file.
3. Create a new file named `log4j2.xml` in your root directory and add the following content:
```xml
```
This configuration sets up two appenders:
* A Socket appender that sends logs to Fluentd, running on `localhost:24224`. Is uses JSON format for the log messages, which makes it easier to parse and analyze the logs later in Axiom.
* A Console appender that prints logs to the standard output,
## Set log level [#set-log-level]
Log4j supports various log levels, allowing you to control the verbosity of your logs. The main log levels, in order of increasing severity, are the following:
* `TRACE`: Fine-grained information for debugging.
* `DEBUG`: General debugging information.
* `INFO`: Informational messages.
* `WARN`: Indications of potential problems.
* `ERROR`: Error events that might still allow the app to continue running.
* `FATAL`: Severe error events that might lead the app to cancel.
In the configuration above, the root logger level is set to INFO which means it logs messages at INFO level and above (WARN, ERROR, and FATAL).
To set the log level, create a simple Java class to demonstrate these log levels. Create a new file named `App.java` in the `src/main/java/com/example` directory with the following content:
```java
package com.example;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.Level;
import java.util.Random;
public class App {
// Define loggers for different purposes
private static final Logger logger = LogManager.getLogger(App.class);
private static final Logger securityLogger = LogManager.getLogger("SecurityLogger");
private static final Logger performanceLogger = LogManager.getLogger("PerformanceLogger");
public static void main(String[] args) {
// Configure logging levels programmatically
configureLogging();
Random random = new Random();
// Infinite loop to continuously generate log events
while (true) {
try {
// Simulate various logging scenarios
simulateUserActivity(random);
simulateDatabaseOperations(random);
simulateSecurityEvents(random);
simulatePerformanceMetrics(random);
// Simulate a critical error with 10% probability
if (random.nextInt(10) == 0) {
throw new RuntimeException("Simulated critical error");
}
Thread.sleep(1000); // Sleep for 1 second
} catch (InterruptedException e) {
logger.warn("Sleep interrupted", e);
} catch (Exception e) {
logger.error("Critical error occurred", e);
} finally {
// Clear thread context after each iteration
ThreadContext.clearAll();
}
}
}
private static void configureLogging() {
// Set root logger level to DEBUG
Configurator.setRootLevel(Level.DEBUG);
// Set custom logger levels
Configurator.setLevel("SecurityLogger", Level.INFO);
Configurator.setLevel("PerformanceLogger", Level.TRACE);
}
// Simulate user activities and log them
private static void simulateUserActivity(Random random) {
String[] users = {"Alice", "Bob", "Charlie", "David"};
String[] actions = {"login", "logout", "view_profile", "update_settings"};
String user = users[random.nextInt(users.length)];
String action = actions[random.nextInt(actions.length)];
// Add user and action to thread context
ThreadContext.put("user", user);
ThreadContext.put("action", action);
// Log different user actions with appropriate levels
switch (action) {
case "login":
logger.info("User logged in successfully");
break;
case "logout":
logger.info("User logged out");
break;
case "view_profile":
logger.debug("User viewed their profile");
break;
case "update_settings":
logger.info("User updated their settings");
break;
}
}
// Simulate database operations and log them
private static void simulateDatabaseOperations(Random random) {
String[] operations = {"select", "insert", "update", "delete"};
String operation = operations[random.nextInt(operations.length)];
long duration = random.nextInt(1000);
// Add operation and duration to thread context
ThreadContext.put("operation", operation);
ThreadContext.put("duration", String.valueOf(duration));
// Log slow database operations as warnings
if (duration > 500) {
logger.warn("Slow database operation detected");
} else {
logger.debug("Database operation completed");
}
// Simulate database connection loss with 5% probability
if (random.nextInt(20) == 0) {
logger.error("Database connection lost", new SQLException("Connection timed out"));
}
}
// Simulate security events and log them
private static void simulateSecurityEvents(Random random) {
String[] events = {"failed_login", "password_change", "role_change", "suspicious_activity"};
String event = events[random.nextInt(events.length)];
ThreadContext.put("security_event", event);
// Log different security events with appropriate levels
switch (event) {
case "failed_login":
securityLogger.warn("Failed login attempt");
break;
case "password_change":
securityLogger.info("User changed their password");
break;
case "role_change":
securityLogger.info("User role was modified");
break;
case "suspicious_activity":
securityLogger.error("Suspicious activity detected", new SecurityException("Potential breach attempt"));
break;
}
}
// Simulate performance metrics and log them
private static void simulatePerformanceMetrics(Random random) {
String[] metrics = {"cpu_usage", "memory_usage", "disk_io", "network_latency"};
String metric = metrics[random.nextInt(metrics.length)];
double value = random.nextDouble() * 100;
// Add metric and value to thread context
ThreadContext.put("metric", metric);
ThreadContext.put("value", String.format("%.2f", value));
// Log high resource usage as warnings
if (value > 80) {
performanceLogger.warn("High resource usage detected");
} else {
performanceLogger.trace("Performance metric recorded");
}
}
// Custom exception classes for simulating errors
private static class SQLException extends Exception {
public SQLException(String message) {
super(message);
}
}
private static class SecurityException extends Exception {
public SecurityException(String message) {
super(message);
}
}
}
```
This class demonstrates the use of different log levels and also shows how to add context to your logs using `ThreadContext`.
## Forward log messages to Fluentd [#forward-log-messages-to-fluentd]
Fluentd is a popular open-source data collector used to forward logs from Log4j to Axiom. The Log4j configuration is already set up to send logs to Fluentd using the Socket appender. Fluentd acts as a unified logging layer, allowing you to collect, process, and forward logs from various sources to different destinations.
### Configure the Fluentd.conf file [#configure-the-fluentdconf-file]
To configure Fluentd, create a configuration file. Create a new file named `fluentd.conf` in your project root directory with the following content:
```xml
@type forward
bind 0.0.0.0
port 24224
@type multi_format
format json
time_key timeMillis
time_type string
time_format %Q
@type record_transformer
tag java.log4j
@type http
endpoint https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME
headers {"Authorization":"Bearer API_TOKEN"}
json_array true
@type memory
flush_interval 5s
chunk_limit_size 5m
total_limit_size 10m
@type json
```
This configuration does the following:
1. Set up a forward input plugin to receive logs from Log4j.
2. Add a `java.log4j` tag to all logs.
3. Forward the logs to Axiom using the HTTP output plugin.
### Create the Dockerfile [#create-the-dockerfile]
To simplify the deployment of the Java app and Fluentd, use Docker. Create a new file named `Dockerfile` in your project root directory with the following content:
```yaml
# Build stage
FROM maven:3.8.1-openjdk-11-slim AS build
WORKDIR /usr/src/app
COPY pom.xml .
COPY src ./src
COPY log4j2.xml .
RUN mvn clean package
# Runtime stage
FROM openjdk:11-jre-slim
WORKDIR /usr/src/app
RUN apt-get update && \
apt-get install -y --no-install-recommends \
ruby \
ruby-dev \
build-essential && \
gem install fluentd --no-document && \
fluent-gem install fluent-plugin-multi-format-parser && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
COPY --from=build /usr/src/app/target/log4j-axiom-test-1.0-SNAPSHOT.jar .
COPY fluentd.conf /etc/fluent/fluent.conf
COPY log4j2.xml .
# Create startup script
RUN echo '#!/bin/sh\n\
fluentd -c /etc/fluent/fluent.conf &\n\
sleep 5\n\
java -Dlog4j.configurationFile=log4j2.xml -jar log4j-axiom-test-1.0-SNAPSHOT.jar\n'\
> /usr/src/app/start.sh && chmod +x /usr/src/app/start.sh
EXPOSE 24224
CMD ["/usr/src/app/start.sh"]
```
This Dockerfile does the following:
1. Build the Java app.
2. Set up a runtime environment with Java and Fluentd.
3. Copy the necessary files and configurations.
4. Create a startup script to run both Fluentd and the Java app.
### Build and run the Dockerfile [#build-and-run-the-dockerfile]
1. To build the Docker image, run the following command in your project root directory:
```bash
docker build -t log4j-axiom-test .
```
2. Run the container with the following:
```bash
docker run -p 24224:24224 log4j-axiom-test
```
This command starts the container, running both Fluentd and your Java app.
## View logs in Axiom [#view-logs-in-axiom]
Now that your app is running and sending logs to Axiom, you can view them in the Axiom dashboard. Log in to your Axiom account and go to the dataset you specified in the Fluentd configuration.
Logs appear in real-time, with various log levels and context information added.
## Logging in Log4j best practices [#logging-in-log4j-best-practices]
* Use appropriate log levels: Reserve ERROR and FATAL for serious issues, use WARN for potential problems, and INFO for general app flow.
* Include context: Add relevant information to your logs using ThreadContext or by including important variables in your log messages.
* Use structured logging: Log in JSON format to make it easier to parse, and later, analyze the logs using [APL](https://axiom.co/docs/apl/introduction).
* Log actionable information: Include enough detail in your logs to understand and potentially reproduce issues.
* Use parameterized logging: Instead of string concatenation, use Log4j’s support for parameterized messages to improve performance.
* Configure appenders appropriately: Use asynchronous appenders for better performance in high-throughput scenarios.
* Regularly review and maintain your logs: Periodically check your logging configuration and the logs themselves to ensure they’re providing value.
---
# Send logs from a .NET app
Source: https://axiom.co/docs/guides/send-logs-from-dotnet
* [Install the .NET SDK](https://dotnet.microsoft.com/download).
## Option 1: Using HTTP Client [#option-1-using-http-client]
### Create a new .NET project [#create-a-new-net-project]
Create a new .NET project. In your terminal, go to the directory where you want to create your project. Run the following command to create a new console app named `AxiomLogs`.
```bash
dotnet new console -n AxiomLogs
```
### Install packages [#install-packages]
Install the packages for your project. Use the `Microsoft.AspNet.WebApi.Client` package to make HTTP requests to the Axiom API. Run the following command to install the package:
```bash
dotnet add package Microsoft.AspNet.WebApi.Client
```
### Configure the Axiom logger [#configure-the-axiom-logger]
Create a class to handle logging to Axiom. Create a new file named `AxiomLogger.cs` in your project directory with the following content:
```csharp
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
public static class AxiomLogger
{
public static async Task LogToAxiom(string message, string logLevel)
{
// Create an instance of HttpClient to make HTTP requests
var client = new HttpClient();
// Specify the Axiom dataset name and construct the API endpoint URL
var datasetName = "DATASET_NAME";
var axiomEdgeDomain = "AXIOM_DOMAIN";
var axiomUri = $"https://{axiomEdgeDomain}/v1/ingest/{datasetName}";
// Replace with your Axiom API token
var apiToken = "API_TOKEN"; // Ensure your API token is correct
// Create an array of log entries, including the timestamp, message, and log level
var logEntries = new[]
{
new
{
timestamp = DateTime.UtcNow.ToString("o"),
message = message,
level = logLevel
}
};
// Serialize the log entries to JSON format using System.Text.Json.JsonSerializer
var content = new StringContent(System.Text.Json.JsonSerializer.Serialize(logEntries), Encoding.UTF8, "application/json");
// Set the authorization header with the Axiom API token
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", apiToken);
// Make a POST request to the Axiom API endpoint with the serialized log entries
var response = await client.PostAsync(axiomUri, content);
// Check the response status code
if (!response.IsSuccessStatusCode)
{
// If the response isn’t successful, print the error details
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Failed to send log: {response.StatusCode}\n{responseBody}");
}
else
{
// If the response is successful, print "Log sent successfully."
Console.WriteLine("Log sent successfully.");
}
}
}
```
### Configure the main program [#configure-the-main-program]
Now that the Axiom logger is in place, update the main program so it can be used. Open the `Program.cs` file and replace its contents with the following code:
```csharp
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
// Log the application startup event with an "INFO" log level
await AxiomLogger.LogToAxiom("Application started", "INFO");
// Call the SimulateOperations method to simulate various application operations
await SimulateOperations();
// Log the .NET runtime version information with an "INFO" log level
await AxiomLogger.LogToAxiom($"CLR version: {Environment.Version}", "INFO");
// Log the application shutdown event with an "INFO" log level
await AxiomLogger.LogToAxiom("Application shutting down", "INFO");
}
static async Task SimulateOperations()
{
// Log the start of operations with a "DEBUG" log level
await AxiomLogger.LogToAxiom("Starting operations", "DEBUG");
// Log the database connection event with a "DEBUG" log level
await AxiomLogger.LogToAxiom("Connecting to database", "DEBUG");
await Task.Delay(500); // Simulated delay
// Log the successful database connection with an "INFO" log level
await AxiomLogger.LogToAxiom("Connected to database successfully", "INFO");
// Log the user data retrieval event with a "DEBUG" log level
await AxiomLogger.LogToAxiom("Retrieving user data", "DEBUG");
await Task.Delay(1000);
// Log the number of retrieved user records with an "INFO" log level
await AxiomLogger.LogToAxiom("Retrieved 100 user records", "INFO");
// Log the user preference update event with a "DEBUG" log level
await AxiomLogger.LogToAxiom("Updating user preferences", "DEBUG");
await Task.Delay(800);
// Log the successful user preference update with an "INFO" log level
await AxiomLogger.LogToAxiom("Updated user preferences successfully", "INFO");
try
{
// Log the payment processing event with a "DEBUG" log level
await AxiomLogger.LogToAxiom("Processing payments", "DEBUG");
await Task.Delay(1500);
// Intentionally throw an exception to demonstrate error logging
throw new Exception("Payment gateway unavailable");
}
catch (Exception ex)
{
// Log the payment processing failure with an "ERROR" log level
await AxiomLogger.LogToAxiom($"Payment processing failed: {ex.Message}", "ERROR");
}
// Log the email notification sending event with a "DEBUG" log level
await AxiomLogger.LogToAxiom("Sending email notifications", "DEBUG");
await Task.Delay(1200);
// Log the number of sent email notifications with an "INFO" log level
await AxiomLogger.LogToAxiom("Sent 50 email notifications", "INFO");
// Log the high memory usage detection with a "WARN" log level
await AxiomLogger.LogToAxiom("Detected high memory usage", "WARN");
await Task.Delay(500);
// Log the memory usage normalization with an "INFO" log level
await AxiomLogger.LogToAxiom("Memory usage normalized", "INFO");
// Log the completion of operations with a "DEBUG" log level
await AxiomLogger.LogToAxiom("Operations completed", "DEBUG");
}
}
```
This code simulates various app operations and logs messages at different levels (DEBUG, INFO, WARN, ERROR) to Axiom.
### Project file configuration [#project-file-configuration]
Ensure your `axiomlogs.csproj` file is configured with the package reference. The file should look like this:
```xml
Exe
net6.0
enable
enable
```
### Build and run the app [#build-and-run-the-app]
To build and run the app, go to the project directory in your terminal and run the following command:
```bash
dotnet build
dotnet run
```
This command builds the project and runs the app. You see the log messages being sent to Axiom, and the console displays `Log sent successfully.` for each log entry.
## Option 2: Using Serilog [#option-2-using-serilog]
### Install Serilog Packages [#install-serilog-packages]
Add Serilog and the necessary extensions to your project. You need the `Serilog`, `Serilog.Sinks.Http`, `Serilog.Formatting.Elasticsearch` and `Serilog.Formatting.Json` packages.
```bash
dotnet add package Serilog
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.Http
dotnet add package Serilog.Formatting.Elasticsearch
dotnet add package Microsoft.Extensions.Configuration
```
### Configure Serilog [#configure-serilog]
In your `Program.cs` or a startup configuration file, set up Serilog to use the HTTP sink. Configure the sink to point to the Axiom ingestion API endpoint.
```csharp
using Serilog;
using Serilog.Formatting.Elasticsearch;
using Serilog.Sinks.Http;
using System.Net.Http;
using System.Net.Http.Headers;
using System.IO;
using Microsoft.Extensions.Configuration;
public class AxiomConfig
{
public const string DatasetName = "DATASET_NAME";
public const string ApiToken = "API_TOKEN";
public const string ApiUrl = "https://AXIOM_DOMAIN/v1";
}
public class AxiomHttpClient : IHttpClient
{
private readonly HttpClient _httpClient;
public AxiomHttpClient()
{
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", AxiomConfig.ApiToken);
}
public void Configure(IConfiguration configuration)
{
}
public async Task PostAsync(string requestUri, Stream contentStream, CancellationToken cancellationToken = default)
{
var content = new StreamContent(contentStream);
content.Headers.Add("Content-Type", "application/json");
return await _httpClient.PostAsync(requestUri, content, cancellationToken).ConfigureAwait(false);
}
public void Dispose()
{
_httpClient?.Dispose();
}
}
public class Program
{
public static async Task Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.Http(
requestUri: $"{AxiomConfig.ApiUrl}/ingest/{AxiomConfig.DatasetName}",
queueLimitBytes: null,
textFormatter: new ElasticsearchJsonFormatter(renderMessageTemplate: false, inlineFields: true),
httpClient: new AxiomHttpClient()
)
.CreateLogger();
try
{
Log.Information("Application started on .NET 8");
await SimulateOperations();
Log.Information($"Runtime version: {Environment.Version}");
Log.Information("Application shutting down");
}
catch (Exception ex)
{
Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
await Log.CloseAndFlushAsync();
}
}
static async Task SimulateOperations()
{
Log.Debug("Starting operations");
Log.Debug("Connecting to database");
await Task.Delay(500);
Log.Information("Connected to database successfully");
Log.Debug("Retrieving user data");
await Task.Delay(1000);
Log.Information("Retrieved 100 user records");
Log.Debug("Updating user preferences");
await Task.Delay(800);
Log.Information("Updated user preferences successfully");
try
{
Log.Debug("Processing payments");
await Task.Delay(1500);
throw new Exception("Payment gateway unavailable");
}
catch (Exception ex)
{
Log.Error(ex, "Payment processing failed: {ErrorMessage}", ex.Message);
}
Log.Debug("Sending email notifications");
await Task.Delay(1200);
Log.Information("Sent 50 email notifications");
Log.Warning("Detected high memory usage: {UsagePercentage}%", 85);
await Task.Delay(500);
Log.Information("Memory usage normalized");
Log.Debug("Operations completed");
}
}
```
### Project file configuration [#project-file-configuration-1]
Ensure your `axiomlogs.csproj` file is configured with the package references. The file should look like this:
```xml
Exe
net8.0
enable
enable
SerilogApp
SerilogApp
```
### Build and run the app [#build-and-run-the-app-1]
To build and run the app, go to the project directory in your terminal and run the following commands:
```bash
dotnet build
dotnet run
```
This command builds the project and runs the app. You see the log messages being sent to Axiom.
## Option 3: Using NLog [#option-3-using-nlog]
### Install NLog Packages [#install-nlog-packages]
You need NLog and potentially an extension for HTTP targets.
```bash
dotnet add package NLog
dotnet add package NLog.Web.AspNetCore
dotnet add package NLog.Targets.Http
```
### Configure NLog [#configure-nlog]
Set up NLog by creating an `NLog.config` file or configuring it programmatically. Here is an example configuration for `NLog` using an HTTP target:
```xml
```
### Configure the main program [#configure-the-main-program-1]
Update the main program to use `NLog`. In your `Program.cs` file:
```csharp
using NLog;
using NLog.Web;
var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
class Program
{
static async Task Main(string[] args)
{
logger.Info("Application started");
await SimulateOperations();
logger.Info($"CLR version: {Environment.Version}");
logger.Info("Application shutting down");
}
static async Task SimulateOperations()
{
logger.Debug("Starting operations");
logger.Debug("Connecting to database");
await Task.Delay(500); // Simulated delay
logger.Info("Connected to database successfully");
logger.Debug("Retrieving user data");
await Task.Delay(1000);
logger.Info("Retrieved 100 user records");
logger.Debug("Updating user preferences");
await Task.Delay(800);
logger.Info("Updated user preferences successfully");
try
{
logger.Debug("Processing payments");
await Task.Delay(1500);
throw new Exception("Payment gateway unavailable");
}
catch (Exception ex)
{
logger.Error($"Payment processing failed: {ex.Message}");
}
logger.Debug("Sending email notifications");
await Task.Delay(1200);
logger.Info("Sent 50 email notifications");
logger.Warn("Detected high memory usage");
await Task.Delay(500);
logger.Info("Memory usage normalized");
logger.Debug("Operations completed");
}
}
```
### Project file configuration [#project-file-configuration-2]
Ensure your `axiomlogs.csproj` file is configured with the package references. The file should look like this:
```xml
Exe
net6.0
enable
enable
```
### Build and run the app [#build-and-run-the-app-2]
To build and run the app, go to the project directory in your terminal and run the following commands:
```bash
dotnet build
dotnet run
```
This command builds the project and runs the app. You should see the log messages being sent to Axiom.
## Best practices for logging [#best-practices-for-logging]
To make your logging more effective, consider the following best practices:
* Include relevant information such as user IDs, request details, and system state in your log messages to provide context when investigating issues.
* Use different log levels (DEBUG, INFO, WARN, ERROR) to categorize the severity and importance of log messages. This allows you to filter and analyze logs more effectively
* Use structured logging formats like JSON to make it easier to parse and analyze log data
## Conclusion [#conclusion]
This guide covers the steps to send logs from a C# .NET app to Axiom. By following these instructions and adhering to logging best practices, you can effectively monitor your app, diagnose issues, and gain valuable insights into its behavior.
---
# Send logs from Laravel to Axiom
Source: https://axiom.co/docs/guides/send-logs-from-laravel
This guide explains integrating Axiom as a logging solution in a Laravel app. Using Axiom’s capabilities with a custom log channel, you can efficiently send your app’s logs to Axiom for storage, analysis, and monitoring. This integration uses Monolog, Laravel’s underlying logging library, to create a custom logging handler that forwards logs to Axiom.
* PHP development [environment](https://www.php.net/manual/en/install.php)
* [Composer](https://laravel.com/docs/11.x/installation) installed on your system
* Laravel app setup
## Installation [#installation]
### Create a Laravel project [#create-a-laravel-project]
Create a new Laravel project:
```bash
composer create-project --prefer-dist laravel/laravel laravel-axiom-logger
```
## Exploring the logging config file [#exploring-the-logging-config-file]
In your Laravel project, the `config` directory contains several configurations on how different parts of your app work, such as how it connects to the database, manages sessions, and handles caching. Among these files, **`logging.php`** identifies how you can define your app logs activities and errors. This file is designed to let you specify where your logs go: a file, a cloud service, or other destinations. The configuration file below includes the Axiom logging setup.
```bash
code config/logging.php
```
```php
env('LOG_CHANNEL', 'stack'),
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => false,
],
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'axiom' => [
'driver' => 'monolog',
'handler' => App\Logging\AxiomHandler::class,
'level' => env('LOG_LEVEL', 'debug'),
'with' => [
'apiToken' => env('AXIOM_TOKEN'),
'dataset' => env('AXIOM_DATASET'),
'axiomUrl' => env('AXIOM_HOST'),
],
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
'replace_placeholders' => true,
],
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
'processors' => [PsrLogMessageProcessor::class],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => LOG_USER,
'replace_placeholders' => true,
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
];
```
At the start of the `logging.php` file in your Laravel project, you’ll find some Monolog handlers like `NullHandler`, `StreamHandler`, and a few more. This shows that Laravel uses Monolog to help with logging, which means it can do a lot of different things with logs.
### Default log channel [#default-log-channel]
The `default` configuration specifies the primary channel Laravel uses for logging. In this setup, this is set through the **`.env`** file with the **`LOG_CHANNEL`** variable, which you’ve set to **`axiom`**. This means that, by default, log messages will be sent to the Axiom channel, using the custom handler you’ve defined to send logs to the dataset.
```bash
LOG_CHANNEL=axiom
AXIOM_TOKEN=API_TOKEN
AXIOM_DATASET=DATASET_NAME
AXIOM_HOST=AXIOM_DOMAIN
LOG_LEVEL=debug
LOG_DEPRECATIONS_CHANNEL=null
```
### Deprecations log channel [#deprecations-log-channel]
The `deprecations` channel is configured to handle logs about deprecated features in PHP and libraries, helping you prepare for updates. By default, it’s set to ignore these warnings, but you can adjust this to direct deprecation logs to a specific channel if needed.
```php
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => false,
],
```
### Configuration log channel [#configuration-log-channel]
The heart of the `logging.php` file lies within the **`channels`** array where you define all available logging channels. The configuration highlights channels like **`single`**, **`axiom`**, and **`daily`**, each serving different logging purposes:
```php
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'axiom' => [
'driver' => 'monolog',
'handler' => App\Logging\AxiomHandler::class,
'level' => env('LOG_LEVEL', 'debug'),
'with' => [
'apiToken' => env('AXIOM_TOKEN'),
'dataset' => env('AXIOM_DATASET'),
'axiomUrl' => env('AXIOM_HOST'),
],
],
```
* **Single**: Designed for simplicity, the **`single`** channel writes logs to a single file. It’s a straightforward solution for tracking logs without needing complex log management strategies.
* Axiom: The custom **`axiom`** channel sends logs to your specified Axiom dataset, providing advanced log management capabilities. This integration enables powerful log analysis and monitoring, supporting better insights into your app’s performance and issues.
* **Daily**: This channel rotates logs daily, keeping your log files manageable and making it easier to navigate log entries over time.
Each channel can be customized further, such as adjusting the log level to control the verbosity of logs captured. The **`LOG_LEVEL`** environment variable sets this, defaulting to **`debug`** for capturing detailed log information.
## Getting started with log levels in Laravel [#getting-started-with-log-levels-in-laravel]
Laravel lets you choose from eight different levels of importance for your log messages, just like a list of warnings from very serious to just for info. Here’s what each level means, starting with the most severe:
* **EMERGENCY**: Your app is broken and needs immediate attention.
* **ALERT**: similar to `EMERGENCY`, but less severe.
* **CRITICAL**: Critical errors within the main parts of your app.
* **ERROR**: error conditions in your app.
* **WARNING**: something unusual happened that may need to be addressed later.
* **NOTICE**: Important info, but not a warning or error.
* **INFO**: General updates about what your app is doing.
* **DEBUG**: used to record some debugging messages.
Not every situation fits into one of these levels. For example, in an online store, you might use **INFO** to log when someone buys something and **ERROR** if a payment doesn’t go through because of a problem.
Here’s a simple way to log messages at each level in Laravel:
```php
use Illuminate\Support\Facades\Log;
Log::debug("Checking details.");
Log::info("User logged in.");
Log::notice("User tried a feature.");
Log::warning("Feature might not work as expected.");
Log::error("Feature failed to load.");
Log::critical("Major issue with the app.");
Log::alert("Immediate action needed.");
Log::emergency("The app is down.");
```
Output:
```php
[2023-09-01 00:00:00] local.DEBUG: Checking details.
[2023-09-01 00:00:00] local.INFO: User logged in.
[2023-09-01 00:00:00] local.NOTICE: User tried a feature.
[2023-09-01 00:00:00] local.WARNING: Feature might not work as expected.
[2023-09-01 00:00:00] local.ERROR: Feature failed to load.
[2023-09-01 00:00:00] local.CRITICAL: Major issue with the app.
[2023-09-01 00:00:00] local.ALERT: Immediate action needed.
[2023-09-01 00:00:00] local.EMERGENCY: The app is down.
```
## Creating the custom logger class [#creating-the-custom-logger-class]
This section explains how to create the custom logger class designed for sending your Laravel app’s logs to Axiom. This class named `AxiomHandler` , extends Monolog’s **`AbstractProcessingHandler`** giving you a structured way to handle log messages and forward them to Axiom.
* **Initializing cURL**: The **`initializeCurl`** method sets up a cURL handle to communicate with Axiom’s API. It prepares the request with the appropriate headers, including the authorization header that uses your Axiom API token and content type set to **`application/json` .**
* **Handling errors**: If there’s an error during the cURL request, it’s logged to PHP’s error log. This helps in diagnosing issues with log forwarding without disrupting your app’s normal operations.
* **Formatting logs**: Lastly, specify the log message format using the **`getDefaultFormatter`** method. By default, this uses Monolog’s **`JsonFormatter`** to ensure the log messages are JSON encoded, making them easy to parse and analyze in Axiom.
```php
apiToken = $apiToken;
$this->dataset = $dataset;
$this->axiomUrl = $axiomUrl;
}
private function initializeCurl(): \CurlHandle
{
$endpoint = "https://{$this->axiomUrl}/v1/ingest/{$this->dataset}";
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $this->apiToken,
'Content-Type: application/json',
]);
return $ch;
}
protected function write(LogRecord $record): void
{
$ch = $this->initializeCurl();
$data = [
'message' => $record->message,
'context' => $record->context,
'level' => $record->level->getName(),
'channel' => $record->channel,
'extra' => $record->extra,
];
$payload = json_encode([$data]);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_exec($ch);
if (curl_errno($ch)) {
// Optionally log the curl error to PHP error log
error_log('Curl error: ' . curl_error($ch));
}
curl_close($ch);
}
protected function getDefaultFormatter(): FormatterInterface
{
return new \Monolog\Formatter\JsonFormatter();
}
}
```
## Creating the test controller [#creating-the-test-controller]
This section demonstrates the process of verifying that your custom Axiom logger is properly set up and functioning within your Laravel app. To do this, create a simple test controller with a method designed to send a log message using the Axiom channel. Following this, define a route that triggers this logging action, allowing you to easily test the logger by accessing a specific URL in your browser or using a tool like cURL.
Create a new controller called `TestController` within your `app/Http/Controllers` directory. In this controller, add a method named `logTest` . This method will use Laravel’s logging to send a test log message to your Axiom dataset. Here’s how you set it up:
```php
check() ? auth()->user()->id : 'guest';
return $record;
};
// Get the Monolog instance for the 'axiom' channel and push the custom processor
$logger = Log::channel('axiom')->getLogger();
if ($logger instanceof Logger) {
$logger->pushProcessor($customProcessor);
}
Log::channel('axiom')->debug("Checking details.", ['action' => 'detailCheck', 'status' => 'initiated']);
Log::channel('axiom')->info("User logged in.", ['user_id' => 'exampleUserId', 'method' => 'standardLogin']);
Log::channel('axiom')->info("User tried a feature.", ['feature' => 'experimentalFeatureX', 'status' => 'trial']);
Log::channel('axiom')->warning("Feature might not work as expected.", ['feature' => 'experimentalFeature', 'warning' => 'betaStage']);
Log::channel('axiom')->warning("Feature failed to load.", ['feature' => 'featureY', 'error_code' => 500]);
Log::channel('axiom')->error("Major issue with the app.", ['system' => 'paymentProcessing', 'error' => 'serviceUnavailable']);
Log::channel('axiom')->warning("Immediate action needed.", ['issue' => 'security', 'level' => 'high']);
Log::channel('axiom')->error("The app is down.", ['system' => 'entireApplication', 'status' => 'offline']);
return 'Log messages sent to Axiom';
}
}
```
This method targets the `axiom` channel, which you previously configured to forward logs to your Axiom account. The message **`Testing Axiom logger!`** should then appear in your Axiom dataset, confirming that the logger is working as expected.
## Registering the route [#registering-the-route]
Next, you need to make this test accessible via a web route. Open your `routes/web.php` file and add a new route that points to the **`logTest`** method in your **`TestController`**. This enables you to trigger the log message by visiting a specific URL in your web browser.
```php
## Conclusion [#conclusion]
This guide has introduced you to integrating Axiom for logging in Laravel apps. You’ve learned how to create a custom logger, configure log channels, and understand the significance of log levels. With this knowledge, you’re set to track errors and analyze log data effectively using Axiom.
---
# Send logs from a Ruby on Rails app using Faraday
Source: https://axiom.co/docs/guides/send-logs-from-ruby-on-rails
This guide provides step-by-step instructions on how to send logs from a Ruby on Rails app to Axiom using the Faraday library. By following this guide, you configure your Rails app to send logs to Axiom, allowing you to monitor and analyze your app logs effectively.
* Install a [Ruby version manager](https://www.ruby-lang.org/en/documentation/installation/) like `rbenv` and use it to install the latest Ruby version.
* Install [Ruby on Rails](https://guides.rubyonrails.org/v5.0/getting_started.html) using the `gem install rails` command.
## Set up the Ruby on Rails app [#set-up-the-ruby-on-rails-app]
1. Create a new Rails app using the `rails new myapp` command.
2. Navigate to the app directory: `cd myapp`
## Setting up the Gemfile [#setting-up-the-gemfile]
Open the `Gemfile` in your Rails app, and then add the following gems:
```ruby
gem 'faraday'
gem 'dotenv-rails', groups: [:development, :test]
```
Install the dependencies by running `bundle install`.
## Create and configure the Axiom logger [#create-and-configure-the-axiom-logger]
1. Create a new file named `axiom_logger.rb` in the `app/services` directory of your Rails app.
2. Add the following code to `axiom_logger.rb`:
```ruby
# app/services/axiom_logger.rb
require 'faraday'
require 'json'
class AxiomLogger
def self.send_log(log_data)
dataset_name = "DATASET_NAME"
axiom_ingest_api_url = "https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME"
ingest_token = "API_TOKEN"
conn = Faraday.new(url: axiom_ingest_api_url) do |faraday|
faraday.request :url_encoded
faraday.adapter Faraday.default_adapter
end
wrapped_log_data = [log_data]
response = conn.post do |req|
req.headers['Content-Type'] = 'application/json'
req.headers['Authorization'] = "Bearer #{ingest_token}"
req.body = wrapped_log_data.to_json
end
puts "AxiomLogger Response status: #{response.status}, body: #{response.body}"
if response.status != 200
Rails.logger.error "Failed to send log to Axiom: #{response.body}"
end
end
end
```
## Test with the Axiom logger [#test-with-the-axiom-logger]
1. Create a new file named `axiom_logger_test.rb` in the `config/initializers` directory.
2. Add the following code to `axiom_logger_test.rb`:
```ruby
# config/initializers/axiom_logger_test.rb
Rails.application.config.after_initialize do
puts "Sending test logs to Axiom using Ruby on Rails Faraday..."
# Info logs
AxiomLogger.send_log({ message: "Application started successfully", level: "info", service: "initializer" })
AxiomLogger.send_log({ message: "User authentication successful", level: "info", service: "auth" })
AxiomLogger.send_log({ message: "Data fetched from external API", level: "info", service: "external_api" })
AxiomLogger.send_log({ message: "Email notification sent", level: "info", service: "email" })
# Warn logs
AxiomLogger.send_log({ message: "API request took longer than expected", level: "warn", service: "external_api", duration: 1500 })
AxiomLogger.send_log({ message: "User authentication token expiring soon", level: "warn", service: "auth", user_id: 123 })
AxiomLogger.send_log({ message: "Low disk space warning", level: "warn", service: "system", disk_usage: "85%" })
AxiomLogger.send_log({ message: "Non-critical configuration issue detected", level: "warn", service: "config" })
# Error logs
AxiomLogger.send_log({ message: "Database connection error", level: "error", service: "database", error: "Timeout" })
AxiomLogger.send_log({ message: "Failed to process payment", level: "error", service: "payment", user_id: 456, error: "Invalid card" })
AxiomLogger.send_log({ message: "Unhandled exception occurred", level: "error", service: "application", exception: "NoMethodError" })
AxiomLogger.send_log({ message: "Third-party API returned an error", level: "error", service: "integration", status_code: 500 })
# Debug logs
AxiomLogger.send_log({ message: "Request parameters", level: "debug", service: "api", params: { page: 1, limit: 20 } })
AxiomLogger.send_log({ message: "Response headers", level: "debug", service: "api", headers: { "Content-Type" => "application/json" } })
AxiomLogger.send_log({ message: "User object details", level: "debug", service: "user", user: { id: 789, name: "Axiom Observability", email: "support@axiom.co" } })
AxiomLogger.send_log({ message: "Cache hit for key", level: "debug", service: "cache", key: "popular_products" })
end
```
Each log entry includes a message, level, service, and additional relevant data.
* Info logs:
* Application started successfully
* User authentication successful
* Data fetched from external API
* Email notification sent
* Warn logs:
* API request took longer than expected (including duration)
* User authentication token expiring soon (including user ID)
* Low disk space warning (including disk usage percentage)
* Non-critical configuration issue detected
* Error logs:
* Database connection error (including error message)
* Failed to process payment (including user ID and error message)
* Unhandled exception occurred (including exception type)
* Third-party API returned an error (including status code)
* Debug logs:
* Request parameters (including parameter values)
* Response headers (including header key-value pairs)
* User object details (including user attributes)
* Cache hit for key (including cache key)
Adjust the log messages, services, and additional data according to your app’s specific requirements and context.
## Create the `log.rake` tasks [#create-the-lograke-tasks]
1. Create a new directory named `tasks` in the `lib` directory of your Rails app.
2. Create a new file named `log.rake` in the `lib/tasks` directory.
3. Add the following code to `log.rake`:
```ruby
# lib/tasks/log.rake
namespace :log do
desc "Send a test log to Axiom"
task send_test_log: :environment do
log_data = { message: "Hello, Axiom from Rake!", level: "info", service: "rake_task" }
AxiomLogger.send_log(log_data)
puts "Test log sent to Axiom."
end
end
```
This code defines a Rake task that sends a test log to Axiom when invoked.
## View logs in Axiom [#view-logs-in-axiom]
1. Start your Rails server by running `rails server`.
2. Go to `http://localhost:3000` to trigger the test log from the initializer.
3. Run the Rake task to send another test log by executing `rails log:send_test_log` in your terminal.
4. In Axiom, go to the Stream tab, and then select the dataset where you send the logs.
5. You see the test logs appear allowing you to view and analyze your event data coming from your Ruby on Rails app.
## Conclusion [#conclusion]
You have successfully set up your Ruby on Rails app to send logs to Axiom using the Faraday library. With this configuration, you can centralize your app logs and use Axiom’s powerful features like [APL](/apl/introduction) for log querying, monitoring, and observing various log levels and types effectively.
---
# Axiom transport for Winston logger
Source: https://axiom.co/docs/guides/winston
## Install SDK [#install-sdk]
To install the SDK, run the following:
```shell
npm install @axiomhq/winston
```
## Import the Axiom transport for Winston [#import-the-axiom-transport-for-winston]
```js
import { WinstonTransport as AxiomTransport } from '@axiomhq/winston';
```
## Create a Winston logger instance [#create-a-winston-logger-instance]
```js
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
defaultMeta: { service: 'user-service' },
transports: [
// You can pass an option here. If you don’t, the transport is configured automatically
// using environment variables like `AXIOM_DATASET` and `AXIOM_TOKEN`
new AxiomTransport({
dataset: 'DATASET_NAME',
token: 'API_TOKEN',
}),
],
});
```
After setting up the Axiom transport for Winston, use the logger as usual:
```js
logger.log({
level: 'info',
message: 'Logger successfully setup',
});
```
### Error, exception, and rejection handling [#error-exception-and-rejection-handling]
To log errors, use the [`winston.format.errors`](https://github.com/winstonjs/logform#errors) formatter. For example:
```ts
import winston from 'winston';
import { WinstonTransport as AxiomTransport } from '@axiomhq/winston';
const { combine, errors, stack } = winston.format;
const axiomTransport = new AxiomTransport({ ... });
const logger = winston.createLogger({
// 8<----snip----
format: combine(errors({ stack: true }), json()),
// 8<----snip----
});
```
To automatically log exceptions and rejections, add the Axiom transport to the [`exceptionHandlers`](https://github.com/winstonjs/winston#exceptions) and [`rejectionHandlers`](https://github.com/winstonjs/winston#rejections). For example:
```ts
import winston from 'winston';
import { WinstonTransport as AxiomTransport } from '@axiomhq/winston';
const axiomTransport = new AxiomTransport({ ... });
const logger = winston.createLogger({
// 8<----snip----
transports: [axiomTransport],
exceptionHandlers: [axiomTransport],
rejectionHandlers: [axiomTransport],
// 8<----snip----
});
```
Running on Edge runtime isn’t supported.
## Configure region [#configure-region]
By default, the transport sends data to `api.axiom.co`. To target a specific edge region, set the `edge` option on `AxiomTransport` to the edge domain that matches the region your dataset lives in:
```ts EU Central 1
new AxiomTransport({
dataset: 'DATASET_NAME',
token: 'API_TOKEN',
edge: 'eu-central-1.aws.edge.axiom.co',
});
```
```ts US East 1
new AxiomTransport({
dataset: 'DATASET_NAME',
token: 'API_TOKEN',
edge: 'us-east-1.aws.edge.axiom.co',
});
```
The following edge domains are available:
| Edge deployment | Base domain for ingest and query |
| ------------------ | -------------------------------- |
| US East 1 (AWS) | `us-east-1.aws.edge.axiom.co` |
| EU Central 1 (AWS) | `eu-central-1.aws.edge.axiom.co` |
For more information about edge deployments, see [Edge deployments](/reference/edge-deployments).
Always use the `edge` option to target a region. Don't put a regional hostname in `url` — `url` is reserved for non-ingest API operations and won't route ingest correctly. (This is unrelated to the Edge runtime note above, which is about where your code runs.)
### Transport options [#transport-options]
| Option | Required | Description |
| --------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
| `dataset` | yes | The Axiom dataset to ingest logs into. Falls back to the `AXIOM_DATASET` environment variable. |
| `token` | yes | An Axiom API token with `ingest` permission for the dataset. |
| `orgId` | no | Organization ID. Required when using a personal token. |
| `edge` | no | Edge domain for ingest, without scheme. Example: `eu-central-1.aws.edge.axiom.co`. Use this to target a region. |
| `edgeUrl` | no | Full edge URL with scheme. Takes precedence over `edge` if both are set. Useful for self-hosted or proxy setups. |
| `url` | no | Base URL for non-ingest API operations. |
| `onError` | no | Callback invoked when sending data fails. |
## Examples [#examples]
For more examples, see the [examples in GitHub](https://github.com/axiomhq/axiom-js/tree/main/examples/winston).
---
# Axiom adapter for Zap
Source: https://axiom.co/docs/guides/zap
Use the adapter of the Axiom Go SDK to send logs generated by the [uber-go/zap](https://github.com/uber-go/zap) library to Axiom.
The Axiom Go SDK is an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/axiom-go).
## Set up SDK [#set-up-sdk]
1. Install the Axiom Go SDK and configure your environment as explained in [Send data from Go app to Axiom](/guides/go).
2. In your Go app, import the `zap` package. It’s imported as an `adapter` so that it doesn’t conflict with the `uber-go/zap` package.
```go
import adapter "github.com/axiomhq/axiom-go/adapters/zap"
```
Alternatively, configure the adapter using [options](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/zap#Option) passed to the [New](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/zap#New) function:
```go
core, err := adapter.New(
adapter.SetDataset("DATASET_NAME"),
)
```
## Configure client [#configure-client]
To configure the underlying client manually, choose one of the following:
* Use [SetClient](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/zap#SetClient) to pass in the client you have previously created with [Send data from Go app to Axiom](/guides/go).
* Use [SetClientOptions](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/zap#SetClientOptions) to pass [client options](https://pkg.go.dev/github.com/axiomhq/axiom-go/axiom#Option) to the adapter.
```go
import (
"github.com/axiomhq/axiom-go/axiom"
adapter "github.com/axiomhq/axiom-go/adapters/zap"
)
// ...
core, err := adapter.New(
adapter.SetClientOptions(
axiom.SetPersonalTokenConfig("AXIOM_TOKEN"),
),
)
```
### Configure region [#configure-region]
By default, the adapter sends data to `api.axiom.co`. To target a specific edge region, pass `axiom.SetEdge` through `SetClientOptions` with the edge domain that matches the region your dataset lives in:
```go EU Central 1
core, err := adapter.New(
adapter.SetClientOptions(
axiom.SetAPITokenConfig("xaat-your-api-token"),
axiom.SetEdge("eu-central-1.aws.edge.axiom.co"),
),
)
```
```go US East 1
core, err := adapter.New(
adapter.SetClientOptions(
axiom.SetAPITokenConfig("xaat-your-api-token"),
axiom.SetEdge("us-east-1.aws.edge.axiom.co"),
),
)
```
The following edge domains are available:
| Edge deployment | Base domain for ingest and query |
| ------------------ | -------------------------------- |
| US East 1 (AWS) | `us-east-1.aws.edge.axiom.co` |
| EU Central 1 (AWS) | `eu-central-1.aws.edge.axiom.co` |
Edge endpoints require an API token (`xaat-`), not a personal token (`xapt-`). Use `axiom.SetAPITokenConfig` when targeting an edge region. You can also set `AXIOM_EDGE` or `AXIOM_EDGE_URL` in the environment instead of `axiom.SetEdge`. For a full edge URL such as a proxy, use `axiom.SetEdgeURL`, which takes precedence over `axiom.SetEdge`.
The adapter uses a buffer to batch events before sending them to Axiom. Flush this buffer explicitly by calling [Sync](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/zap#WriteSyncer.Sync). For more information, see the [zap documentation](https://pkg.go.dev/go.uber.org/zap/zapcore#WriteSyncer) and the [example in GitHub](https://github.com/axiomhq/axiom-go/blob/main/examples/zap/main.go).
## Reference [#reference]
For a full reference of the adapter’s functions, see the [Go Packages page](https://pkg.go.dev/github.com/axiomhq/axiom-go/adapters/zap).
---
# Interpret Axiom docs with LLMs
Source: https://axiom.co/docs/llms/llms-overview
## AI chat [#ai-chat]
To get an answer about Axiom from the documentation’s built-in AI assistant, press Cmd/Ctrl K , and then type your question.
## Interpret individual pages [#interpret-individual-pages]
To interpret individual pages of the documentation with an LLM, click in the top right, and then choose one of the following options:
* Copy the page in Markdown format and manually pass it to LLMs
* View the page in Markdown format
* Open the page in ChatGPT
* Open the page in Claude
## Interpret the full docs [#interpret-the-full-docs]
To make it easy for LLMs to interpret the documentation, Axiom offers the following based on the [/llms.txt standard](https://llmstxt.org/):
* [List of docs pages](https://axiom.co/docs/llms.txt)
* [Full documentation](https://axiom.co/docs/llms-full.txt)
* [List of query reference pages](/llms-apl.md)
Access one of the above documents and pass it to your LLM to start interpreting the Axiom documentation.
---
# Anomaly monitors
Source: https://axiom.co/docs/monitor-data/anomaly-monitors
Anomaly monitors allow you to aggregate your event data and compare the results of this aggregation to what can be considered normal for the query. When the results are too much above or below the value that Axiom expects based on the event history, the monitor enters the alert state. The monitor remains in the alert state until the results no longer deviate from the expected value. This can happen without the results returning to their previous level if they stabilize around a new value. An anomaly monitor sends you a notification each time it enters or exits the alert state.
## Create anomaly monitor [#create-anomaly-monitor]
To create an anomaly monitor, follow these steps:
1. Click the **Monitors** tab, and then click **New monitor**.
2. Click **Anomaly monitor**.
3. Name your monitor and add a description.
4. Configure the monitor using the following options:
* The comparison operator is the rule to apply when comparing the results to the expected value. The possible values are **above**, **below**, and **above or below**.
* The tolerance factor controls the sensitivity of the monitor. Axiom combines the tolerance factor with a measure of how much the results of your query tend to vary, and uses them to determine how much deviation from the expected value to tolerate before triggering the monitor. The higher the tolerance factor, the wider the tolerated range of deviation. When the results of the aggregation stay within this range, the monitor doesn’t trigger. When the results of the aggregation cross this range, the monitor triggers. The tolerance factor can be any positive numeric value.
* The frequency is how often the monitor runs. This is a positive integer number of minutes.
* The range is the time range for your query. This is a positive integer number of minutes. A longer time range allows the anomaly monitor to consider a larger number of datapoints when calculating the expected value.
* **Alert on no data** triggers the monitor when your query doesn’t return any data. Your query returns no data if no events match your filters and an aggregation used in the query is undefined. For example, you take the average of a field not present in any matching events. The monitor sends a notification when data returns after a no-data alert state. Notifications about deviations from the expected value take precedence over notifications about data returning after a no-data state.
* You can group by attributes when defining your query. By default, your monitor enters the alert state if any of the values returned for the group-by attributes deviate from the expected value, and remains in the alert state until none of the values returned deviates from the expected value. To trigger the monitor separately for each group that deviates from the expected value, enable **Notify by group**. At most one trigger notification is sent per monitor run. This option only has an effect if the monitor’s query groups by a non-time field.
* Toggle **Require seasonality** to compare the results to seasonal patterns in your data. For example, your query produces a time series that increases at the same time each morning. Without accounting for seasonality, the monitor compares to recent results only. By toggling **Require seasonality**, the monitor compares the results to the same time of the previous day or week and only triggers if the results deviate from the expected seasonal pattern.
5. Click **Add notifier**, and then select the notifiers that define how you want to receive notifications for this monitor. For more information, see [Notifiers](#notifiers).
6. To define your query, use one of the following options:
* To use the visual query builder, click **Simple query builder**. Click **Visualize** to select an aggregation method, and then click **Run query** to preview the results.
* To use Axiom Processing Language (APL), click **Advanced query language**. Write a query where the final clause uses the `summarize` operator and bins the results by `_time`, and then click **Run query** to preview the results. For more information, see [Introduction to APL](/apl/introduction).
In the preview, the boundary where the monitor triggers is displayed as a dashed line. Where there isn’t enough data to compute a boundary, the chart is grayed out. If the monitor preview shows that it alerts when you don’t want it to, try increasing the tolerance. Inversely, try decreasing the tolerance if the monitor preview shows that it doesn’t alert when you want it to.
7. Click **Create**.
You have created an anomaly monitor. Axiom alerts you when the results from your query are too high or too low compared to what’s expected based on the event history.
In the chart, the red dotted line displays the tolerance range around the expected value over time. When the results of the query cross this range, the monitor triggers.
## Examples [#examples]
For real-world use cases, see [Monitor examples](/monitor-data/monitor-examples).
---
# Configure monitors
Source: https://axiom.co/docs/monitor-data/configure-monitors
## Edit monitors [#edit-monitors]
To edit an existing monitor:
1. Click the Monitors tab.
2. Click the monitor in the list that you want to edit.
3. In the top right, click **Edit monitor**.
4. Make changes to the monitor.
5. Click **Save**.
## Disable monitors [#disable-monitors]
Disable a monitor to prevent it from running for a specific amount of time.
To disable a monitor:
1. Click the Monitors tab.
2. Click the monitor in the list that you want to disable.
3. In the top right, click **Disable monitor**.
4. Select the time period for which you want to disable the monitor.
5. Click **Disable monitor**.
Axiom automatically enables the monitor after the time period you specified.
## Enable monitors [#enable-monitors]
To enable a monitor:
1. Click the Monitors tab.
2. Click the monitor in the list that you want to enable.
3. In the top right, click **Enable monitor**.
4. Click **Enable monitor**.
## Clone monitors [#clone-monitors]
To clone a monitor:
1. Click the Monitors tab.
2. Click the monitor in the list that you want to delete.
3. In the top right, click **More**.
4. Click **Clone monitor**.
## Delete monitors [#delete-monitors]
To delete a monitor:
1. Click the Monitors tab.
2. Click the monitor in the list that you want to delete.
3. In the top right, click **More**.
4. Click **Delete monitor**.
---
# Configure notifiers
Source: https://axiom.co/docs/monitor-data/configure-notifiers
## Disable notifiers [#disable-notifiers]
Disable a monitor to prevent it from running for a specific amount of time.
To disable a notifier:
1. Click the Monitors tab.
2. In the left, click **Notifiers**.
3. Click the notifier in the list that you want to disable.
4. In the top right, click **Disable notifier**.
5. Select the time period for which you want to disable the notifier.
6. Click **Disable**.
Axiom automatically enables the notifier after the time period you specified.
## Enable notifiers [#enable-notifiers]
To enable a notifier:
1. Click the Monitors tab.
2. In the left, click **Notifiers**.
3. Click the notifier in the list that you want to enable.
4. In the top right, click **Enable notifier**.
5. Click **Enable**.
## Delete notifiers [#delete-notifiers]
To delete a notifier:
1. Click the Monitors tab.
2. In the left, click **Notifiers**.
3. Click the notifier in the list that you want to delete.
4. In the top right, click .
5. Click **Delete**.
---
# Custom webhook notifier
Source: https://axiom.co/docs/monitor-data/custom-webhook-notifier
Use a custom webhook notifier to connect your monitors to internal or external services. The webhook URL receives a POST request with a content type of `application/json` together with any other headers you specify.
To create a custom webhook notifier, follow these steps:
1. Click the **Monitors** tab, and then click **Manage notifiers** on the right.
2. Click **New notifier** on the top right.
3. Name your notifier.
4. Click **Custom webhook**.
5. In **Webhook URL**, enter the URL where you want to send the POST request.
6. Optional: To customize the content of your webhook, use the [Go template syntax](https://pkg.go.dev/text/template) to interact with these variables:
* `.Action` has value `Open` when the notification corresponds to a match monitor matching or a threshold monitor triggering, and has value `Closed` when the notification corresponds to a threshold monitor resolving.
* `.MonitorID` is the unique identifier for the monitor associated with the notification.
* `.Body` is the message body associated with the notification. When the notification corresponds to a match monitor, this is the matching event data. When the notification corresponds to a threshold monitor, this provides information about the value that gave rise to the monitor triggering or resolving.
* `.Description` is the description of the monitor associated with the notification.
* `.QueryEndTime` is the end time applied in the monitor query that gave rise to the notification.
* `.QueryStartTime` is the start time applied in the monitor query that gave rise to the notification.
* `.Timestamp` is the time the notification was generated.
* `.Title` is the name of the monitor associated with the notification.
* `.Value` is the value that gave rise to the monitor triggering or resolving. It’s only applicable if the notification corresponds to a threshold monitor. When a threshold monitor resolves because its query stops returning data for a series (for example, a grouped series that goes idle or drops out), there’s no current value to report and `.Value` is `0`.
* `.MatchedEvent` is a JSON object that represents the event that matched the criteria of the monitor. It’s only applicable if the notification corresponds to a match monitor.
* `.GroupKeys` and `.GroupValues` are JSON arrays that contain the keys and the values returned by the group-by attributes of your query. They’re only applicable if the APL query of the monitor groups by a non-time field.
You can fully customize the content of the webhook to match the requirements of your environment.
7. Optional: Add headers to the POST request sent to the webhook URL.
8. Click **Create**.
## Examples [#examples]
The example below is the default template for a custom webhook notification:
```json
{
"action": "{{.Action}}",
"event": {
"monitorID": "{{.MonitorID}}",
"body": "{{.Body}}",
"description": "{{.Description}}",
"queryEndTime": "{{.QueryEndTime}}",
"queryStartTime": "{{.QueryStartTime}}",
"timestamp": "{{.Timestamp}}",
"title": "{{.Title}}",
"value": {{.Value}},
"matchedEvent": {{jsonObject .MatchedEvent}},
"groupKeys": {{jsonArray .GroupKeys}},
"groupValues": {{jsonArray .GroupValues}}
}
}
```
Using the template above, the body of a POST request sent to the webhook URL for a threshold monitor triggering:
```json
{
"action": "Open",
"event": {
"monitorID": "CabI3w142069etTgd0",
"body": "Current value of 57347 is above or equal to the threshold value of 0",
"description": "",
"queryEndTime": "2024-06-28 14:55:57.631364493 +0000 UTC",
"queryStartTime": "2024-06-28 14:45:57.631364493 +0000 UTC",
"timestamp": "2024-06-28 14:55:57 +0000 UTC",
"title": "Axiom Monitor Test Triggered",
"value": 57347,
"matchedEvent": null,
"groupKeys": null,
"groupValues": null
}
}
```
The example template below formats the webhook message to match the [expectations of incident.io](https://api-docs.incident.io/tag/Alert-Events-V2/) using the monitor ID as the `deduplication_key`.
```json
{
"title": "{{.Title}}",
"description": "{{.Body}}",
"deduplication_key": "{{.MonitorID}}",
"status": "{{ if eq .Action "Open" }}firing{{ else }}resolved{{ end }}",
"metadata": {
"description": "{{.Description}}",
"value": {{.Value}}
},
"source_url": "https://app.axiom.co/{your-org-id-here}/monitors/{{.MonitorID}}"
}
```
---
# Discord notifier
Source: https://axiom.co/docs/monitor-data/discord-notifier
Use a Discord notifier to notify specific channels in your Discord server.
To create a Discord notifier, choose one of the following methods:
* [Create Discord notifier with a token](#create-discord-notifier-with-token)
* [Create Discord notifier with a webhook URL](#create-discord-notifier-with-webhook)
## Create Discord notifier with token [#create-discord-notifier-with-token]
In Discord, create a token and get the channel ID:
1. Go to [Discord .dev](https://discord.com/developers/applications) and create a new app.
2. Click **Bot > Add Bot > Reset Token** to get your Discord token.
3. Click **OAuth2 > URL Generator**, check the Bot scope and the Send Messages permission.
4. Open the generated URL to add the bot to your server.
5. Click **User Settings > Advanced**, and then enable developer mode.
6. Right-click a channel, and then click **Copy ID**.
7. Ensure the **Discord Bot** has the proper allow channel access permissions.
In Axiom:
1. Click the **Monitors** tab, and then click **Manage notifiers** on the right.
2. Click **New notifier** on the top right.
3. Name your notifier.
4. Click **Discord**.
5. Enter the token you have previously generated and the channel ID.
6. Click **Create**.
### Create Discord notifier with webhook [#create-discord-notifier-with-webhook]
1. In Discord, generate a webhook. For more information, see the [Discord documentation](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks).
2. In Axiom, click the **Monitors** tab, and then click **Manage notifiers** on the right.
3. Click **New notifier** on the top right.
4. Name your notifier.
5. Click **Discord Webhook**.
6. Enter the webhook URL you have previously generated.
7. Click **Create**.
---
# Email notifier
Source: https://axiom.co/docs/monitor-data/email-notifier
To create an email notifier, follow these steps:
1. Click the **Monitors** tab, and then click **Manage notifiers** on the right.
2. Click **New notifier** on the top right.
3. Name your notifier.
4. Click **Email**.
5. In the **Users** section, add the email addresses where you want to send notifications, and then click **+** on the right.
6. Click **Create**.
---
# Match monitors
Source: https://axiom.co/docs/monitor-data/match-monitors
Match monitors allow you to continuously filter your log data and send you matching events. Axiom sends a notification for each matching event. By default, the notification message contains the entire matching event in JSON format. When you define your match monitor using APL, you can control which event attributes to include in the notification message.
Axiom recommends using match monitors for alerting purposes only. A match monitor can send 10 notifications per minute and 500 notifications per day. A notification can usually include events up to 0.1 MB but the maximum size can be smaller depending on the type of the notifier.
## Create match monitor [#create-match-monitor]
To create a match monitor, follow these steps:
1. Click the **Monitors** tab, and then click **New monitor**.
2. Click **Match monitor**.
3. Name your monitor and add a description.
4. Click **Add notifier**, and then select the notifiers that define how you want to receive notifications for this monitor. For more information, see [Notifiers](#notifiers).
5. To define your query, use one of the following options:
* To use the visual query builder, click **Simple query builder**. Select the filters, and then click **Run query** to preview the recent events that match your filters. To preview matching events over a specific period, select the time range.
* To use Axiom Processing Language (APL), click **Advanced query language**. Write a query using the `where` operator to filter for events, and then click **Run query** to preview the results. To transform matching events before sending them to you, use the `extend` and the `project` operators. Don’t use aggregations in your query. For more information, see [Introduction to APL](/apl/introduction).
6. When the preview displays the events that you want to match, click **Create**. You can’t create a match monitor if more than 500 events match your query within the past 24 hours.
You have created a match monitor, and Axiom alerts you about every event that matches the filters you set. Each notification contains the event details as shown in the preview.
If you define your query using APL, you can use the following limited set of tabular operators:
* [extend](/apl/tabular-operators/extend-operator)
* [extend-valid](/apl/tabular-operators/extend-valid-operator)
* [parse](/apl/tabular-operators/parse-operator)
* parse-kv
* [project](/apl/tabular-operators/project-operator)
* [project-away](/apl/tabular-operators/project-away-operator)
* [project-keep](/apl/tabular-operators/project-keep-operator)
* project-rename
* [project-reorder](/apl/tabular-operators/project-reorder-operator)
* [where](/apl/tabular-operators/where-operator)
This restriction only applies to tabular operators.
## Examples [#examples]
For real-world use cases, see [Monitor examples](/monitor-data/monitor-examples).
---
# Microsoft Teams notifier
Source: https://axiom.co/docs/monitor-data/microsoft-teams-notifier
Use a Microsoft Teams notifier to send a notification to a specific channel in your Microsoft Teams instance.
To create a Microsoft Teams notifier, follow these steps:
1. In Microsoft Teams, generate an incoming webhook. For more information, see the [Microsoft documentation](https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook).
2. In Axiom, click the **Monitors** tab, and then click **Manage notifiers** on the right.
3. Click **New notifier** on the top right.
4. Name your notifier.
5. Click **Microsoft Teams**.
6. Enter the webhook URL you have previously generated.
7. Click **Create**.
---
# Monitor examples
Source: https://axiom.co/docs/monitor-data/monitor-examples
## Notify on all occurrences of error [#notify-on-all-occurrences-of-error]
To receive a notification on all occurrences of an error, create a match monitor where the filter conditions match the events reporting the error.
To receive only certain attributes in the notification message, use the `project` operator.
## Notify when error rate above threshold [#notify-when-error-rate-above-threshold]
To receive a notification when the error rate exceeds a threshold, [create a threshold monitor](/monitor-data/threshold-monitors) with an APL query that identifies the rate of error messages.
For example, logs in your dataset `['sample_dataset']` have a `status.code` attribute that takes the value `ERROR` when a log is about an error. In this case, the following example query tracks the error rate every minute:
```apl
['sample_dataset']
| extend is_error = case(['status.code'] == 'ERROR', 1, 0)
| summarize avg(error) by bin(_time, 1m)
```
Other options:
* To trigger the monitor when the error rate is above or equal to 0.01, set the threshold value to 0.01 and the comparison operator to `above or equal`.
* To run the monitor every 5 minutes, set the frequency to 5.
* To keep the monitor in the alert state until 10 minutes have passed with the per-minute error rate remaining below your threshold value, set the range to 10.
## Notify when number of error messages above threshold [#notify-when-number-of-error-messages-above-threshold]
To receive a notification when the number of error message of a given type exceeds a threshold, create a threshold monitor with an APL query that counts the different error messages.
For example, logs in your dataset `['sample_dataset']` have a `error.message` attribute. In this case, the following example query counts errors by type every 5 minutes:
```apl
['sample_dataset']
| summarize count() by ['error.message'], bin(_time, 5m)
```
Other options:
* To trigger the monitor when the count is above or equal to 10 for any individual message type, set the threshold to 10 and the comparison operator to **above or equal**.
* To run the monitor every 5 minutes, set the frequency to 5.
* To run the monitor the query with a range of 10 minutes, set the range to 10.
By default, the monitor enters the alert state when any of the counts returned by the query cross the threshold, and remains in the alert state until no counts cross the threshold. To alert separately for each message value instead, enable **Notify by group**.
## Notify when response times spike [#notify-when-response-times-spike]
To receive a notification whenever your response times spike without having to rely on a single threshold, [create an anomaly monitor](/monitor-data/anomaly-monitors) with an APL query that tracks your median response time.
For example, you have a dataset `['my_traces']` of trace data with the following:
* Route information is in the `route` field.
* Duration information is in the `duration` field.
* For top-level spans, the `parent_span_id` field is empty.
The following query gives median response times by route in one-minute intervals:
```apl
['my_traces']
| where isempty(parent_span_id)
| summarize percentile(duration, 50) by ['route'], bin(_time, 1m)
```
Other options:
* To only trigger the monitor when response times are unusually high for a route, set the comparison operator to **above**.
* To run the monitor every 5 minutes, set the frequency to 5.
* To consider the previous 30 minutes of data when determining what sort of variation is expected for median response times for a route, set the range to 30.
* To notify separately for each route, enable **Notify by group**.
---
# Monitors
Source: https://axiom.co/docs/monitor-data/monitors
A monitor is a background task that periodically runs a query that you define. For example, it counts the number of error messages in your logs over the previous 5 minutes. A notifier defines how Axiom notifies you about the monitor output. For example, Axiom can send you an email.
You can use the following types of monitor:
* [Anomaly monitors](/monitor-data/anomaly-monitors) aggregate event data over time and look for values that are unexpected based on the event history. When the results of the aggregation are too high or low compared to the expected value, Axiom sends you an alert.
* [Match monitors](/monitor-data/match-monitors) filter for key events and send them to you.
* [Threshold monitors](/monitor-data/threshold-monitors) aggregate event data over time. When the results of the aggregation cross a threshold, Axiom sends you an alert.
---
# Notifiers
Source: https://axiom.co/docs/monitor-data/notifiers-overview
A monitor is a background task that periodically runs a query that you define. For example, it counts the number of error messages in your logs over the previous 5 minutes. A notifier defines how Axiom notifies you about the monitor output. For example, Axiom can send you an email.
By adding a notifier to a monitor, you receive a notification with the following message:
* When a match monitor matches an event, the message contains the full event if you created the monitor using the simple query builder, or the output of the APL query if you created the monitor using APL.
* When a threshold monitor changes state, the message includes a relevant value from the query results. If you enable **Notify by group**, the notification message also contains the relevant group value.
Choose one of the following to learn more about a type of notifier:
---
# Opsgenie notifier
Source: https://axiom.co/docs/monitor-data/opsgenie-notifier
Use an Opsgenie notifier to use all the incident management features of Opsgenie with Axiom.
To create an Opsgenie notifier, follow these steps:
1. In Opsgenie, create an API integration. For more information, see the [Opsgenie documentation](https://support.atlassian.com/opsgenie/docs/create-a-default-api-integration/).
2. Click the **Monitors** tab, and then click **Manage notifiers** on the right.
3. Click **New notifier** on the top right.
4. Name your notifier.
5. Click **Opsgenie**.
6. Enter the API key you have previously generated.
7. Select the region of your Opsgenie instance.
8. Click **Create**.
---
# PagerDuty notifier
Source: https://axiom.co/docs/monitor-data/pagerduty
Use a PagerDuty notifier to use all the incident management features of PagerDuty with Axiom.
## Benefits of using PagerDuty with Axiom [#benefits-of-using-pagerduty-with-axiom]
* Increase the performance and availability of your apps and services.
* Use specific insights in your backend, apps, and workloads by running PagerDuty in tandem with Axiom.
* Detect critical issues before any disruption happens to your resources: Axiom automatically opens and closes PagerDuty incidents.
* Obtain deep understanding of the issue root cause by visualising the data using Axiom.
Axiom creates PagerDuty events that arise from critical issues, disruptions, vulnerabilities, or workloads downtime on a service created in PagerDuty. The alert on Axiom side is linked to the PagerDuty Event allowing for Axiom to automatically close the Event incident if the Alert is resolved. This ensures no duplicate Events on PagerDuty side are created for the corresponding ones on Axiom side.
### Prerequisites [#prerequisites]
* Ensure you have [Admin base role](https://support.pagerduty.com/docs/user-roles) in PagerDuty.
## Create PagerDuty notifier [#create-pagerduty-notifier]
To create a PagerDuty notifier, follow these steps:
1. In PagerDuty’s Events V2 API, create a new service named **Axiom** with the default settings. Copy the integration key. For more information, see the [PagerDuty documentation](https://support.pagerduty.com/main/docs/services-and-integrations#create-a-service)
2. In Axiom, click the **Monitors** tab, and then click **Manage notifiers** on the right.
3. Click **New notifier** on the top right.
4. Name your notifier.
5. Click **Slack**.
6. Enter the integration key you have previously generated.
7. Click **Create**.
You can now add your PagerDuty notifier to a specific monitor in Axiom. If any incident happens on your monitor, Axiom notifies you on the PagerDuty Service Activity dashboard.
---
# Slack notifier
Source: https://axiom.co/docs/monitor-data/slack-notifier
Use a Slack notifiers to notify specific channels in your Slack organization.
To create a Slack notifier, follow these steps:
1. In Slack, generate an incoming webhook. For more information, see the [Slack documentation](https://api.slack.com/messaging/webhooks).
2. In Axiom, click the **Monitors** tab, and then click **Manage notifiers** on the right.
3. Click **New notifier** on the top right.
4. Name your notifier.
5. Click **Slack**.
6. Enter the webhook URL you have previously generated.
7. Click **Create**.
---
# Threshold monitors
Source: https://axiom.co/docs/monitor-data/threshold-monitors
Threshold monitors allow you to periodically aggregate your event data and compare the results of this aggregation to a threshold that you define. When the results cross the threshold, the monitor enters the alert state. The monitor remains in the alert state until the results no longer cross the threshold. A threshold monitor sends you a notification each time it enters or exits the alert state.
## Create threshold monitor [#create-threshold-monitor]
To create a threshold monitor, follow these steps:
1. Click the **Monitors** tab, and then click **New monitor**.
2. Click **Threshold monitor**.
3. Name your monitor and add a description.
4. Configure the monitor using the following options:
* The threshold is the value to compare the results of the query to. This can be any numeric value.
* The comparison operator is the rule to apply when comparing the results to the threshold. The possible values are **above**, **above or equal**, **below**, and **below or equal**.
* The frequency is how often the monitor runs. This is a positive integer number of minutes.
* The range is the time range for your query. This is a positive integer number of minutes. The end time is the time the monitor runs.
* **Alert on no data** triggers the monitor when your query doesn’t return any data. Your query returns no data if no events match your filters and an aggregation used in the query is undefined. For example, you take the average of a field not present in any matching events. The monitor sends a notification when data returns after a no-data alert state. Notifications about crossing the threshold take precedence over notifications about data returning after a no-data state.
* You can group by attributes when defining your query. By default, your monitor enters the alert state if any of the values returned for the group-by attributes cross the threshold, and remains in the alert state until none of the values returned cross the threshold. To trigger the monitor separately for each group that crosses the threshold, enable **Notify by group**. At most one trigger notification is sent per monitor run. This option only has an effect if the monitor’s query groups by a non-time field.
5. Click **Add notifier**, and then select the notifiers that define how you want to receive notifications for this monitor. For more information, see [Notifiers](#notifiers).
6. To define your query, use one of the following options:
* To use the visual query builder, click **Simple query builder**. Click **Visualize** to select an aggregation method, and then click **Run query** to preview the results in a chart. The monitor enters the alert state if any points on the chart cross the threshold. Optionally, use filters to specify which events to aggregate, and group by fields to split the aggregation across the values of these fields.
* To use Axiom Processing Language (APL), click **Advanced query language**. Write a query where the final clause uses the `summarize` operator, and then click **Run query** to preview the results. For more information, see [Introduction to APL](/apl/introduction). If your query returns a chart, the monitor enters the alert state if any points on the chart cross the threshold. If your query returns a table, the monitor enters the alert state if any numeric values in the table cross the threshold. If your query uses the `bin_auto` function, Axiom displays a warning. To ensure that the monitor preview gives an accurate picture of future performance, use `bin` rather than `bin_auto`.
7. Click **Create**.
You have created a threshold monitor, and Axiom alerts you when the results from your query cross the threshold.
## Examples [#examples]
For real-world use cases, see [Monitor examples](/monitor-data/monitor-examples).
---
# View monitor status
Source: https://axiom.co/docs/monitor-data/view-monitor-status
To view the status of a monitor:
1. Click the Monitors tab.
2. Click the monitor in the list whose status you want to view.
The monitor status page provides an overview of the monitor’s current status and history.
## View recent activity and history of runs [#view-recent-activity-and-history-of-runs]
On the left, you see the recent activity and the history of the monitor runs:
* The `_time` field displays the time of the monitor run.
* The `Status` field displays the status of the monitor.
* The `Range from` and `Range to` fields display the time range used in the monitor run.
You can change the time range of this overview in the top right corner.
## View information about monitor configuration [#view-information-about-monitor-configuration]
On the right, you see information about the monitor’s configuration.
* Current status
* Monitor type
* Query the monitor periodically runs
* Configuration details
* Notifiers attached to the monitor
* Metadata such as name and description
## Check recent viewers of monitor status [#check-recent-viewers-of-monitor-status]
The status page displays the initials of the users who have recently looked at the monitor. To check which users have recently viewed the status page of monitors, hold the pointer over the initials in the top right of the page.
For example, this can be useful if you want to know who has recently seen that a monitor had been triggered and you can start a conversation with them to understand what’s happening.
---
# Architecture
Source: https://axiom.co/docs/platform-overview/architecture
You don’t need to understand any of the following material to get massive value from Axiom. As a fully managed data platform, Axiom just works. This technical deep-dive is intended for curious minds wondering: Why is Axiom different?
Axiom routes ingestion requests through a distributed edge layer to a cluster of specialized services that process and store data in proprietary columnar formats optimized for different data types. EventDB handles high-volume event data, while MetricsDB is purpose-built for time-series metrics with high-cardinality dimensions. Query requests are executed by ephemeral, serverless workers that operate directly on compressed data stored in object storage.
## Ingestion architecture [#ingestion-architecture]
Data flows through a multi-layered ingestion system designed for high throughput and reliability:
* **Regional Edge Layer:** HTTPS ingestion requests are received by regional edge proxies positioned to meet data jurisdiction requirements. These proxies handle protocol translation, authentication, and initial data validation. The edge layer supports multiple input formats (JSON, CSV, compressed streams) and can buffer data during downstream issues.
* **High-availability routing:** The system provides intelligent routing to healthy database nodes using real-time health monitoring. When primary ingestion paths fail, requests are automatically routed to available nodes or queued in a backlog system that processes data when systems recover.
* **Streaming Pipeline:** Raw events are parsed, validated, and transformed in streaming fashion. Field limits and schema validation occur during this phase.
* **Write-Ahead Logging:** All ingested data is durably written to a distributed write-ahead log before being processed. This ensures zero data loss even during system failures and supports concurrent writes across multiple ingestion nodes.
## Storage architecture [#storage-architecture]
Axiom’s storage layer uses specialized columnar formats optimized for different workload types:
### EventDB storage [#eventdb-storage]
EventDB’s storage is built around a custom columnar format that achieves extreme compression ratios:
* **Columnar organization:** Events are decomposed into columns and stored using specialized encodings optimized for each data type. String columns use dictionary encoding, numeric columns use various compression schemes, and boolean columns use bitmap compression.
* **Block-based storage:** Data is organized into immutable blocks that are written once and read many times. Each block contains:
* Column metadata and statistics
* Compressed column data in a proprietary format
* Separate time indexes for temporal queries
* Field schemas and type information
* **Compression pipeline:** Data flows through multiple compression stages:
1. **Ingestion compression:** Real-time compression during ingestion (25-50% reduction)
2. **Block compression:** Columnar compression within storage blocks (10-20x additional compression)
3. **Compaction compression:** Background compaction further optimizes storage (additional 2-5x compression)
* **Object storage integration:** Blocks are stored in object storage (S3) with intelligent partitioning strategies that distribute load and avoid hot-spotting. The system supports multiple storage tiers and automatic lifecycle management.
### MetricsDB storage [#metricsdb-storage]
MetricsDB uses a specialized columnar format engineered for time-series metrics with high-cardinality tags:
* **High-cardinality optimization:** Unlike traditional metrics datastores that struggle with dimensional complexity, MetricsDB is designed from the ground up to handle high numbers of unique tag combinations efficiently.
* **Intentional design constraints:** MetricsDB makes deliberate trade-offs to optimize for the most common metrics use cases. These constraints are purposeful architectural choices that enable MetricsDB to deliver exceptional performance and cost-efficiency for real-world metrics workloads. Where other systems penalize you for high cardinality or force you to pre-aggregate data, MetricsDB lets you store and query metrics with full dimensional flexibility.
* **Unified observability:** Query metrics alongside logs and traces, enabling powerful correlations across all your telemetry data without switching tools or learning multiple query languages.
## Query architecture [#query-architecture]
Axiom executes queries using a serverless architecture that spins up compute resources on-demand:
* **Query compilation:** The APL (Axiom Processing Language) query is parsed, optimized, and compiled into an execution plan. The compiler performs predicate pushdown, projection optimization, and identifies which blocks need to be read.
* **Serverless Workers:** Query execution occurs in ephemeral workers optimized through "Fusion queries"—a system that runs parallel queries inside a single worker to reduce costs and leave more resources for large queries. Workers download only the necessary column data from object storage, enabling efficient resource utilization. Multiple workers can process different blocks in parallel.
* **Block-level parallelism:** Each query spawns multiple workers that process different blocks concurrently. Workers read compressed column data directly from object storage, decompress it in memory, and execute the query.
* **Result aggregation:** Worker results are streamed back and aggregated by a coordinator process. Large result sets are automatically spilled to object storage and streamed to clients via signed URLs.
* **Intelligent caching:** Query results are cached in object storage with intelligent cache keys that account for time ranges and query patterns. Cache hits dramatically reduce query latency for repeated queries.
## Compaction system [#compaction-system]
A background compaction system continuously optimizes storage efficiency:
* **Automatic compaction:** The compaction scheduler identifies blocks that can be merged based on size, age, and access patterns. Small blocks are combined into larger "superblocks" that provide better compression ratios and query performance.
* **Multiple strategies:** The system supports several compaction algorithms:
* **Default:** General-purpose compaction with optimal compression
* **Clustered:** Groups data by common field values for better locality
* **Fieldspace:** Optimizes for specific field access patterns
* **Concat:** Simple concatenation for append-heavy workloads
* **Compression optimization:** During compaction, data is recompressed using more aggressive algorithms and column-specific optimizations that aren’t feasible during real-time ingestion.
## System architecture [#system-architecture]
The overall system is composed of specialized microservices:
* **Core services:** Handle authentication, billing, dataset management, and API routing. These services are stateless and horizontally scalable.
* **Database layer:** The core database engine processes ingestion, manages storage, and coordinates query execution. It supports multiple deployment modes and automatic failover.
* **Orchestration layer:** Manages distributed operations, monitors system health, and coordinates background processes like compaction and maintenance.
* **Edge services:** Handle real-time data ingestion, protocol translation, and provide regional data collection points.
## Why this architecture wins [#why-this-architecture-wins]
* **Cost efficiency:** Serverless query execution means you only pay for compute during active queries. Extreme compression (25-50x) dramatically reduces storage costs compared to traditional row-based systems.
* **Operational simplicity:** The system is designed to be self-managing. Automatic compaction, intelligent caching, and distributed coordination eliminate operational overhead.
* **Elastic scale:** Each component scales independently. Ingestion scales with edge capacity, storage scales with object storage, and query capacity scales with serverless workers.
* **Fault tolerance:** Write-ahead logging, distributed routing, and automatic failover ensure high availability. The system gracefully handles node failures and storage outages.
* **Real-time performance:** Despite the distributed architecture, the system maintains sub-second query performance through intelligent caching, predicate pushdown, and columnar storage optimizations.
This architecture enables Axiom to ingest millions of events per second while maintaining sub-second query latency at a fraction of the cost of traditional logging and observability solutions.
---
# Features
Source: https://axiom.co/docs/platform-overview/features
| Component | Sub-Component | Feature | Description |
| :-------------------------- | :--------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Data Platform** | Deployment | Axiom Cloud | Axiom hosts and manages all infrastructure in its own cloud. |
| | | | |
| **EventDB** | - | - | Foundation of Axiom’s platform for ingesting, storing, and querying timestamped event data at scale. |
| **EventDB** | Ingest | - | Ingest pipeline that’s coordination-free, durable by default, and scales linearly without requiring Kafka or other heavy middleware. |
| **EventDB** | Storage | - | Custom block-based format on object storage, with extreme compression (average 25×, up to 50× for more structured events) and efficient metadata. |
| **EventDB** | Query | - | Serverless ephemeral runtimes that spin up on demand to process queries, powered by the Axiom Processing Language (APL). |
| **EventDB** | Query | [Axiom Processing Language (APL)](/apl/introduction) | Powerful query language for logs and traces, supporting filtering, aggregations, transformations, and specialized operators. |
| **EventDB** | Query | [Virtual fields](/query-data/virtual-fields) | Ability to derive new values from data in real-time during queries without pre-structuring or transforming data during ingestion. |
| | | | |
| **MetricsDB** | - | - | Dedicated metrics datastore purpose-built for high-cardinality time-series data without the cost penalties of traditional metrics systems. |
| **MetricsDB** | Ingest | [OpenTelemetry Metrics](/send-data/opentelemetry) | Native support for OpenTelemetry (gRPC over HTTP) metrics protocol. |
| **MetricsDB** | Storage | High-cardinality native | Store metrics with high-cardinality tags without performance degradation. |
| **MetricsDB** | Query | [Metrics Processing Language (MPL)](/mpl/introduction) | Query metrics using Metrics Processing Language (MPL) with support for time-series aggregations, dimensional filtering, and cross-metric correlations. |
| | | | |
| **Console** | - | - | Web UI for data management, querying, dashboarding, monitoring, and user administration. |
| **Console** | Query | [Builder](/query-data/explore) | Guided interface to quickly filter and group data. |
| **Console** | Query | [Editor](/query-data/query-editor) | A full text-based query language environment for complex aggregations, transformations, and correlations. |
| **Console** | Query | [Natural-language querying](/query-data/query-editor#generate-query-using-natural-language) | Generate valid APL queries from plain English descriptions using AI. |
| **Console** | Query | [Visualizations](/query-data/visualizations) | Charts, graphs, and other visual components to make sense of query results. |
| **Console** | Query | [Trace waterfall](/query-data/traces) | Dedicated interface for analyzing distributed traces. |
| **Console** | Query | [Saved queries](/query-data/datasets#saved-queries) | Save and share queries with team members for easy reuse. |
| **Console** | Query | [Views](/query-data/views) | Apply filters and transformations to datasets to create reusable virtual tables with scoped access control. |
| **Console** | Stream | [Live tailing](/query-data/stream) | Real-time streaming view of incoming logs and events. |
| **Console** | Dashboards | - | Combine multiple visual elements (charts, tables, logs, etc.) onto a single page. |
| **Console** | Dashboards | [AI-powered dashboard generation](/dashboards/create#generate-dashboards-using-ai) | Describe requirements in natural language and generate complete dashboards instantly. |
| **Console** | Dashboards | [Elements](/dashboard-elements/create) | Various chart types, log streams, notes, and more to tailor each dashboard. |
| **Console** | Dashboards | [Annotations](/query-data/annotate-charts) | Mark points in time or highlight events directly in your dashboards. |
| **Console** | Monitors | [Threshold monitors](/monitor-data/threshold-monitors) | Checks if aggregated values exceed or fall below a predefined threshold (for example, error counts > 100). |
| **Console** | Monitors | [Match monitors](/monitor-data/match-monitors) | Triggers on specific log patterns or conditions for each event. |
| **Console** | Monitors | [Anomaly monitors](/monitor-data/anomaly-monitors) | Learns from historical data to detect unexpected deviations or spikes. |
| **Console** | Alerting | [Notifiers](/monitor-data/notifiers-overview) (Webhooks, Email, Slack, and more) | Sends notifications through various channels including email, chat platforms, incident management systems, and custom webhook integrations. |
| **Console** | Governance | [Dataset management](/reference/datasets) | Data retention controls, trim functionality, field vacuuming, and dataset sharing capabilities. |
| **Console** | Governance | [Role-Based Access Control (RBAC)](/reference/settings) | Fine-grained permissions at the organization and dataset levels. |
| **Console** | Governance | [Audit logs](/reference/audit-log) | Track every change and action taken by users within Axiom. |
| | | | |
| **Intelligence** | - | - | AI-powered features that accelerate insights and automate data analysis. |
| **Intelligence** | Analysis | [Spotlight](/console/intelligence/spotlight) | Automatically identify differences between selected events and baseline data for root cause analysis and anomaly investigation. Quickly understand key differences with AI-generated summaries. |
| **Intelligence** | Agent integration | [MCP Server](/console/intelligence/mcp-server) | Model Context Protocol server enabling AI agents to query data, list datasets, and access monitors using APL. |
| **Intelligence** | LLM observability | [GenAI dashboard and traces](/use-cases/llm-observability) | Auto-provisioned GenAI dashboard and waterfall trace view for any dataset containing AI telemetry. Instrument using OpenTelemetry or the Axiom AI SDK. |
| | | | |
| **Integrations** | - | - | A wide range of official and community tools to collect logs, metrics, and traces and send them into Axiom. |
| **Integrations** | Popular connectors | [OpenTelemetry](/send-data/opentelemetry), [Vector](/send-data/vector), [Cribl](/send-data/cribl), and [more](/send-data/methods) | Common shippers and pipeline tools for bridging data sources to Axiom. |
| **Integrations** | AWS | [CloudWatch Forwarder](/send-data/cloudwatch), [Lambda Extension](/send-data/aws-lambda), [Kinesis](/send-data/aws-firehose), [S3](/send-data/aws-s3) | Official AWS-based ingestion solutions. |
| **Integrations** | Language libraries | [Go](/guides/go), [Python](/guides/python), [Node.js](/guides/opentelemetry-nodejs), [Java](/guides/opentelemetry-java), [Ruby](/guides/opentelemetry-ruby), [Rust](/guides/rust), [.NET](/guides/opentelemetry-dotnet) | SDKs to send logs and metrics directly from applications. |
| | | | |
| **APIs and CLI** | - | - | Programmatic and command-line interfaces for ingesting, querying, and managing Axiom resources. |
| **APIs and CLI** | REST API | [Endpoints](/restapi/introduction) | Programmatic interfaces for ingesting data, running queries, retrieving results, and managing annotations and tokens. |
| **APIs and CLI** | REST API | [API tokens and Personal Access Tokens](/reference/tokens) | Authentication mechanisms for API access with basic and advanced tokens, plus personal tokens. |
| **APIs and CLI** | CLI | [Auth and dataset management]() | Create datasets, set tokens, or update config from a shell. |
| **APIs and CLI** | CLI | [Query from terminal](/restapi/query) | Execute queries in APL or simpler filters directly in a command-line session. |
| **APIs and CLI** | [Terraform Provider](https://registry.terraform.io/providers/axiomhq/axiom/latest) | - | Terraform provider for programmatically creating and managing Axiom resources including datasets, notifiers, monitors, and users. |
| | | | |
| **Security and Compliance** | - | - | Axiom’s data protection measures and compliance with major privacy/security frameworks. |
| **Security and Compliance** | [Compliance](/platform-overview/security) | SOC 2 Type II, GDPR, CCPA, HIPAA | Meets industry standards for data handling and privacy. |
## Related links [#related-links]
* Explore Axiom’s interactive demo [Playground](https://play.axiom.co/) to try these features.
* Check Axiom’s [roadmap](/platform-overview/roadmap) for upcoming features.
* [Contact](https://axiom.co/contact) Axiom if you have a feature request.
---
# Roadmap
Source: https://axiom.co/docs/platform-overview/roadmap
## Focus areas [#focus-areas]
Axiom continuously ships improvements to its core platform. Beyond these changes, Axiom’s current product development is focused on transforming the core data platform into an intelligent assistant.
[Contact](https://axiom.co/contact) the Axiom team to request more details about specific roadmap items or to join early access programs.
### Intelligent assistant [#intelligent-assistant]
Axiom’s Console is evolving from a powerful analysis tool into an intelligent assistant that dramatically accelerates time-to-insight. By augmenting human-led investigations with AI, Axiom helps teams move from reactive troubleshooting to proactive action.
Learn more in the [Intelligence](/console/intelligence) docs.
### Platform excellence and scale [#platform-excellence-and-scale]
Supporting ambitious builders requires a rock-solid and scalable foundation. Axiom continues to invest heavily in core performance, reliability, and capabilities of the Axiom platform to ensure it can handle the most demanding workloads.
* **Full observability coverage**: Axiom is working to make **Metrics** a generally available, first-class citizen within the data platform, completing the "three pillars of observability" and providing a unified platform for all your telemetry.
* **Global architecture**: Axiom is being re-architected for global scale and efficiency. This includes building out a multi-region edge infrastructure and overhauling storage to support millions of datasets, ensuring high availability and low latency.
## Feature states [#feature-states]
Each feature of Axiom is in one of the following states:
* **In development:** Axiom is actively building this feature. It’s not available yet but it’s progressing towards Preview.
* **Private preview:** An early access feature available to selected customers which helps Axiom validate the feature with trusted partners.
* **Public preview:** The feature is available for everyone to try but may have some rough edges. Axiom is gathering feedback before making it GA.
* **Generally available (GA):** The feature is fully released, production-ready, and supported. Feel free to use it in your workflows.
* **Planned end of life:** The feature is scheduled to be retired. It’s still working but you should start migrating to alternative solutions.
* **End of life:** The feature is no longer available or supported. Axiom has sunset it in favor of newer solutions.
Private and public preview features are experimental. They aren’t guaranteed to work as expected and may return unexpected query results. Axiom doesn’t guarantee data integrity or accuracy for preview features. Consider the risk you run when you use preview features against production workloads.
{/*
Current private preview features:
*/}
Current public preview features:
* [Cursor-based pagination](/restapi/pagination)
* [`externaldata` operator](/apl/tabular-operators/externaldata-operator)
* [`join` operator](/apl/tabular-operators/join-operator)
* [Metrics Processing Language (MPL)](/mpl/introduction)
---
# Security
Source: https://axiom.co/docs/platform-overview/security
## Compliance [#compliance]
Axiom complies with key standards and regulations.
### ISO 27001 [#iso-27001]
Axiom’s ISO 27001 certification indicates that Axiom has established a robust system to manage information security risks concerning the data it controls or processes.
### SOC2 Type II [#soc2-type-ii]
Axiom’s SOC 2 Type II certification proves that Axiom has strict security measures in place to protect customer data.
If you’re on the Axiom Cloud or the Bring Your Own Cloud plan, you can request a report that outlines the technical and legal details under non-disclosure agreement (NDA). For more information, see the [Axiom Trust Center](https://trust.axiom.co/).
### General Data Protection Regulation (GDPR) [#general-data-protection-regulation-gdpr]
Axiom complies with GDPR and its core principles including data minimization and rights of the data subject.
### California Consumer Privacy Act (CCPA) [#california-consumer-privacy-act-ccpa]
Axiom complies with CCPA and its core principles including transparency on data collection, processing and storage. You can request a Data Processing Addendum that outlines the technical and legal details.
### Health Insurance Portability and Accountability Act (HIPAA) [#health-insurance-portability-and-accountability-act-hipaa]
Axiom complies with HIPAA and its core principles. HIPAA compliance means that Axiom can enter into Business Associate Agreements (BAAs) with healthcare providers, insurers, pharma and health research firms, and service providers who work with protected health information (PHI).
You can request a Business Associate Agreement (BAA) with Axiom if you meet the following requirements:
* You’re on the Axiom Cloud plan.
* You have signed up for all of the following security-related add-ons:
* [Role-Based Access Control (RBAC)](/reference/settings)
* [SAML Single Sign-On (SSO)](/reference/settings#single-sign-on-saml-sso)
* [Audit log](/reference/audit-log)
For more information, see the [Axiom Trust Center](https://trust.axiom.co/) and [Manage add-ons](/reference/usage-billing#manage-add-ons).
After signing a BAA, user sessions in your organization time out after 24 hours to meet HIPAA security requirements.
## Compliance and Axiom AI [#compliance-and-axiom-ai]
## Comprehensive security measures [#comprehensive-security-measures]
Axiom employs a multi-faceted approach to ensure data security, covering encryption, penetration testing, infrastructure security, and organizational measures.
### Data encryption [#data-encryption]
Data at Axiom is encrypted both at rest and in transit. Axiom’s encryption practices align with industry standards and are regularly audited to ensure the highest level of security.
Data is stored in the Amazon Web Services (AWS) infrastructure at rest and encrypted through technologies offered by AWS using AES-256 bit encryption. The same high level of security is provided for data in transit using AES-256 bit encryption and TLS to secure network traffic.
### Penetration testing [#penetration-testing]
Axiom performs regular vulnerability scans and annual penetration tests to proactively identify and mitigate potential security threats.
### System protection [#system-protection]
Axiom systems are segmented into separate networks and protected through restrictive firewall rules. Network access to production environments is tightly restricted. Monitors are in place to ensure that service delivery matches SLA requirements.
### Resilience against system failure [#resilience-against-system-failure]
Axiom maintains daily encrypted backups and full system replication of production platforms across multiple availability zones to ensure business continuity and resilience against system failures. Axiom periodically tests restoration capabilities to ensure your data is always protected and accessible.
### Organizational security practices [#organizational-security-practices]
Axiom’s commitment to security extends beyond technological measures to include comprehensive organizational practices. Axiom employees receive regular security training and follow stringent security requirements like encryption of storage and two-factor authentication.
Axiom supports secure, centralized user authentication through SAML-based SSO (Security Assertion Markup Language-based Single Sign-On). This makes it easy to keep access grants up-to-date with support for the industry standard SCIM protocol. Axiom supports both the flows initiated by the service provider and the identity provider (SP- and the IdP-initiated flows).
Axiom enables you to take control over access to your data and features within Axiom through role-based permissions.
Axiom provides you with searchable audit logs that provide you with comprehensive tracking of all activity in your Axiom organization to meet even the most stringent compliance requirements.
SAML-based SSO, role-based access control (RBAC), and full access to the audit log are available as add-ons on the Axiom Cloud plan. For more information, see [Manage add-ons](/reference/usage-billing#manage-add-ons).
## Sub-processors [#sub-processors]
Axiom works with a limited number of trusted sub-processors. For a full list, see [Sub-processors](https://trust.axiom.co/subprocessors). Axiom regularly reviews all third parties to ensure they meet high standards for security.
## Report vulnerabilities [#report-vulnerabilities]
Axiom takes all reports seriously and has a responsible disclosure process. Please submit vulnerabilities by email to [security@axiom.co](mailto:security@axiom.co).
---
# Annotate dashboard elements
Source: https://axiom.co/docs/query-data/annotate-charts
Annotating charts lets you add context to your charts. For example, use annotations to mark the time of the following:
* Deployments
* Server outages
* Incidents
* Feature flags
This adds context to the trends displayed in your charts and makes it easier to investigate issues in your app or system.
* [Send data](/send-data/methods) to your Axiom dataset.
* [Create an API token in Axiom](/reference/tokens) with permissions to create, read, update, and delete annotations.
## Create annotations [#create-annotations]
Create annotations in one of the following ways:
* [Use a GitHub Action](#create-annotations-with-github-actions)
* [Send a request to the Axiom API](#create-annotations-with-axiom-api)
If you use the Axiom Vercel integration, annotations are automatically created for deployments.
Axiom automatically creates an annotation if a monitor triggers.
### Create annotations with GitHub Actions [#create-annotations-with-github-actions]
You can configure GitHub Actions using YAML syntax. For more information, see the [GitHub documentation](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions#create-an-example-workflow).
To create an annotation when a deployment happens in GitHub, add the following to the end of your GitHub Action file:
```yml
- name: Add annotation in Axiom when a deployment happens
uses: axiomhq/annotation-action@v0.1.0
with:
axiomToken: ${{ secrets.API_TOKEN }}
datasets: DATASET_NAME
type: "production-release"
time: "2024-01-01T00:00:00Z" # optional, defaults to now
endTime: "2024-01-01T01:00:00Z" # optional, defaults to null
title: "Production deployment" # optional
description: "Commit ${{ github.event.head_commit.message }}" # optional
url: "https://example.com" # optional, defaults to job URL
```
Customize the other fields of the code above such as the title, the description, and the URL.
This creates an annotation in Axiom each time you deploy in GitHub.
### Create annotations using Axiom API [#create-annotations-using-axiom-api]
To create an annotation using the Axiom API, use the following API request:
```bash
curl -X 'POST' 'https://api.axiom.co/v2/annotations' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"time": "2024-03-18T08:39:28.382Z",
"type": "deploy",
"datasets": ["DATASET_NAME"],
"title": "Production deployment",
"description": "Deploy new feature to the sales form",
"url": "https://example.com"
}'
```
Customize the other fields of the code above such as the title, the description, and the URL. For more information on the allowed fields, see [Annotation object](#annotation-object).
**Example response**
```bash
{
"datasets": ["my-dataset"],
"description": "Deploy new feature to the sales form",
"id": "ann_123",
"time": "2024-03-18T08:39:28.382Z",
"title": "Production deployment",
"type": "deploy",
"url": "https://example.com"
}
```
The API response from Axiom contains an `id` field. This is the annotation ID that you can later use to change or delete the annotation.
## Get information about annotations [#get-information-about-annotations]
To get information about all datasets in your org, use the following API request:
```bash
curl -X 'GET' 'https://api.axiom.co/v2/annotations' \
-H 'Authorization: Bearer API_TOKEN'
```
Use the following parameters in the endpoint URL to filter for a specific time interval and dataset:
* `start` is an ISO timestamp that specifies the beginning of the time interval.
* `end` is an ISO timestamp that specifies the end of the time interval.
* `datasets` is the list of datasets whose annotations you want to get information about. Separate datasets by commas, for example `datasets=my-dataset1,my-dataset2`.
The example below gets information about annotations about occurrences between March 16th and 19th, 2024 and added to the dataset `my-dataset`:
```bash
curl -X 'GET' 'https://api.axiom.co/v2/annotations?start=2024-03-16T00:00:00.000Z&end=2024-03-19T23:59:59.999Z&datasets=my-dataset' \
-H 'Authorization: Bearer API_TOKEN'
```
**Example response**
```json
[
{
"datasets": ["my-dataset"],
"description": "Deploy new feature to the navigation component",
"id": "ann_234",
"time": "2024-03-17T01:15:45.232Z",
"title": "Production deployment",
"type": "deploy",
"url": "https://example.com"
},
{
"datasets": ["my-dataset"],
"description": "Deploy new feature to the sales form",
"id": "ann_123",
"time": "2024-03-18T08:39:28.382Z",
"title": "Production deployment",
"type": "deploy",
"url": "https://example.com"
}
]
```
The API response from Axiom contains an `id` field. This is the annotation ID that you can later use to change or delete the annotation. For more information on the other fields, see [Annotation object](#annotation-object).
To get information about a specific annotation, use the following API request:
```bash
curl -X 'GET' 'https://api.axiom.co/v2/annotations/ANNOTATION_ID' \
-H 'Authorization: Bearer API_TOKEN'
```
Replace `ANNOTATION_ID` with the ID of the annotation.
**Example response**
```bash
{
"datasets": ["my-dataset"],
"description": "Deploy new feature to the sales form",
"id": "ann_123",
"time": "2024-03-18T08:39:28.382Z",
"title": "Production deployment",
"type": "deploy",
"url": "https://example.com"
}
```
For more information on these fields, see [Annotation object](#annotation-object).
## Change annotations [#change-annotations]
To change an existing annotation, use the following API request:
```bash
curl -X 'PUT' 'https://api.axiom.co/v2/annotations/ANNOTATION_ID' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"endTime": "2024-03-18T08:49:28.382Z"
}'
```
Replace `ANNOTATION_ID` with the ID of the annotation. For more information about how to determine the annotation ID, see [Get information about annotations](#get-information-about-annotations).
In the payload, specify the properties of the annotation that you want to change. The example above adds an `endTime` field to the annotation created above. For more information on the allowed fields, see [Annotation object](#annotation-object).
**Example response**
```bash
{
"datasets": ["my-dataset"],
"description": "Deploy new feature to the sales form",
"id": "ann_123",
"time": "2024-03-18T08:39:28.382Z",
"title": "Production deployment",
"type": "deploy",
"url": "https://example.com",
"endTime": "2024-03-18T08:49:28.382Z"
}
```
## Delete annotations [#delete-annotations]
To delete an existing annotation, use the following API request:
```bash
curl -X 'DELETE' 'https://api.axiom.co/v2/annotations/ANNOTATION_ID' \
-H 'Authorization: Bearer API_TOKEN' \
```
Replace `ANNOTATION_ID` with the ID of the annotation. For more information about how to determine the annotation ID, see [Get information about annotations](#get-information-about-annotations).
## Annotation object [#annotation-object]
Annotations are represented as objects with the following fields:
* `datasets` is the list of dataset names for which the annotation appears on charts.
* `id` is the unique ID of the annotation.
* `description` is an explanation of the event the annotation marks on the charts.
* `time` is an ISO timestamp value that specifies the time the annotation marks on the charts.
* `title` is a summary of the annotation that appears on the charts.
* `type` is the type of the event marked by the annotation. For example, production deployment.
* `url` is the URL relevant for the event marked by the annotation. For example, link to GitHub pull request.
* Optional: `endTime` is an ISO timestamp value that specifies the end time of the annotation.
## Show and hide annotations on dashboards [#show-and-hide-annotations-on-dashboards]
To show and hide annotations on a dashboard, follow these steps:
1. Go to the dashboard where you see annotations. For example, the prebuilt Vercel dashboard automatically shows annotations about deployments.
2. Click **Toggle annotations**.
3. Select the datasets whose annotations you want to display on the charts.
## Example use case [#example-use-case]
The example below demonstrates how annotations help you troubleshoot issues in your app or system. Your monitor alerts you about rising form submission errors. You explore this trend and when it started. Right before form submission errors started rising, you see an annotation about a deployment of a new feature to the form. You make the hypothesis that the deployment is the reason for the error and decide to investigate the code changes it introduced.
### Create annotation [#create-annotation]
Use the following API request to create an annotation:
```bash
curl -X 'POST' 'https://api.axiom.co/v2/annotations' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"time": "2024-03-18T08:39:28.382Z",
"type": "deploy",
"datasets": ["my-dataset"],
"title": "Production deployment",
"description": "Deploy new feature to the sales form",
"url": "https://example.com"
}'
```
### Create a monitor [#create-a-monitor]
In this example, you set up a monitor that alerts you when the number of form submission errors rises. For more information on creating a monitor, see [Monitoring and Notifiers](/monitor-data/monitors).
### Explore trends [#explore-trends]
Suppose your monitor sends you a notification about rising form submission errors.
You decide to investigate and run a query to display the number of form submission errors over time. Ensure you select a time range that includes the annotation.
You get a chart similar to the example below displaying form submission errors and annotations about the time of important events such as deployments.
### Inspect issue [#inspect-issue]
1. From the chart, you see that the number of errors started to rise after the deployment of a new feature to the sales form. This correlation allows you to form the hypothesis that the errors might be caused by the deployment.
2. You decide to investigate the deployment by clicking on the link associated with the annotation. The link takes you to the GitHub pull request.
3. You inspect the code changes in depth and discover the cause of the errors.
4. You quickly fix the issue in another deployment.
---
# Correlations
Source: https://axiom.co/docs/query-data/correlations
Correlations connect datasets that describe the same system from different telemetry signals. A correlation group records which logs, traces, and metrics datasets belong together.
Use correlations to move between related signals without manually copying IDs or rebuilding context in another query.
## What correlations are [#what-correlations-are]
A correlation group contains up to three datasets:
* **Logs dataset:** OpenTelemetry log records with trace context.
* **Traces dataset:** OpenTelemetry trace spans.
* **Metrics dataset:** OpenTelemetry metrics stored in MetricsDB.
Axiom uses the datasets in a correlation group to find related data during an investigation. For example, a log event that contains a trace ID links to the matching trace. A trace links back to log records from the correlated logs dataset. A span in a trace links to metrics from the correlated metrics dataset.
## Prerequisites [#prerequisites]
Before you create a correlation group, prepare the datasets that you plan to connect:
* Send logs, traces, and metrics to separate datasets.
* Use a dedicated OpenTelemetry metrics dataset for metrics.
* Ensure logs include `trace_id` and `span_id` for log-to-trace and trace-to-log navigation. This should happen by default if you use `Logger.emit` or the equivalent in your OpenTelemetry library.
* Use OpenTelemetry semantic conventions when possible
## Create a correlation group [#create-a-correlation-group]
1. In Axiom Console, go to **Datasets**.
2. Click **Correlations**.
3. Click **New correlation group**.
4. Enter a **Name**.
5. Enter a **Slug**.
6. Select one or more datasets:
* **Logs dataset**
* **Traces dataset**
* **Metrics dataset**
7. Click **Create correlation group**.
Alternatively, you can create a correlation group from new datasets. This will create logs, traces, and metrics datasets with a prefix and optional suffix of your choice, and create a correlation group that contains them.
## Use correlations [#use-correlations]
Use correlations from query results and trace details to move between telemetry signals.
### Go from logs to traces [#go-from-logs-to-traces]
When a log event belongs to a correlated logs dataset and contains a trace ID, Axiom links the event to the matching trace in the correlated traces dataset.
#### Query view [#query-view]
1. Run a query against a logs dataset that has correlated traces.
2. Click a log event to view details.
3. Click **View trace** to open the matching trace in the Trace view.
#### Stream tab [#stream-tab]
1. Open the stream for a logs dataset that has correlated traces.
2. Click a log event to view details.
3. Click **View trace** to open the matching trace in the Trace view.
### Go from traces to logs [#go-from-traces-to-logs]
When a trace belongs to a correlated traces dataset, Axiom queries the correlated logs dataset for log records in the trace time range.
Use this flow when a trace shows a slow or failed span and the investigation needs nearby log records from the same request.
### Go from traces to metrics [#go-from-traces-to-metrics]
When a trace belongs to a correlated traces dataset and the group has a metrics dataset, Axiom shows relevant span metrics in the span details panel.
Trace-to-metrics correlation uses OpenTelemetry semantic conventions and the metric catalog to show common runtime, process, container, and service metrics. Each metric includes an **Explore** action that opens the generated MPL query in the Query tab.
Use this flow when a span shows latency or errors and the investigation needs resource or service health around the same time.
Trace-to-metrics correlation requires the correlated metrics dataset to use an edge deployment.
## What's next [#whats-next]
* [Explore traces](/query-data/traces)
* [Metrics](/query-data/metrics)
* [Datasets overview](/query-data/datasets)
---
# Datasets overview
Source: https://axiom.co/docs/query-data/datasets
The Datasets tab allows you to gain a better understanding of the fields you have in your datasets.
In Axiom, an individual piece of data is an event, and a dataset is a collection of related events. Datasets contain incoming event data. The Datasets tab provides you with information about each field within your datasets.
## Datasets overview [#datasets-overview]
When you open the Datasets tab, you see the list of datasets.
Right-click a dataset to access the following options:
* **Open dataset:** Open the dataset in the Datasets tab.
* **Open in Stream:** Open the dataset in the Stream tab.
* **Open in Query:** Open the dataset in the Query tab.
* **Edit dataset:** Change the dataset description or data retention period.
* **Copy dataset name:** Copy the name of the dataset to your clipboard.
* **Copy link to dataset:** Copy a link to the dataset to your clipboard.
* **Open in new tab:** Open the dataset in a new browser tab.
* **Delete dataset:** Delete the dataset.
### Explore datasets [#explore-datasets]
To explore the fields in a dataset, select the dataset from the list.
When you select a dataset, Axiom displays the list of fields within the dataset on the left. The field types are the following:
* String
* Number
* Boolean
* Array
* [Virtual fields](#virtual-fields)
This view flattens field names with dot notation. This means that the event `{"foo": { "bar": "baz" }}` appears as `foo.bar`. Field names containing periods (`.`) are folded.
By default, when you ingest an event with a field that isn’t yet part of the dataset’s schema, Axiom adds the new field to the schema. To freeze the set of fields in an event dataset and the type of each field, lock its schema. For more information, see [Lock dataset schema](/reference/datasets#lock-dataset-schema).
On the right, you see the following:
* [Views](#views)
* [Saved queries](#saved-queries)
* [Query history](#query-history)
### Edit field [#edit-field]
To edit a field:
1. Go to the Datasets tab.
2. Select the dataset that contains the field.
3. Find the field in the list, and then click it.
4. Edit the following:
* Field description.
* Field unit. This is only available for number field types.
* Hidden. This means that the field is still present in the underlying Axiom database, but it doesn’t appear in the Axiom UI. Use this option if you sent the field to Axiom by mistake or you don’t want to use it anymore in Axiom.
## Quick charts [#quick-charts]
Quick charts allow fast charting of fields depending on their field type. For example, for number fields, choose one of the following for easily visualizing the data:
* Percentiles
* Averages
* Histograms
## Virtual fields [#virtual-fields]
Virtual fields are powerful expressions that run on every event during a query to create new fields. The virtual fields are calculated from the events in the query using an APL expression. They’re similar to tools like derived columns in other products but super-charged with an expressive interpreter and with the flexibility to add, edit, or remove them at any time.
To manage a dataset’s virtual fields, click in the toolbar.
For more information, see [Virtual fields](/query-data/virtual-fields).
## Map fields [#map-fields]
Map fields are a special type of field that can hold a collection of nested key-value pairs within a single field. You can think of the content of a map field as a JSON object. The Dataset tab enables you to create map fields, and view unused and removed map fields. For more information, see [Map fields](/apl/data-types/map-fields#create-map-fields).
## Views [#views]
Views allow you to apply commonly used filters and transformations to your dataset. The result is a view that you can use similarly to how you use datasets. The concept of a view in Axiom is similar to the concept of a virtual table in a database.
For more information, see [Views](/query-data/views).
## Queries [#queries]
Every query has a unique ID that you can save and share with your team members. The Datasets tab allows you to do the following:
* Save a query so that you and your team members can easily find it in the future.
* Browse previous queries and find a past query.
### Saved queries [#saved-queries]
To find and run previously saved queries:
1. Select a dataset.
2. Optional: In the top right of the **Saved queries** section, select whether to display your queries or all queries saved by members of your organization, including queries you saved.
3. Find the query in the list, and then click it to run the query.
Saved queries are automatically visible to all members of your Axiom organization.
### Query history [#query-history]
To find and run recent queries:
1. Select a dataset.
2. Optional: In the top right of the **Query history** section, select whether to display your queries or your team’s queries.
3. Find the query in the list, and then click it to run the query.
---
# Query using Builder
Source: https://axiom.co/docs/query-data/explore
Builder allows you query your data using a visual interface in the Console.
## Typical query workflow [#typical-query-workflow]
Creating a query in Builder typically involves the following steps:
1. **Source:** Select the data you want to query.
2. **Filter:** Narrow down the query results.
3. **Transform and aggregate:** Apply functions to shape and summarize the data before viewing results.
4. **Time range:** Specify the time period over which you want to look up data.
Builder adapts to the type of data you query:
* **Logs, traces, and events:** Select dataset as the source, use **Summarize** to aggregate, and **More** for sort and limit options.
* **Metrics:** Select dataset and metric as the source, use **Transformations** to transform and aggregate the data.
## Query data using Builder [#query-data-using-builder]
1. Click the Query tab.
2. Click **Builder** in the top left.
3. Define the query:
* From the list, select the dataset that you want to query.
* In **Where**, [add filters](#add-filters) to narrow down the query results.
* In **Summarize**, [add visualizations](#add-visualizations) to the query results.
* In **More**, [specify additional options](#more-options) such as sorting the results or limiting the number of displayed events.
* In **Dataset**, define the source of your query. Select an OTel metrics dataset, and then select the metric you want to query.
The dataset and metric names are separated by a colon in the Builder interface. For example, `axiom-dev.metrics:alertmanager_alerts`.
* In **Where**, [add filters](#filter) to narrow down the query results. Restrict the query to a set of series whose tag values match the conditions you specify.
* In **Transformations**, [select transformations](#transformations) to apply to the data.
The metric selector only shows metrics that were active during the selected time range. Expand the time range to see more metrics in the dropdown.
4. [Select the time range](#select-time-range).
5. Click **Run**.
While the query runs, the status bar gives you continuous updates about the number of rows examined, matched, and returned.
See below for more information about each of these steps.
## Example query [#example-query]
This example queries the `sample-http-logs` dataset to find all events where the HTTP status code is 200 and groups the results by county.
This example queries the `axiom-dev.metrics` dataset’s `alertmanager_alerts` metric one hour before the current time. It filters results to events where `k8s.namespace.name` is `monitoring` and aggregates events over 30-second time windows into their average value.
## Query logs, traces, and events [#query-logs-traces-and-events]
### Add filters [#add-filters]
Use the **Where** section to filter the results to specific events. For example, to filter for events that originate in a specific geolocation like France.
To add a filter:
1. Click **+** in the **Where** section.
2. Select the field where you want to filter for values. For example, `geo.country`.
3. Select the logical operator of the filter. These are different for each field type. For example, you can use **starts-with** for string fields and **>=** for number fields. In this example, select `==` for an exact match.
4. Specify the value for which you want to filter. In this example, enter `France`.
When you run the query, the results only show events matching the criteria you specified for the filter.
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20where%20%5B'geo.country'%5D%20%3D~%20'France'%22%7D)
### Add multiple filters [#add-multiple-filters]
You can add multiple filters and combine them with AND/OR operators. For example, to filter for events that originate in France or Germany.
To add and combine multiple filters:
1. Add a filter for France as explained in [Add filters](#add-filters).
2. Add a filter for Germany as explained in [Add filters](#add-filters).
3. Click **and** that appears between the two filters, and then select **or**.
The query results display events that originate in France or Germany.
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20where%20\(%5B'geo.country'%5D%20%3D~%20'France'%20or%20%5B'geo.country'%5D%20%3D~%20'Germany'\)%22%7D)
You can add groups of filters using the **New Group** element.
Axiom supports AND/OR operators at the top level and one level deep.
### Add visualizations [#add-visualizations]
Axiom provides powerful visualizations that display the output of aggregate functions across your dataset. The **Summarize** section provides you with several ways to visualize the query results. For example, the `count` visualization displays the number of events matching your query over time. Some visualizations require an argument such as a field or other parameters.
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20summarize%20count\(\)%20by%20bin_auto\(_time\)%22%7D)
For more information about visualizations, see [Visualize data](/query-data/visualizations).
### Segment data [#segment-data]
When visualizing data, segment data into specific groups to see more clearly how the data behaves. For example, to see how many events originate in each geolocation, select the `count` visualization and group by `geo.country`.
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20summarize%20count\(\)%20by%20bin_auto\(_time\)%2C%20%5B'geo.country'%5D%22%7D)
### More options [#more-options]
In the **More** section, specify the following additional options:
* By default, Axiom automatically chooses the best ordering for the query results. To specify the sorting order manually, click **Sort by**, and then select the field according to which you want to sort the results.
* To limit the number of events the query returns, click **Limit**, and then specify the maximum number of returned events.
* Specify whether to display or hide open intervals.
## Query metrics [#query-metrics]
Your metrics data is organized into datasets, metrics, tags, and series:
* **Dataset:** A group of related metrics.
* **Metric:** A measurement that tracks a specific aspect of your system over time.
* **Tag:** A key-value pair identifying a series.
* **Series:** A unique combination of a metric and tag set.
### Filter [#filter]
Use the **Where** section to filter series based on tag values:
1. Click **+** in the **Where** section.
2. Select the tag where you want to filter for values.
3. Select the logical operator of the filter. Available operators are:
* Equality: `==`, `!=`
* Comparisons: `<`, `<=`, `>`, `>=`
4. Specify the value for which you want to filter.
5. Click **+** to add another filter and join them with the logical and operator.
For example, the following joins three filters: `project == /.*metrics.*/ and code >= 200 and code < 300`.
### Transformations [#transformations]
Use the **Transformations** section to transform individual values or series.
1. Click **+** in the **Transformations** section.
2. Select the transformation you want to apply to the data. Available transformations are:
* **map:** Map the data to a new value using the expression you specify.
* **align:** Aggregate data using the function and the time window you specify.
* **group:** Group the data by a set of tags using the aggregation function you specify.
* **bucket** Aggregate the data by time and tag dimensions simultaneously.
#### Map [#map]
Use `map` to transform individual values.
Available mapping functions:
| Function | Description |
| --------------------- | ------------------------------------------------------- |
| `rate` | Computes the per-second rate of change for a metric. |
| `abs` | Returns the absolute value of each data point. |
| `interpolate::linear` | Linear interpolation of missing values. |
| `fill::prev` | Fills missing values using the previous non-null value. |
For example, to calculate rate per second for the metric, use `map rate`. To fill empty values with the latest value, use `map fill::prev`.
#### Align [#align]
Use `align` to aggregate over time windows. You can specify the time window and the aggregation function to apply.
Available aggregation functions:
| Function | Description |
| ------------ | -------------------------------------- |
| `avg` | Averages values in each interval. |
| `count` | Counts non-null values per interval. |
| `max` | Takes the maximum value per interval. |
| `min` | Takes the minimum value per interval. |
| `prom::rate` | PromQL-style rate calculation. |
| `sum` | Sums values in each interval. |
| `last` | Takes the last value in each interval. |
For example, to calculate the average over 5-minute time windows, use `align to 5m using avg`. To count the data points in the last hour, use `align to 1h using count`.
When metrics emit data at regular intervals but with different offsets across time series, `align` maps your data to a fixed set of time intervals. This makes it easier to visualize data from different sources on a single chart. If the alignment interval is shorter than the emission interval, some intervals can have null values. Select how to handle null values in the [chart options](/query-data/query-results#configure-chart-options).
#### Group [#group]
Use `group` to combine series by tags. You can specify the tags to group by and the aggregation function to apply. If you don’t specify tags, Axiom aggregates all series into one group.
Available aggregation functions:
| Function | Description |
| -------- | ---------------------------------- |
| `avg` | Averages values in each group. |
| `count` | Counts non-null values per group. |
| `max` | Takes the maximum value per group. |
| `min` | Takes the minimum value per group. |
| `sum` | Sums values in each group. |
For example:
* To calculate the number of series, use `group using count`.
* To sum the values of all series, use `group using sum`.
* To group data by the `project` and `namespace` tags using the `sum` aggregation, use `group by project, namespace using sum`.
#### Bucket [#bucket]
Use `bucket` to aggregate over time and tag dimensions simultaneously.
Available aggregation functions:
| Function | Description |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `avg` | Averages values in each group and time inverval. |
| `count` | Counts non-null values per group and time inverval. |
| `max` | Takes the maximum value per group and time inverval. |
| `min` | Takes the minimum value per group and time inverval. |
| `sum` | Sums values in each group and time inverval. |
| `quantile` | Computes the specified quantile per group and time inverval. Specify the quantile as a value between 0 and 1. For example, specifying 0.99 corresponds to the 99th percentile. |
For example:
* To calculate the average value per group and time interval, use `bucket using histogram(avg)`.
* To also count the number of data points per group and time interval, use `bucket using histogram(count, avg)`.
To show the results of multiple aggregations in a single chart, use multiple functions.
If you don’t specify tags with `bucket`, Axiom applies the specified aggregations to all tags.
When the underlying metric is a histogram, Axiom computes `bucket` aggregations using the `interpolate_cumulative_histogram` and `interpolate_delta_histogram` functions depending on the temporality used. For quantile aggregations, this estimates the quantile using the underlying histogram data.
## Select time range [#select-time-range]
When you select the time range of a query, you specify the time interval where you want to look for events.
To select the time range, choose one of the following options:
1. In the top left, click **Time range**.
2. Choose one of the following options:
* Use the **Quick range** items to quickly select popular time ranges.
* To the right of the **Start date** and **End date** fields, click **Select date and time** to select specific times.
* Enter the specific time in the **Start date** and **End date** fields using natural language. For example, `1 hour ago` or `last Monday at 3pm`. For more information on the supported natural language inputs, see the [Sugar documentation](https://sugarjs.com/docs/#/DateParsing). Don’t use the `from` and `after` keywords in your input.
---
# Create dashboards with filters
Source: https://axiom.co/docs/query-data/filters
Filters let you choose the data you want to display in your dashboard. This page explains how to create and configure dashboards with filters.
Try out all the examples explained on this page in the [HTTP logs dashboard of the Axiom Playground](https://play.axiom.co/axiom-play-qf1k/dashboards/gZXp8KNJy68q7yGsuA).
## Filter types [#filter-types]
You can use two types of filter in your dashboards:
* Search filters let you enter any text, filter for data that matches the text input, and then narrow down the results displayed by the charts in the dashboard. For example, you enter **Mac OS**, filter for results that contain this string in the user agent field, and then only display the corresponding results in the charts.
* Select filters let you choose one option from a list of options, filter for data that matches the chosen option, and then narrow down the results displayed by the charts in the dashboard. For example, you choose **France** from the list of countries, filter for results that match the chosen geographical origin, and then only display the corresponding results in the charts.
## Use dashboards with filters [#use-dashboards-with-filters]
To see different filters in action, check out the [HTTP logs dashboard of the Axiom Playground](https://play.axiom.co/axiom-play-qf1k/dashboards/gZXp8KNJy68q7yGsuA). The search filter on the top right lets you search for a specific phrase in the user agent field to only display HTTP requests from a specific user agent. The select filters on the top left let you choose country and city to only display HTTP requests from a specific geographical origin.
In each chart on your dashboard, you can use all, some, or none of the filters to narrow down the data displayed in the chart. For example, in the [HTTP logs dashboard of the Axiom Playground](https://play.axiom.co/axiom-play-qf1k/dashboards/gZXp8KNJy68q7yGsuA), the charts **Popular data centers** and **Popular countries** aren’t affected by your choices in the select filters. You choose to use a filter in a chart by [referencing the unique ID of the filter in the chart query](#reference-filters-in-chart-query) as explained later on this page.
Filters can be interdependent. For example, in the [HTTP logs dashboard of the Axiom Playground](https://play.axiom.co/axiom-play-qf1k/dashboards/gZXp8KNJy68q7yGsuA), the values you can choose in the city filter depend on your choice in the country filter. You make a filter dependent on another by [referencing the unique ID of the filter](#create-select-filters) as explained later on this page.
For each filter, you define a unique ID when you create the filter. When you create multiple filters, all of them must have a different ID. You can later use this ID to reference the filter in dashboard charts and other filters.
Each filter must have a unique ID. This means that the filter IDs must be different from each other and from the field names in your data. In general, starting filter IDs with an underscore (`_`) can help avoid naming conflicts with fields in your data. For example, if you have a field named `user_agent`, you can define the filter ID as `_user_agent_filter`.
Filters are visually displayed in your dashboard in a filter bar that you can create and move as any other chart. You can add different types of filter to a single filter bar. A filter bar can contain maximum one search filter and any number of select filters.
## Create search filters [#create-search-filters]
1. In the empty dashboard, click **Edit dashboard**.
2. Click **Add element**.
3. In **Chart type**, select **Filter bar**.
4. In **Filter type**, select **Search**.
5. In **Filter name**, enter the placeholder text you want to display in your search filter.
6. Specify a unique filter ID that you later use to reference the filter. For example, `user_agent_filter`.
Try out this filter in the [HTTP logs dashboard of the Axiom Playground](https://play.axiom.co/axiom-play-qf1k/dashboards/gZXp8KNJy68q7yGsuA).
## Create select filters [#create-select-filters]
1. In the empty dashboard, click **Edit dashboard**.
2. Click **Add element**.
3. In **Chart type**, select **Filter bar**.
4. In **Filter type**, select **Select**.
5. In **Filter name**, enter the text you want to display above the select filter.
6. Specify a unique filter ID that you later use to reference the filter. For example, `country_filter`.
7. In the **Value** section, define the list of options to choose from in the select filter as key-value pairs. Axiom displays the key in the list of options in the filter dropdown, and uses the value to filter your data. For example, the key `France` is displayed in the list of options, and the value `FR` is used to filter data in your charts. Define the key-value pairs in one of the following ways:
* Choose **List** to manually define a static list of options. Enter the options as a list of key-value pairs.
* Choose **Query** to define a dynamic list of options. In this case, Axiom determines the list of options displayed in the filter dynamically based on a query. You can use an APL query or an MPL metrics query, as explained in [Define options with an APL query](#define-options-with-an-apl-query) and [Define options with a metrics query](#define-options-with-a-metrics-query).
### Define options with an APL query [#define-options-with-an-apl-query]
To populate the list of options from an APL query, the results must contain a `key` and a `value` field, which Axiom interprets as key-value pairs. Use the `project` command to create these fields from any output.
The value in the key-value pairs must be a string. To use number or Boolean fields, convert their values to strings using [`tostring()`](/apl/scalar-functions/conversion-functions#tostring\(\)).
The example APL query below uses the distinct values in the `geo.country` field to populate the list of options. It projects these values as both the key and the value and sorts them in alphabetical order.
```kusto
['sample-http-logs']
| distinct ['geo.country']
| project key=['geo.country'] , value=['geo.country']
| sort by key asc
```
See this filter in action in the [HTTP logs dashboard of the Axiom Playground](https://play.axiom.co/axiom-play-qf1k/dashboards/gZXp8KNJy68q7yGsuA).
### Define options with a metrics query [#define-options-with-a-metrics-query]
To populate the list of options from an MPL metrics query, group by exactly one tag. Axiom uses the distinct values of that tag as the filter options.
Add `align using last` before the `group by` clause to simplify the query and reduce the data Axiom processes when generating the options.
The example MPL query below populates the list of options with the distinct values of the `service.name` tag in the `http.server.request.duration` metric:
```kusto
`otel-demo-metrics`:`http.server.request.duration`
| align using last
| group by `service.name` using count
```
### Create dependent select filters [#create-dependent-select-filters]
Sometimes it makes sense that filters depend on each other. For example, in one filter you select the country, and in the other filter the city. In this case, the list of options in the city filter depends on your choice in the country filter.
To create a filter that depends on another filter, follow these steps:
1. Create a filter. In this example, the ID of the independent filter is `country_filter`.
2. Create a dependent select filter. In this example, the ID of the dependent select filter is `city_filter`. The dependent filter must be a select filter.
3. In the dependent filter, use `declare query_parameters` at the beginning of your query to reference the independent filter’s ID. For example, `declare query_parameters (country_filter:string = "")`. This lets you use `country_filter` as a parameter in your query even though it doesn’t exist in your data. For more information, see [Declare query parameters](#declare-query-parameters).
4. Use the `country_filter` parameter to filter results in the dependent filter’s query.
The example APL query below defines the dependent filter. It uses the value of the independent filter with the ID `country_filter` to determine the list of options in the dependent filter. Based on the selected country, the APL query uses the distinct values in the `geo.city` field to populate the list of options. It projects these values as both the key and the value and sorts them in alphabetical order.
```kusto
declare query_parameters (country_filter:string = "");
['sample-http-logs']
| where isnotempty(['geo.country']) and isnotempty(['geo.city'])
| where ['geo.country'] == country_filter
| summarize count() by ['geo.city']
| project key = ['geo.city'], value = ['geo.city']
| sort by key asc
```
Check out this filter in the [HTTP logs dashboard of the Axiom Playground](https://play.axiom.co/axiom-play-qf1k/dashboards/gZXp8KNJy68q7yGsuA).
### Filter to select datasets [#filter-to-select-datasets]
Use a select filter to choose the dataset whose values you want to display in the dashboard:
1. Create a select filter where you specify the dataset names as the keys and the values. In this example, the ID of the independent filter is `dataset_filter`.
2. Create a dashboard element based on an APL query:
* Use `declare query_parameters` at the beginning of your query to reference the filter’s ID. For example, `declare query_parameters (dataset_filter:string = "")`.
* Use the `dataset_filter` parameter as the value of the `table` operator to filter for dataset names in the query.
The APL query below defines a chart where the data displayed depends on your choice in the filter with the ID `dataset_filter`. The chart only displays data from the selected dataset.
```kusto
declare query_parameters (dataset_filter:string = "");
table(dataset_filter)
| summarize count() by bin_auto(_time)
```
## Reference filters in chart queries [#reference-filters-in-chart-queries]
After creating a filter, specify how you want to use the value chosen in the filter. Include the filter in the APL query of each chart where you want to use the filter to narrow down results. To do so, use `declare query_parameters` at the beginning of the chart’s APL query to reference the filter’s ID. For example, `declare query_parameters (country_filter:string = "")`. This lets you use `country_filter` as a parameter in the chart’s query even though it doesn’t exist in your data. For more information, see [Declare query parameters](#declare-query-parameters).
The APL query below defines a statistic chart where the data displayed depends on your choice in the filter with the ID `country_filter`. For example, if you choose **France** in the filter, the chart only displays the number of HTTP requests from this geographical origin.
```kusto
declare query_parameters (country_filter:string = "");
['sample-http-logs']
| where isempty(country_filter) or ['geo.country'] == country_filter
| summarize count() by bin_auto(_time)
```
## Combine filters [#combine-filters]
You can combine several filters of different types in a chart’s query. For example, the APL query below defines a statistic chart where the data displayed depends on three filters:
* A select filter that lists countries.
* A select filter that lists cities within the chosen country.
* A search filter that lets you search in the `user_agent` field.
```kusto
declare query_parameters (country_filter:string = "",
city_filter:string = "",
user_agent_filter:string = "");
['sample-http-logs']
| where isempty(country_filter) or ['geo.country'] == country_filter
| where isempty(city_filter) or ['geo.city'] == city_filter
| where isempty(user_agent_filter) or user_agent contains user_agent_filter
| summarize count() by bin_auto(_time)
```
See this filter in action in the Total requests chart in the [HTTP logs dashboard of the Axiom Playground](https://play.axiom.co/axiom-play-qf1k/dashboards/gZXp8KNJy68q7yGsuA).
## Declare query parameters [#declare-query-parameters]
Use `declare query_parameters` at the beginning of an APL query to reference a filter’s ID. For example, `declare query_parameters (country_filter:string = "")`. This lets you use `country_filter` as a parameter in the chart’s query even though it doesn’t exist in your data.
You must define unique IDs for each filter. This means that the filter IDs must be different from each other and from the field names in your data. In general, starting filter IDs with an underscore (`_`) can help avoid naming conflicts with fields in your data. For example, if you have a field named `user_agent`, you can define the filter ID as `_user_agent_filter`.
The `declare query_parameters` statement defines the data type of the parameter. In the case of filters, the data type is always string.
## Choose default option in select filter [#choose-default-option-in-select-filter]
The default option of a select filter is the option chosen when the dashboard loads. In most cases, this means that no filter is applied. This option is added automatically as the first in the list of options when you create the filter with the key **All** and an empty value. To choose another default value, reorder the list of options.
## Handle empty values [#handle-empty-values]
The examples on this page assume that you use the default setting where the **All** key means an empty value, and the empty value in a filter means that the data isn’t filtered in the chart. The example chart queries above handle this empty (null) value in the `where` clause. For example, `where isempty(country_filter) or [‘geo.country’] == country_filter` means that if no option is chosen in the country filter, `isempty(country_filter)` is true and the data isn’t filtered. If any other option is chosen with a non-null value, the chart only displays data where the `geo.country` field’s value is the same as the value chosen in the filter.
## Use filters with metrics queries [#use-filters-with-metrics-queries]
Filter bars work with MPL metrics queries as well as APL queries.
### Reference filter variables [#reference-filter-variables]
In an MPL query, reference a filter by prefixing its ID with `$`. For example, a filter with ID `service_filter` is referenced as `$service_filter`.
Use `$filter_id` in a `where` clause to filter metric series by the selected value:
```kusto
dataset:metric
| where tag_name == $filter_id
| align to $__interval using avg
```
**Example:**
The query below filters `http.server.request.duration` to a specific service chosen in the filter bar with ID `service_filter`:
```kusto
`otel-demo-metrics`:`http.server.request.duration`
| where `service.name` == $service_filter
| align to $__interval using avg
```
### Optional filters [#optional-filters]
By default, a filter must have a value selected for the query to run. To make a filter optional — so the query returns all data when no value is selected — use the **Unset** default and an `ifdef` block.
**Configure the filter bar:**
1. Create a select filter as described in [Create select filters](#create-select-filters).
2. In the filter options, check the **Unset** box for the default value.
Checking **Unset** tells Axiom to treat the variable as optional. When no option is selected, the variable isn't passed to the query.
**Write the query:**
Wrap the `where` clause in an `ifdef` block so it only runs when the variable is set:
```kusto
dataset:metric
| ifdef($filter_id) { where tag_name == $filter_id }
| align to $__interval using avg
```
**Example:**
The query below filters by `service.name` only when a value is selected. When the filter is unset, the query returns data for all services.
```kusto
`otel-demo-metrics`:`http.server.request.duration`
| ifdef($service_filter) { where `service.name` == $service_filter }
| align to $__interval using avg
```
---
# Metrics
Source: https://axiom.co/docs/query-data/metrics
Axiom’s dedicated MetricsDB provides a purpose-built metrics datastore that handles high-cardinality time-series data without the cost penalties and performance degradation common in traditional metrics systems. This section explains how to work with OpenTelemetry (OTel) metrics in Axiom.
## What makes MetricsDB different [#what-makes-metricsdb-different]
MetricsDB is engineered from the ground up to embrace dimensional complexity:
* **High-cardinality as a design principle**: Store metrics with high-cardinality tags. Where other metrics datastore penalize you with higher costs or degraded performance, MetricsDB treats high cardinality as a core capability.
* **Intentional architecture**: The storage format, query engine, and compression algorithms are specifically optimized for time-series metrics workloads. These design constraints are thoughtful trade-offs that deliver exceptional performance and cost-efficiency for real-world metrics use cases.
* **Unified observability**: Query metrics alongside logs, traces, and events, enabling powerful correlations across all your telemetry data without switching tools or learning multiple query languages.
### MetricsDB vs EventDB [#metricsdb-vs-eventdb]
Logs, traces, events, and metrics are different forms of machine data. It’s often possible to represent metrics as events, but a dedicated metrics datastore comes with the following benefits:
* **Efficient compression**: Instrumentation aggregates measurements on the client and sends summaries at regular intervals instead of individual events. This dramatically reduces data volume for high-frequency measurements.
* **Standardized data types**: OpenTelemetry defines a small set of metric types with clear semantics. The metric type tells you how to query the data—no guessing how to aggregate or calculate rates.
* **Faster queries**: Metrics data is stored in a format optimized for producing time series and other popular metrics visualizations. No need to process the data at query time.
### When to use MetricsDB or EventDB for metrics [#when-to-use-metricsdb-or-eventdb-for-metrics]
| Use MetricsDB when | Use EventDB when |
| ---------------------------------------------------------------------------- | ------------------------------------- |
| You can send metrics using the OTel Collector or another OTel-compatible SDK | You can’t use OTel-compatible tooling |
| High-frequency measurements (many per second) | Low-frequency point-in-time gauges |
| You need rate calculations or percentiles | Simple aggregations are sufficient |
When in doubt, use MetricsDB. It’s designed for metrics workloads and scales better as volume grows.
You must use a dedicated dataset for OTel metrics. When you create a dataset, select the type of OTel data you want to send to it. For more information, see [Create dataset](/reference/datasets#create-dataset).
## Ingest metrics [#ingest-metrics]
You can ingest OTel metrics the same way you ingest other types of OTel data.
For more information, see [Send OpenTelemetry data to Axiom](/send-data/opentelemetry).
The `/v1/metrics` endpoint only supports the `application/x-protobuf` content type. JSON format isn’t supported for metrics ingestion.
### Try it quickly with axiom-hostmetrics [#try-it-quickly-with-axiom-hostmetrics]
[axiom-hostmetrics](https://github.com/axiomhq/axiom-hostmetrics) is a small CLI that scrapes host metrics (CPU, memory, disk, network, and more) using the OpenTelemetry `hostmetricsreceiver` and ships them to Axiom via OTLP/HTTP. It’s a fast way to get real metrics data flowing so you can explore MetricsDB and MPL without setting up a full OTel Collector.
Install with Homebrew:
```sh
brew install axiomhq/tap/axiom-hostmetrics
```
Or install with Go:
```sh
go install github.com/axiomhq/axiom-hostmetrics/cmd/axiom-hostmetrics@latest
```
Run `axiom-hostmetrics` with no arguments to launch a setup wizard that asks for your dataset, API token, and endpoint, then starts streaming metrics immediately.
## Query metrics [#query-metrics]
You can query metric data in one of the following ways:
* **Builder**: A visual interface for building queries step by step. For more information, see [Query data using Builder](/query-data/explore).
* **MPL**: A text-based query language (Metrics Processing Language) for writing expressive queries directly. For more information, see [Query metrics using MPL](/mpl/introduction) and [Sample queries](/mpl/sample-queries).
Support for MPL (Metrics Processing Language) is currently in public preview. For more information, see [Feature states](/platform-overview/roadmap#feature-states).
* **Skills**: Use the [Query metrics skill](/console/intelligence/skills/query-metrics) to give AI agents the ability to query your metrics data via API.
* **MCP**: Use the [Axiom MCP server](/console/intelligence/mcp-server) to allow an MCP-compatible client to query your metrics data.
## Migrate PromQL queries [#migrate-promql-queries]
You can translate your existing PromQL queries to MPL with minimal changes. MPL also offers features not available in PromQL, such as native support for non-string tag types and custom transformation pipelines.
For more information, see [Migrate PromQL queries to Axiom](/mpl/migrate-metrics).
## Dashboards and monitors [#dashboards-and-monitors]
You can use OTel metrics in dashboards and monitors the same way you use other data types.
* Build visualizations using metrics queries.
* Set alerts on derived metrics such as error rate or latency percentiles.
* Combine multiple signals in a single panel.
For more information, see [Dashboards](/dashboards/overview) and [Monitors](/monitor-data/monitors).
## Design choices and constraints [#design-choices-and-constraints]
MetricsDB makes intentional architectural trade-offs to optimize for the most common metrics use cases while maintaining exceptional performance at scale.
### Query scope [#query-scope]
You can query one dataset per query.
### Supported data types [#supported-data-types]
MetricsDB focuses on the core OpenTelemetry metric types that cover the vast majority of observability scenarios.
Axiom supports the following OpenTelemetry metric types:
* **Gauge**: Point-in-time measurements. For example, CPU usage or temperature.
* **Histogram**: Distribution of values with configurable buckets. For example, request latency.
* **Sum**: Sum of values. For example, request count.
* **Summary**: Summary of values. For example, request latency.
Axiom doesn’t currently support the following data types:
* Exponential histograms
* `bytes`, `kvlist`, and `array` tag value types
* Exemplar, baggage, and context data
* Nanosecond-precision timestamps
### Data model optimizations [#data-model-optimizations]
MetricsDB applies the following transformations to improve query performance and reduce storage costs:
* **Timestamp precision**: Truncate nanosecond timestamps to second precision. MetricsDB is built for use cases where second-level granularity is sufficient, and this optimization significantly improves compression ratios and query speed.
* **Unified tag namespace**: Flatten resource, scope, and metric tags into a single namespace. This simplification makes queries more straightforward and enables faster dimensional filtering. You don’t need to remember which tags came from which scope.
* **Unit normalization**: Convert the `unit` attribute to `otel.metric.unit` for consistent handling across all metric types.
* **Histogram handling**: Assume equal-width histograms and don’t preserve histogram metadata. This trade-off supports the most common histogram analysis patterns (percentiles, distribution visualization) while reducing storage requirements.
These design choices reflect real-world metrics usage patterns. If your use case requires capabilities not currently supported, [contact Axiom](https://axiom.co/contact) to discuss your requirements. Your feedback helps shape MetricsDB’s evolution.
---
# Query using Editor
Source: https://axiom.co/docs/query-data/query-editor
Query your data using the text-based query editor in Console:
1. Click the Query tab.
2. Click **Editor** in the top left.
3. Write your query in the editor.
4. Click **Run**.
The Editor of the Query tab accepts both APL (Axiom Processing Language) and MPL (Metrics Processing Language) queries and automatically detects the query language you use:
* APL is a data processing language that supports filtering, extending, and summarizing data. For more information, see [Introduction to APL](/apl/introduction).
* MPL is a metric-focused query language that combines the simplicity of APL with the expressive power of PromQL. It enables effective querying, transformation, and aggregation of metric data, supporting diverse observability use cases. For more information, see [Introduction to MPL](/mpl/introduction).
## Generate query using natural language [#generate-query-using-natural-language]
Instead of writing the query yourself, you can use Axiom AI to generate a query for you. Explain what you want to infer from your data in your own words and Axiom AI generates the valid APL query.
Query using natural language isn't currently supported for metrics datasets.
To generate a query using natural language:
1. Click the Query tab.
2. Click **APL**, and then click in the query editor.
3. Press Cmd/Ctrl K .
4. Type what you want to infer from your data in your own words using natural language, and then click **Generate**. For example, type `Show me the most common status responses in HTTP logs.`
5. Axiom’s AI generates the APL query based on your prompt and gives you the following options:
* Click **Accept** to update the editor with the generated query and change the generated query before running it. Any previous input in the query editor is lost.
* Click **Accept and run** to update the editor with the generated query and run it immediately. Any previous input in the query editor is lost.
* Click **Reject** to go back to your previous input in the query editor and close the query generator.
### Iterate over prompt history [#iterate-over-prompt-history]
Axiom saves the prompts you type in the query generator. To find one of your previous prompts and generate an APL query for it:
1. Click the Query tab.
2. Click **APL**, and then click in the query editor.
3. Press Cmd/Ctrl K .
4. Cycle through your history using the arrow keys and to find the prompt.
5. Click **Generate**.
## APL examples [#apl-examples]
Some APL queries are explained below. The pipe symbol `|` separates the operations as they flow from left to right, and top to bottom.
APL is case-sensitive for everything: dataset names, field names, operators, functions, etc.
Use double forward slashes (`//`) for comments.
### `count` operator [#count-operator]
The below query returns the number of events from the `sample-http-logs` dataset.
```kusto
['sample-http-logs']
| summarize count()
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20summarize%20count\(\)%22%7D)
### `limit` operator [#limit-operator]
The `limit` operator returns a random subset of rows from a dataset up to the specified number of rows. This query returns a thousand rows from `sample-http-logs` randomly chosen by APL.
```kusto
['sample-http-logs']
| limit 1000
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20limit%201000%22%7D)
### `summarize` operator [#summarize-operator]
The `summarize` operator produces a table that aggregates the content of the dataset. This query returns a chart of the `avg(req_duration_ms)`, and a table of `geo.city` and `avg(req_duration_ms)` of the `sample-http-logs` dataset from the time range of 2 days and time interval of 4 hours.
```kusto
['sample-http-logs']
| where _time > ago(2d)
| summarize avg(req_duration_ms) by _time=bin(_time, 4h), ['geo.city']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20where%20_time%20%3E%20ago\(2d\)%5Cn%7C%20summarize%20avg\(req_duration_ms\)%20by%20_time%3Dbin\(_time%2C%204h\)%2C%20%5B'geo.city'%5D%22%7D)
## MPL examples [#mpl-examples]
### `align` operator [#align-operator]
The `align` operator aggregates metric values over time windows. This query returns the average value of the `go.memory.used` metric from the `otel-demo-metrics` dataset, grouped into 5-minute intervals.
```kusto
`otel-demo-metrics`:`go.memory.used`
| align to 5m using avg
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%60otel-demo-metrics%60%3A%60go.memory.used%60%20%7C%20align%20to%205m%20using%20avg%22%2C%22metricsDataset%22%3A%22otel-demo-metrics%22%7D)
### `where` operator [#where-operator]
The `where` operator restricts results to series whose tag values match the specified conditions. This query returns the average value of the `go.memory.used` metric from the `otel-demo-metrics` dataset for the `checkout` deployment, aligned over 5-minute windows.
```kusto
`otel-demo-metrics`:`go.memory.used`
| where `k8s.deployment.name` == "checkout"
| align to 5m using avg
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%60otel-demo-metrics%60%3A%60go.memory.used%60%20%7C%20where%20%60k8s.deployment.name%60%20%3D%3D%20%5C%22checkout%5C%22%20%7C%20align%20to%205m%20using%20avg%22%2C%22metricsDataset%22%3A%22otel-demo-metrics%22%7D)
### `group` operator [#group-operator]
The `group` operator combines multiple series by tag values into a single aggregated series. This query returns the sum of `go.memory.used` per deployment, aligned to 5-minute windows, showing memory usage broken down by `k8s.deployment.name`.
```kusto
`otel-demo-metrics`:`go.memory.used`
| align to 5m using avg
| group by `k8s.deployment.name` using sum
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%60otel-demo-metrics%60%3A%60go.memory.used%60%20%7C%20align%20to%205m%20using%20avg%20%7C%20group%20by%20%60k8s.deployment.name%60%20using%20sum%22%2C%22metricsDataset%22%3A%22otel-demo-metrics%22%7D)
## What's next [#whats-next]
* [Interpret query results](/query-data/query-results)
* [Visualize data](/query-data/visualizations)
* [Views](/query-data/views)
* [Virtual fields](/query-data/virtual-fields)
---
# Query results
Source: https://axiom.co/docs/query-data/query-results
The results view adapts to the query. This means that it adds and removes components as necessary to give you the best experience. The toolbar is always visible and gives details on the currently running or last-run query.
The features explained on this page are available for the results of both APL and MPL queries.
{/*
## Interpret query results
TODO
*/}
## Configure chart options [#configure-chart-options]
Click to access the following options for each chart:
* In **Null values**, specify how to treat missing or undefined values.
* In **Variant**, specify the chart type. Select from area, bar, or line charts.
* In **Y-Axis**, specify the scale of the vertical axis. Select from linear or log scales.
* In **Annotations**, specify the types of annotations to display in the chart.
For more information on each option, see [Configure dashboard elements](/dashboard-elements/configure).
## Merge charts [#merge-charts]
When you run a query that produces several visualizations, Axiom displays the charts separately. For example:
```kusto
['sample-http-logs']
| summarize percentiles_array(req_duration_ms, 50, 90, 95) by status, bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20summarize%20percentiles_array\(req_duration_ms%2C%2050%2C%2090%2C%2095\)%20by%20status%2C%20bin_auto\(_time\)%22%7D)
To merge the separately displayed charts into a single chart, click , and then select **Merge charts**.
## APL query results [#apl-query-results]
When you run an APL query without specifying a visualization, Axiom displays a table with the raw query results.
When you run an APL query with visualizations:
* Axiom displays all the visualizations that you add to the query. Hold the pointer over charts to get extra detail on each result set.
* Below the charts, Axiom displays a table with the totals from each of the aggregate functions for the visualizations you specify.
* If the query includes group-by clauses, there is a row for each group. Hold the pointer over a group row to highlight the group’s data on time series charts. Select the checkboxes on the left to display data only for the selected rows.
This section explains how to work with the results of APL queries in Console.
The features explained in this section are only available for the results of APL queries.
### View event details [#view-event-details]
To view the details for an event, click the event in the table.
To configure the event details view, select one of the following in the top right corner:
* Click **Navigate up** or **Navigate down** to display the details of the next or previous event.
* Click **Fit panel to results** or **Fit panel to viewport height** to change the height of the event details view.
In the event details view, click **More** for additional options:
* **View in context** opens the event in the stream of other events in the Stream tab.
* **Copy link to event**
* **Copy JSON**
* **Show nulls** or **Hide nulls** toggles whether to display fields with null values.
### Select displayed fields [#select-displayed-fields]
To select the fields to be highlighted or displayed in the table, click **Toggle fields panel**, and then click the fields in the list.
Select **Single column for event** to highlight the selected fields below the raw data for each event. Alternatively, select **Column for each field** to display each selected field in a different column without showing the raw event data. In this view, you can resize the width of columns by dragging the borders.
### Configure table options [#configure-table-options]
To configure the table options, click , and then select one of the following:
* Select **Fit columns** to automatically adjust the width of columns to fit the viewport.
* Select **Wrap lines** to keep the whole table within the viewport and avoid horizontal scrolling.
* Select **Show timestamp** to display the time field.
* Select **Show event** to display the raw event data in a single column and highlight the selected fields below the raw data for each event. Alternatively, clear **Show event** to display each selected field in a different column without showing the raw event data. In this view, you can resize the width of columns by dragging the borders.
* Select **Hide nulls** to hide empty data points.
### Event timeline [#event-timeline]
Axiom can also display an event timeline about the distribution of events across the selected time range. In the event timeline, each bar represents the number of events matched within that specific time interval. Holding the pointer over a bar reveals a blue line marking the total events and shows when those events occurred in that particular time range. To display the event timeline, click , and then click **Show chart**.
### Highlight time range [#highlight-time-range]
In the event timeline, line charts, and heat maps, you can drag the pointer over the chart to highlight a specific time range, and then choose one of the following:
* **Zoom** enlarges the section of the chart you highlighted.
* **Show events** displays events in the selected time range in the event details view.
The time range of your query automatically updates to match what you selected.
### Search within query results [#search-within-query-results]
To quickly search for an expression and highlight its occurrences within the query results:
1. In the query results view, press Cmd/Ctrl F .
2. Type the expression that you want to search for. Axiom automatically highlights the matches and jumps to the first match.
3. Press Enter repeatedly to go forward in the list of matches, and press Enter Shift to go backward.
Axiom’s search overrides the browser’s native search. Axiom’s search is more powerful because it highlights matching entries in all results returned by the query (while still respecting automatic limits). In contrast, the browser’s search can only highlight matching entries in the events rendered on your screen.
### Compare time periods [#compare-time-periods]
On time series charts, holding the pointer over a specific time shows the same marker on similar charts for easy comparison.
When you run a query with a time series visualization, you can use the **Compare period** menu to select a historical time against which to compare the results of your time range. For example, to compare the last hour’s average response time to the same time yesterday, select `1 hr` in the time range menu, and then select `-1 day` from the **Compare period** menu. The dotted line represents results from the base date, and the totals table includes the comparative totals.
---
# Save and export queries
Source: https://axiom.co/docs/query-data/save-export-query
Save and export queries and their results to use them in other contexts.
## Save query [#save-query]
Save a query so that you and your team members can easily find it in the future. A saved query only includes the APL query itself, not the query results. Saved queries are automatically visible to all members of your Axiom organization. You can later [find saved queries](/query-data/datasets#saved-queries) in the Datasets tab.
### Create new saved query [#create-new-saved-query]
1. Click **Save** in the top bar.
2. Axiom AI generates a descriptive name based on the query. Accept it or edit it to fit your needs.
3. Click **Save**.
### Replace previously saved query [#replace-previously-saved-query]
1. Click **Save** in the top bar.
2. Click **Replace previously saved query**.
3. Select the existing saved query that you want to overwrite.
4. Click **Save**.
## Export query [#export-query]
To export a query and its results, click **More** in the top bar to access the following options:
* **Add to dashboard** lets you create dashboard elements based on the query and add them to a dashboard. For more information, see [Create dashboard elements](/dashboard-elements/create).
* **Create new monitor** lets you create a monitor based on the query. For more information, see [Monitors](/monitor-data/monitors).
* **Copy results** lets you copy the query results to your clipboard in JSON, CSV, TOON (structured), or TOON (list) format. TOON is a structured data format that's more efficient when sharing data with LLMs and other tools. Choose TOON (structured) for hierarchical data or TOON (list) for flat representations. For more information, see the [TOON documentation](https://github.com/toon-format/toon).
* **Download results** lets you download the query results in JSON, CSV, TOON (structured), or TOON (list) format.
* **Copy link with relative time** copies a link to the query where the time range is relative to the time when you open the link. For example, if the time range of the query is the last 30 minutes, using the link shows query results for the 30-minute time range before opening the link.
* **Copy link with absolute time** copies a link to the query where the time range is fixed to the same time range that you see in the query when you create the link. This link shows query results for the same time range, irrespective of when you open the link.
---
# Stream data with Axiom
Source: https://axiom.co/docs/query-data/stream
The Stream tab allows you to inspect individual events and watch as they’re ingested live.
It can be incredibly useful to be able to live-stream events as they’re ingested to know what’s going on in the context of the entire system. Like a supercharged terminal, the Stream tab in Axiom allows you to view streams of events, filter them to only see important information, and finally inspect each individual event.
This section introduces the Stream tab and its components that unlock powerful insights from your data.
## Choose a dataset [#choose-a-dataset]
The default view is one where you can easily see which datasets are available and also see some recent saved queries in case you want to jump directly into a stream:
Select a dataset from the list of datasets to continue.
## Event stream [#event-stream]
Upon selecting a dataset, you are immediately taken to the live event stream for that dataset:
You can click an event to be taken to the event details slide-out:
On this slide-out, you can copy individual field values, or copy the entire event as JSON.
You can view and copy the raw data:
## Filter data [#filter-data]
The Stream tab provides access to a powerful filter builder right on the toolbar:
For more information, see the [filters documentation](/dashboard-elements/create#filters).
## Time range selection [#time-range-selection]
The stream has two time modes:
* Live stream (default)
* Time range
Live stream continuously checks for new events and presents them in the stream.
Time range only shows events that fall between a specific start and end date. This can be useful when investigating an issue. The time range menu has some options to quickly choose some time ranges, or you can input a specific range for your search.
When you are ready to return to live streaming, click this button:
Click the button again to pause the stream.
## View settings [#view-settings]
The Stream tab is customizable via the view settings menu:
Options include:
* Text size used in the stream
* Wrap lines
* Highlight severity (this is automatically extracted from the event)
* Show the raw event details
* Fields to display in their own column
## Saved queries [#saved-queries]
The saved queries slide-out is activated via the toolbar:
For more information, see [Saved queries](/query-data/datasets#saved-queries).
## Highlight severity [#highlight-severity]
The Stream tab allows you to easily detect warnings and errors in your logs by highlighting the severity of log entries in different colors.
To highlight the severity of log entries:
1. Specify the log level in the data you send to Axiom. For more information, see [Requirements for log level fields](/reference/limits#requirements-for-log-level-fields).
2. In the Stream tab, click in the top right, and then select **Highlight severity**.
As a result, Axiom automatically searches for the words `warn` and `error` in the keys of the fields mentioned in Step 1, and then displays warnings in orange and errors in red.
---
# Explore traces
Source: https://axiom.co/docs/query-data/traces
Distributed tracing in Axiom allows you to observe how requests propagate through your distributed systems. This could involve a user request going through several microservices, and resources until the requested information is retrieved and returned. By tracing these requests, you’re able to understand the interactions between these microservices, pinpoint issues, understand latency, and trace the life of the request through your app’s architecture.
### Traces and spans [#traces-and-spans]
A trace is a representation of a single operation or transaction as it moves through a system. A trace is made up of multiple spans.
A span represents a logical unit of work in the system with a start and end time. For example, an HTTP request handling process might be a span. Each span includes metadata like unique identifiers (`trace_id` and `span_id`), start and end times, parent-child relationships with other spans, and optional events, logs, or other details to help describe the span’s operation.
### Trace schema overview [#trace-schema-overview]
| Field | Type | Description |
| ---------------- | -------- | -------------------------------------------------------- |
| `trace_id` | String | Unique identifier for a trace |
| `span_id` | String | Unique identifier for a span within a trace |
| `parent_span_id` | String | Identifier of the parent span |
| `name` | String | Name of the span for example, the operation |
| `kind` | String | Type of the span (for example, client, server, producer) |
| `duration` | Timespan | Duration of the span |
| `error` | Boolean | Whether this span contains an error |
| `status.code` | String | Status of the span (for example, null, OK, error) |
| `status.message` | String | Status message of the span |
| `attributes` | Object | Key-value pairs providing additional metadata |
| `events` | Array | Timestamped events associated with the span |
| `links` | Array | Links to related spans or external resources |
| `resource` | Object | Information about the source of the span |
This guide explains how you can use Axiom to analyze and interrogate your trace data from simple overviews to complex queries.
## Browse traces with the OpenTelemetry app [#browse-traces-with-the-opentelemetry-app]
The Axiom OpenTelemetry app automatically detects any OpenTelemetry trace data flowing into your datasets and publishes an OpenTelemetry Traces dashboard to help you browse your trace data.
The following fields are expected to display the OpenTelemetry Traces dashboard: `duration`, `kind`, `name`, `parent_span_id`, `service.name`, `span_id`, and `trace_id`.
### Navigate the app [#navigate-the-app]
* Use the **Filter Bar** at the top of the app to narrow the charts to a specific service or operation.
* Use the **Search Input** to find a trace ID in the selected time period.
* Use the **Slowest Operations** chart to identify performance issues across services and traces.
* Use the **Top Errors** list to quickly identify the worst-offending causes of errors.
* Use the **Results** table to get an overview and navigate between services, operations, and traces.
### View a trace [#view-a-trace]
Click a trace ID in the results table to show the waterfall view. This view allows you to see that span in the context of the entire trace from start to finish.
### Customize the app [#customize-the-app]
To customize the app, use the fork button to create an editable duplicate for you and your team.
## Query traces [#query-traces]
In Axiom, trace events are just like any other events inside datasets. This means they’re directly queryable in the UI. While this is can be a powerful experience, it’s important to note some important details to consider before querying:
* Directly aggregating upon the `duration` field produces aggregate values across every span in the dataset. This is usually not the desired outcome when you want to inspect a service’s performance or robustness.
* For request, rate, and duration aggregations, it’s best to only include the root span using `isnull(parent_span_id)`.
## Waterfall view of traces [#waterfall-view-of-traces]
To see how spans in a trace are related to each other, explore the trace in a waterfall view. In this view, each span in the trace is correlated with its parent and child spans.
### Traces in OpenTelemetry Traces dashboard [#traces-in-opentelemetry-traces-dashboard]
To explore spans within a trace using the OpenTelemetry Traces app, follow these steps:
1. Click the `Dashboards` tab.
2. Click `OpenTelemetry Traces`.
3. In the `Slowest Operations` chart, click the service that contains the trace.
4. In the list of trace IDs, click the trace you want to explore.
5. Explore how spans within the trace are related to each other in the waterfall view. To reveal additional options such as collapsing and expanding child spans, right-click a span.
To try out this example, go to the Axiom Playground.
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/dashboards/otel.traces.otel-demo-traces)
### Traces in Query tab [#traces-in-query-tab]
To access the waterfall view from the Query tab, follow these steps:
1. Ensure the dataset you work with has trace data.
2. Click the Query tab.
3. Run a query that returns the `_time` and `trace_id` fields. For example, the following query returns the number of spans in each trace:
```kusto
['otel-demo-traces']
| summarize count() by trace_id
```
4. In the list of trace IDs, click the trace you want to explore. To reveal additional options such as copying the trace ID, right-click a trace.
5. Explore how spans within the trace are related to each other in the waterfall view. To reveal additional options such as collapsing and expanding child spans, right-click a span. Event names are displayed on the timeline for each span.
To try out this example, go to the Axiom Playground.
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%5Cn%7C%20summarize%20count\(\)%20by%20trace_id%22%7D)
### Customize waterfall view [#customize-waterfall-view]
To toggle the display of the span details on the right, click **Span details**.
To resize the width of the waterfall view and the span details panel, drag the border.
### Span duration histogram [#span-duration-histogram]
In the waterfall view of traces, Axiom warns you about slow and fast spans. These spans are outliers because they’re at least a standard deviation over or under the average duration of spans that have the same span name and service name. Hold the pointer over the **SLOW** or **FAST** label to see additional information about the span type such as average and maximum duration. In addition, Axiom displays a histogram about the durations of spans that have the same span name and service name as the span you selected. By default, the histogram shows a one-hour window around the selected span.
The span duration histogram can be useful in the following cases, among others:
* You look at a span and you’re not familiar with the typical behavior of the service that created it. You want to know if you look at something normal in terms of duration or an outlier. The histogram helps you determine if you look at an outlier and might drill down further.
* You’ve found an outlier. You want to investigate and look at other outliers. The histogram shows you what the baseline is and what’s not normal in terms of duration. You want to filter for the outliers and see what they have in common.
* You want to see if there was a recent change in the typical duration for the selected span type.
To narrow the time range of the histogram, click and select an area in the histogram.
## Example queries [#example-queries]
Below are a collection of queries that can help get you started with traces inside Axiom. Queries are all executable on the [Axiom Play sandbox](https://axiom.co/play).
Number of requests, average response
```kusto
['otel-demo-traces']
| where isnull(parent_span_id)
| summarize count(),
avg(duration),
percentiles_array(duration, 95, 99, 99.9)
by bin_auto(_time)
```
Top five slowest services by operation
```kusto
['otel-demo-traces']
| summarize count(), avg(duration) by name
| sort by avg_duration desc
| limit 5
```
Top five errors per service and operation
```kusto
['otel-demo-traces']
| summarize topk(['status.message'], 5) by ['service.name'], name
| limit 5
```
## Semantic conventions [#semantic-conventions]
[OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/) specify standard attribute names and values for different kinds of operations and data. For more information on Axiom’s support for OTel semantic conventions and what it means for your data, see [Semantic conventions](/reference/semantic-conventions).
## Span links [#span-links]
Span links allow you to associate one span with one or more other spans, establishing a relationship between them that indicates the operation of one span depends on the other. Span links can connect spans within the same trace or across different traces.
Span links are useful for representing asynchronous operations or batch-processing scenarios. For example, an initial operation triggers a subsequent operation, but the subsequent operation may start at some unknown later time or even in a different trace. By linking the spans, you can capture and preserve the relationship between these operations, even if they’re not directly connected in the same trace.
### How it works [#how-it-works]
Span links in Axiom are based on the [OpenTelemetry specification](https://opentelemetry.io/docs/concepts/signals/traces/#span-links). When instrumenting your code, you create span links using the OpenTelemetry API by passing the `SpanContext` (containing `trace_id` and `span_id`) of the span to which to link. Links are specified when starting a new span by providing them in the span configuration. The OpenTelemetry SDK includes the link information when exporting spans to Axiom. Links are recorded at span creation time so that sampling decisions can consider them.
### View span links [#view-span-links]
1. Run the following APL query to find traces with span links, for example:
```kusto
['dataset']
| where isnotempty(links)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27otel-demo-traces%27%5D%5Cn%7C%20where%20isnotempty%28links%29%22%7D)
2. Click on a trace in the results and select the `trace_id`.
3. In the trace details view, find the links section. This displays the `trace_id` and `span_id` associated with each linked span, as well as other attributes of the link.
4. Click **View span** to navigate to a linked span, either in the same trace or a different trace.
---
# Views
Source: https://axiom.co/docs/query-data/views
Views allow you to apply commonly used filters and transformations to your dataset. The result is a view that you can use like you use datasets:
* You can reference views in queries, dashboards, and monitors in the same way you reference datasets. For example, you can create queries that look for data in a view and you can use this query to [visualize data](/query-data/explore) in the Query tab, create a [dashboard element](/dashboard-elements/create), or set up a [monitor](/monitor-data/monitors).
* You can share and control access to views in the same way you do with datasets using role-based access control (RBAC). For example, views allow you to grant other users scoped access to your datasets. This means that instead of sharing the whole dataset with other users, you have the option to share only a filtered and transformed representation of the data. You define a view using a query that applies filters and transformations to your dataset, and then you only share the results of this query with another user.
Contrary to datasets:
* You can’t ingest directly to a view.
* You can’t trim or delete data from a view.
* You can’t create a view from another view.
The concept of a view in Axiom is similar to the concept of a virtual table in a database.
Views aren't currently supported for metrics datasets.
### Create view [#create-view]
1. Select a dataset.
2. In the top right of the **Views** section, click **+ Create view**.
3. Create the query using [Builder](/query-data/explore) or [Editor](/query-data/query-editor).
4. Below the query, check the preview of the events and the fields that your query returns.
5. Name the view and optionally add a description.
6. Click **Create view**.
### Display view [#display-view]
1. Select a dataset.
2. In the **Views** section on the right, click the view you want to display.
### Grant access to view [#grant-access-to-view]
All views are available to users with organization-level read access to views. You can allow users without these permissions to access an individual view in the same way you grant access to individual datasets:
1. Click **Settings > Roles**.
2. Click the role with which you want to share the view or create a new role.
3. In the **Individual datasets** section, click **Add dataset**.
4. Start typing the name of the view. In the list, find the view that you want to share, and then click **Add**.
It’s enough to grant access to the view. You don’t need to grant access to the underlying dataset.
5. Click **Save**.
---
# Virtual fields
Source: https://axiom.co/docs/query-data/virtual-fields
Virtual fields allow you to derive new values from your data in real time. They eliminate the need for up-front planning of how to structure or transform your data. Instead, send your data as-is and then use virtual fields to manipulate your data in real-time during queries. The feature is also known as derived fields, but Axiom’s virtual fields have some unique properties that make them much more powerful.
Virtual fields are query-time representations only. They don't create new data in storage.
Virtual fields aren't currently supported for metrics datasets.
## Creating a virtual field [#creating-a-virtual-field]
To create a virtual field, follow these steps:
1. Go to the Datasets tab.
2. Select the dataset where you want to create the virtual field.
3. Click **Virtual fields** in the top right. You see a list of all the virtual fields for the dataset.
4. Click **Add virtual field**.
5. Fill in the following fields:
* **Name** and **Description** help your team understand what the virtual field is about.
* **Expression** is the formula applied to every event to calculate the virtual field. The expression produces a result such as a `boolean`, `string`, `number`, or `object`.
The **Preview** section displays the result of applying the expression to some of your data. Use this section to verify the expression and the resulting values of the virtual field.
The power of virtual fields is in letting you manipulate data on read instead of on write, allowing you to adjust and update virtual fields over time as well as easily add new ones without worrying that the data has already been indexed.
## Usage [#usage]
### Visualizations [#visualizations]
Virtual fields are available as parameters to visualizations but, as the type of a virtual field can be any of the supported types, it’s important to make sure that you use a virtual field that produces the correct type of argument.
### Filters [#filters]
Virtual fields are available in the filter menu and all filter options are presented. It’s important to ensure that you are using a supported filter operation for the type of result your virtual field produces.
## Group By [#group-by]
Virtual fields can be used for segmentation in the same way as any standard field.
## Virtual fields vs ingest-time parsing [#virtual-fields-vs-ingest-time-parsing]
When deciding how to structure your data, consider the trade-offs between using virtual fields (query-time parsing) and parsing fields at ingest time.
### When to use virtual fields [#when-to-use-virtual-fields]
Virtual fields are ideal when:
* You need flexibility to experiment with different data transformations.
* Your query patterns are still evolving and you're not sure which fields you'll need.
* You want to derive new fields without re-ingesting historical data.
* The fields are used infrequently or for ad-hoc analysis.
### When to parse at ingest time [#when-to-parse-at-ingest-time]
For high-performance use cases, parsing fields at ingest time is often the better choice:
* **Query performance**: Virtual fields re-parse data on every query execution. For frequently queried fields, especially during incident response when teams run many ad-hoc queries, this adds latency. Parsed fields at ingest time reduce your storage and query hours usage. For more information, see [Virtual fields for simple transformations](/reference/performance#virtual-fields-for-simple-transformations).
* **Compression benefits**: When you parse fields from a JSON body to top-level fields, Axiom uses specialized data-aware compression techniques based on the detected data types. This results in more efficient storage compared to keeping data as unparsed strings. For more information, see [Overusing runtime JSON parsing](/reference/performance#overusing-runtime-json-parsing-parse_json).
### Storage considerations [#storage-considerations]
Parsing fields from a body to top-level attributes doesn't double your storage usage. Axiom's compression is optimized for typed, structured data at the top level, and stores parsed fields efficiently. However, there is some data redundancy if you keep the original body field alongside the parsed fields. To minimize redundancy, remove the parsed fields from the body after extraction during your ingest pipeline.
For a balance between flexibility and performance, parse your most frequently queried fields at ingest time while using virtual fields for less common or experimental transformations.
## Reference [#reference]
Virtual fields are APL expressions and share all the same functions and syntax as APL expressions. For more information, see [Introduction to APL](/apl/introduction).
The list of APL scalar functions:
* [String functions](/apl/scalar-functions/string-functions)
* [Math functions](/apl/scalar-functions/mathematical-functions)
* [Array functions](/apl/scalar-functions/array-functions)
* [Conversion functions](/apl/scalar-functions/conversion-functions)
* [Hash functions](/apl/scalar-functions/hash-functions)
* [DateTime/Timespan functions](/apl/scalar-functions/datetime-functions)
* [Rounding functions](/apl/scalar-functions/rounding-functions)
* [Conditional functions](/apl/scalar-functions/conditional-function)
* [IP functions](/apl/scalar-functions/ip-functions)
Virtual fields may reference other virtual fields. The order of the fields is important. Ensure that the referenced field is specified before the field that references it.
{/*
### Literals
| Functions | Description |
| ------------- | --------------------------------------- |
| `strings` | single and double quotes are supported. |
| `numbers` | `101`, `101.1` |
| `booleans` | `true` and `false` |
| `arrays` | `["one", "two", "three"]` |
| `maps` | `{ region: "us-east-1" }` |
| `nil` - | `nil` |
### Arithmetic operators
| Operator | Description |
| ------------ | --------------- |
| `+` | addition |
| `-` | subtraction |
| `*` | multiplication |
| `/` | division |
| `%` | modulus |
| `**` | pow |
### Comparison operators
| Operator | Description |
| ------------ | ------------------------ |
| `==` | equal |
| `!=` | not equal |
| `<` | less than |
| `>` | greater than |
| `<=` | less than or equal to |
| `>=` | greater than or equal to |
### Logical operators
| Operator |
| -------------------------------------- |
| `and` or `&&` |
| `or` or ` |
| `not` or `!` |
| `success ? 'yes' : 'no'` - ternary |
### String operators
| Operator | Description |
| ------------ | --------------- |
| `+` | concatenation |
| `matches` | regular expression match |
| `contains` | string contains |
| `startsWith` | has prefix |
| `endsWith` | has suffix |
To test the negative case of not matching, wrap the operator in a `not()` operator:
`not ("us-east-1" contains "us")`
Use parenthesis because the operator `not` has precedence over the operator `contains`.
### Numeric operators
In addition to the [arithmetic operators](#arithmetic-operators):
- `..` - numeric range
`age in 18..45`
The range is inclusive: `1..3 == [1, 2, 3]`
### Membership operators
| Operator | Description |
| ------------ | -------------------- |
| `in` | contains |
| `not in` | doesn’t contain |
Examples:
`{Arrays: metadata.region in ["us-east-1", "us-east-2"]}`
`{Maps: 'region' in { region: 'us-east-1 } // true}`
### Built-ins
| Operator | Description |
| ------------ | ---------------------------------------------------------------- |
| `len` | length of an array, map, or string |
| `all` | return true if all element satisfies the predicate |
| `none` | return true if all element doesn’t satisfies the predicate |
| `any` | return true if any element satisfies the predicate |
| `one` | return true if exactly ONE element satisfies the predicate |
| `filter` | filter array by the predicate |
| `map` | map all items with the closure |
| `count` | returns number of elements what satisfies the predicate |
{'all(comments, {.Size < 280})'}
{'one(repos, {.private})'}
### Closures
- `{...}` - closure
Closures allowed only with builtin functions. To access the current item, used the `#` symbol.
{'`map(0..9, {# / 2})`'}
If the item of array is struct, it’s possible to access fields of struct with omitted `#` symbol (`#.Value` becomes `.Value`).
{'filter(comments, {len(.body) > 280})'}
### Slices
- `myArray[:]` - slice
Slices can work with arrays or strings
The variable `myArray` is `[1, 2, 3, 4, 5]`
`myArray[1:5] == [2, 3, 4] myArray[3:] == [4, 5] myArray[:4] == [1, 2, 3] myArray[:] == myArray`
*/}
---
# Visualize
Source: https://axiom.co/docs/query-data/visualizations
Visualizations are powerful aggregations of your data to produce insights that are easy to understand and monitor. With visualizations, you can create and obtain data stats, group fields, and observe methods in running deployments.
This page introduces you to the visualizations supported by Axiom and some tips on how best to use them.
The visualizations explained on this page are only available for APL queries.
To visualize metrics data, see [MPL language features](/mpl/introduction).
## `count` [#count]
The `count` visualization counts all matching events and produces a time series chart.
#### Arguments [#arguments]
This visualization doesn’t take an argument.
#### Group-by behaviour [#group-by-behaviour]
The visualization produces a separate result for each group plotted on a time series chart.
## `distinct` [#distinct]
The `distinct` visualization counts each distinct occurrence of the distinct field inside the dataset and produce a time series chart.
#### Arguments [#arguments-1]
`field: any` is the field to aggregate.
#### Group-By Behaviour [#group-by-behaviour-1]
The visualization produces a separate result for each group plotted on a time series chart.
## `avg` [#avg]
The `avg` visualization averages the values of the field inside the dataset and produces a time series chart.
#### Arguments [#arguments-2]
`field: number` is the number field to average.
#### Group-by behaviour [#group-by-behaviour-2]
The visualization produces a separate result for each group plotted on a time series chart.
## `max` [#max]
The `max` visualization finds the maximum value of the field inside the dataset and produces a time series chart.
#### Arguments [#arguments-3]
`field: number` is the number field where Axiom finds the maximum value.
#### Group-by behaviour [#group-by-behaviour-3]
The visualization produces a separate result for each group plotted on a time series chart.
## `min` [#min]
The `min` visualization finds the minimum value of the field inside the dataset and produces a time series chart.
#### Arguments [#arguments-4]
`field: number` is the number field where Axiom finds the minimum value.
#### Group-by behaviour [#group-by-behaviour-4]
The visualization produces a separate result for each group plotted on a time series chart.
## `sum` [#sum]
The `sum` visualization adds all the values of the field inside the dataset and produces a time series chart.
#### Arguments [#arguments-5]
`field: number` is the number field where Axiom calculates the sum.
#### Group-by behaviour [#group-by-behaviour-5]
The visualization produces a separate result for each group plotted on a time series chart.
## `percentiles` [#percentiles]
The `percentiles` visualization calculates the requested percentiles of the field in the dataset and produces a time series chart.
#### Arguments [#arguments-6]
* `field: number` is the number field where Axiom calculates the percentiles.
* `percentiles: number [, ...]` is a list of percentiles , each a float between 0 and 100. For example, `percentiles(request_size, 95, 99, 99.9)`.
#### Group-by behaviour [#group-by-behaviour-6]
The visualization produces a separate result for each group plotted on a horizontal bar chart, allowing for visual comparison across the groups.
## `histogram` [#histogram]
The `histogram` visualization buckets the field into a distribution of N buckets, returning a time series heatmap chart.
#### Arguments [#arguments-7]
* `field: number` is the number field where Axiom calculates the distribution.
* `nBuckets` is the number of buckets to return. For example, `histogram(request_size, 15)`.
#### Group-by behaviour [#group-by-behaviour-7]
The visualization produces a separate result for each group plotted on a time series histogram. Hovering over a group in the totals table shows only the results for that group in the histogram.
## `topk` [#topk]
The `topk` visualization calculates the top values for a field in a dataset.
#### Arguments [#arguments-8]
* `field: number` is the number field where Axiom calculates the top values.
* `nResults` is the number of top values to return. For example, `topk(method, 10)`.
#### Group-by behaviour [#group-by-behaviour-8]
The visualization produces a separate result for each group plotted on a time series chart.
## `variance` [#variance]
The `variance` visualization calculates the variance of the field in the dataset and produces a time series chart.
The `variance` aggregation returns the sample variance of the fields of the dataset.
#### Arguments [#arguments-9]
`field: number` is the number field where Axiom calculates the variance.
#### Group-by behaviour [#group-by-behaviour-9]
The visualization produces a separate result for each group plotted on a time series chart.
## `stddev` [#stddev]
The `stddev` visualization calculates the standard deviation of the field in the dataset and produces a time series chart.
The `stddev` aggregation returns the sample standard deviation of the fields of the dataset.
#### Arguments [#arguments-10]
`field: number` is the number field where Axiom calculates the standard deviation.
#### Group-by behaviour [#group-by-behaviour-10]
The visualization produces a separate result for each group plotted on a time series chart.
---
# Track activity in Axiom
Source: https://axiom.co/docs/reference/audit-log
The audit log allows you to track who did what and when within your Axiom organization.
Tracking activity in your Axiom organization with the audit log is useful for legal compliance reasons. For example, you can investigate the following:
* Track who has accessed the Axiom platform.
* Track organization access over time.
* Track data access over time.
The audit log also make it easier to manage your Axiom organization. They allow you to do the following, among others:
* Track changes made by your team to your observability posture.
* Track monitoring performance and identify which monitors generate the most query load.
* Monitor query costs and optimize expensive queries before they impact your budget.
* Trace queries back to their source (monitors or direct queries) for debugging.
The audit log is available to all organizations. By default, you can query the audit log for the previous three days. You can purchase full access to the audit log as an add-on on the Axiom Cloud plan. For more information, see [Manage add-ons](/reference/usage-billing#manage-add-ons).
## Explore audit log [#explore-audit-log]
1. Go to the Query tab, and then click **APL**.
2. Query the `axiom-audit` dataset. For example, run the query `['axiom-audit']` to display the raw audit log data in a table.
3. Optional: Customize your query to filter or summarize the audit log. For more information, see [Query data](/query-data/explore).
4. Click **Run**.
The `action` field specifies the type of activity that happened in your Axiom organization.
## Export audit log [#export-audit-log]
1. Run the query to [display the audit log](#explore-audit-log).
2. Click **More > Download as JSON**.
## Give access to audit log [#give-access-to-audit-log]
The audit log is only accessible to users with the Owner role. To allow other users to access the audit log:
1. [Create a view](/query-data/views) that defines the parts of the audit log that you want the user to access.
2. [Give the user access](/reference/settings) to the view.
## Use cases and examples [#use-cases-and-examples]
The audit log captures rich context about queries run in your organization:
* **Query representation**: Privacy-safe representations of queries help you understand query patterns without exposing sensitive data.
* **Query source**: Track whether queries originated from monitors or direct queries.
* **Query cost**: Monitor resource consumption in query units for cost optimization.
* **Storage bytes scanned**: Understand data volumes processed by each query.
The examples below illustrate how the audit log can help you optimize performance, manage costs, and debug issues by tracing queries back to their origin.
### Monitor high-cost queries [#monitor-high-cost-queries]
Identify queries that consume significant resources:
```kusto
['axiom-audit']
| where action == 'runAPLQueryCost'
| where ['properties.query_cost_gbms'] > 1000
```
This query lists queries costing more than 1000 query units and helps you spot expensive queries and optimize them before they impact your budget.
[Create a threshold monitor](/monitor-data/threshold-monitors) using this query to receive alerts when expensive queries run. Adjust the `query_cost_gbms` threshold based on your organization’s usage patterns.
### Track monitor query load [#track-monitor-query-load]
Understanding which monitors generate the most query activity helps you optimize performance:
```kusto
['axiom-audit']
| where action == 'runAPLQueryCost'
| where source == 'monitor'
| summarize
total_queries = count(),
total_cost = sum(['properties.query_cost_gbms']),
avg_cost = avg(['properties.query_cost_gbms'])
by ['resource.id']
| sort by total_cost desc
```
Use this to identify monitors that might benefit from query optimization or frequency adjustments.
### Analyze dataset usage [#analyze-dataset-usage]
Find out which datasets are used the most:
```kusto
['axiom-audit']
| where action == 'runAPLQuery'
| where isnotnull(['properties.datasets'])
| summarize
query_count = count()
by ['properties.datasets'], bin(_time, 1d)
| sort by query_count desc
```
This query helps you understand how your team interacts with Axiom and identifies datasets that may need optimization.
### Track ingest by dataset [#track-ingest-by-dataset]
Track how much data each dataset ingests over time:
```kusto
['axiom-audit']
| where action == "usageCalculated"
| extend ingest_gb = tolong(['properties.hourlyIngestBytes']) / pow(1024, 3)
| summarize IngestGB = sum(ingest_gb) by bin_auto(_time), tostring(['properties.dataset'])
| sort by IngestGB desc
```
### Determine total data ingest [#determine-total-data-ingest]
Determine total data ingest across all datasets over time:
```kusto
['axiom-audit']
| where action == "usageCalculated"
| extend ingest_gb = tolong(['properties.hourlyIngestBytes']) / pow(1024, 3)
| summarize TotalIngestGB = sum(ingest_gb) by bin_auto(_time)
```
[Create a dashboard](/dashboards/overview) with these queries to continuously monitor your ingestion patterns.
[Create a threshold monitor](/monitor-data/threshold-monitors) to alert you when ingestion exceeds a threshold.
### Track query sources [#track-query-sources]
See the distribution of queries across different sources:
```kusto
['axiom-audit']
| where action == 'runAPLQuery'
| summarize query_count = count() by source
| sort by query_count desc
```
This helps you understand how your team interacts with Axiom and where queries originate.
## List of trackable actions [#list-of-trackable-actions]
The `action` field specifies the type of activity that happened in your Axiom organization. The actions that Audit logs allow you to track are the following:
* aplDelete
* createAnnotation
* createAPIToken
* createDashboard
* createDataset
* createEndpoint
* createFlowConfiguration
* createFlowDestination
* createFlowReplay
* createFlowStream
* createGroup
* createMapField
* createMonitor
* createNotifier
* createOrg
* createOrgStorage
* createPersonalToken
* createRole
* createUser
* createView
* createVirtualField
* deleteAnnotation
* deleteAPIToken
* deleteDashboard
* deleteDataset
* deleteEndpoint
* deleteFlowConfiguration
* deleteFlowDestination
* deleteGroup
* deleteMapField
* deleteMonitor
* deleteNotifier
* deleteOrg
* deletePersonalToken
* deleteRepo
* deleteRole
* deleteSession
* deleteShareLink
* deleteView
* downgradeOrg
* downgradePlan
* fieldLimitApproached
* fieldLimitExceeded
* getDashboard
* getDatasetFields
* getField
* getSharedRepos
* logout
* logoutEverywhere
* messageSent
* notifierFailed
* notifierTriggered
* notifyCustomerIOIssues
* postRepos
* regenerateAPIToken
* regeneratePersonalToken
* removeRBAC
* removeUserFromOrg
* resolveMonitor
* resolveMonitorAll
* resumeFlowReplay
* resumeFlowStream
* rotateSharedAccessKeys
* runAPLQuery
* sendOrgDeletedEmails
* sendOrgMonthlyIngestedExceededEmail
* sendOrgMonthlyIngestedNearLimitEmail
* sendUserDeletedEmail
* sendWelcomeEmail
* setEnableAI
* shareRepo
* stopFlowReplay
* stopFlowStream
* streamDataset
* triggerNotifier
* triggerNotifierWithID
* trimDataset
* unShareRepo
* updateDashboard
* updateDataset
* updateDatasetSettings
* updateEndpoint
* updateField
* updateFlowConfiguration
* updateFlowDestination
* updateGroup
* updateMapFields
* updateMonitor
* updateNotifier
* updateOrg
* updatePersonalToken
* updateRepo
* updateRole
* updateUser
* updateUserSettings
* updateView
* updateVirtualField
* upgradeOrg
* upgradePlan
* usageCalculated
* useShareLink
* vacuumDataset
---
# Axiom CLI
Source: https://axiom.co/docs/reference/cli
Axiom’s command line interface (CLI) is an Axiom tool that lets you test, manage, and build your Axiom organizations by typing commands on the command-line.
You can use the command line to ingest data, manage authentication state, and configure multiple organizations.
## Installation [#installation]
### Install using go install [#install-using-go-install]
To install Axiom CLI, make sure you have [Go](https://golang.org/dl/) installed, then run this command from any directory in your terminal.
```bash
go install github.com/axiomhq/cli/cmd/axiom@latest
```
### Install using Homebrew [#install-using-homebrew]
You can also install the CLI using [Homebrew](https://brew.sh/)
```bash
brew install --cask axiomhq/tap/axiom
```
This installs Axiom command globally so you can run `axiom` commands from any directory.
To update:
```bash
brew upgrade --cask axiom
```
### Install from source [#install-from-source]
```bash
git clone https://github.com/axiomhq/cli.git
cd cli
make install # Build and install binary into $GOPATH
```
### Run the Docker image [#run-the-docker-image]
Docker images are available on [DockerHub.](https://hub.docker.com/r/axiomhq/cli)
```bash
docker pull axiomhq/cli
docker run axiomhq/cli
```
You can check the version and find out basic commands about Axiom CLI by running the following command:
```bash
axiom
```
## Authentication [#authentication]
The recommended way to authenticate Axiom CLI is with a personal access token. A personal access token starts with `xapt-` and lets you do everything you can do in the Axiom Console, including querying and streaming data.
1. In the Axiom Console, go to **Settings → Profile → Personal Tokens** and create a token. Copy the token value (it starts with `xapt-`).
2. Log in with the token. Use one of the following methods.
Interactive login (you’re prompted for your token and organization ID):
```bash
axiom auth login --auto-login=false
```
Non-interactive login (useful for scripts and CI). When you pipe the token, both `--alias` (a local name for the deployment) and `--org-id` are required:
```bash
echo "xapt-your-token" | axiom auth login --alias "axiom" --org-id "your-org-id" -f
```
Alternatively, skip `axiom auth login` entirely and set environment variables. `AXIOM_ORG_ID` is optional with a personal access token—the token resolves to your default organization, so set it only when your account belongs to more than one organization and you want to target a specific one:
```bash
export AXIOM_TOKEN="xapt-your-token"
export AXIOM_URL="https://api.axiom.co"
export AXIOM_ORG_ID="your-org-id" # optional; only needed to target a specific organization
```
To confirm you’re authenticated, run `axiom dataset list`.
Running `axiom auth login` without `--auto-login=false` starts a browser-based login flow. This flow is currently affected by a known issue: after you authorize the application, the login can fail with an OAuth error (`oauth2: "invalid_request"`). Until this is resolved, use the personal access token method described above.
For more information about tokens, see [Tokens](/reference/tokens).
## Managing multiple organizations [#managing-multiple-organizations]
While most users will only need to manage a single Axiom deployment, Axiom CLI provides the capability to switch between multiple organizations for those who require it. You can easily switch between organizations using straightforward CLI commands. For example, `axiom auth switch-org` lets you change your active organization, or you can set the `AXIOM_ORG_ID` environment variable for the same purpose.
Every setting in Axiom CLI can be overwritten via environment variables configured in the `~/.axiom.toml` file. Specifically, `AXIOM_URL`, `AXIOM_TOKEN`, and `AXIOM_ORG_ID` are important for configuring your environment. Set `AXIOM_URL` to your Axiom domain. For more information, see [Edge deployments](/reference/edge-deployments). You can switch between environments using the `axiom auth select` command.
To view available environment variables, run `axiom help environment` for an up to date list of env vars:
```
AXIOM_DEPLOYMENT: The deployment to use. Overwrites the choice loaded
from the configuration file.
AXIOM_ORG_ID: The organization ID of the organization the access
token is valid for.
AXIOM_PAGER, PAGER (in order of precedence): A terminal paging
program to send standard output to, for example, "less".
AXIOM_TOKEN: Token The access token to use. Overwrites the choice
loaded from the configuration file.
AXIOM_URL: The deployment url to use. Overwrites the choice loaded
from the configuration file.
VISUAL, EDITOR (in order of precedence): The editor to use for
authoring text.
NO_COLOR: Set to any value to avoid printing ANSI escape sequences
for color output.
CLICOLOR: Set to "0" to disable printing ANSI colors in output.
CLICOLOR_FORCE: Set to a value other than "0" to keep ANSI colors in
output even when the output is piped.
```
## One-Click Login [#one-click-login]
The One-Click Login is an easier way to authenticate Axiom-CLI and log in to your Axiom deployments and account resources directly on your terminal using the Axiom CLI.
## Tokens [#tokens]
You can generate an ingest and personal token manually in your Axiom user settings.
See [Tokens](/reference/tokens) to know more about managing access and authorization.
## Configuration and Deployment [#configuration-and-deployment]
Axiom CLI lets you ingest, authenticate, and stream data.
For more information about Configuration, managing authentication status, ingesting, streaming, and more,
visit the [Axiom CLI](https://github.com/axiomhq/cli) repository on GitHub.
Axiom CLI supports the ingestion of different formats of data **( JSON, NDJSON, and CSV)**
## Querying [#querying]
Get deeper insights into your data using [Axiom Processing Language](/apl/introduction)
## Ingestion [#ingestion]
Import, transfer, load, and process data for later use or storage using the Axiom CLI. With [Axiom CLI](https://github.com/axiomhq/cli) you can Ingest the contents of a **JSON, NDJSON, CSV** logfile into a dataset.
**To view a list of all the available commands run `axiom` on your terminal:**
```bash
➜ ~ axiom
The power of Axiom on the command-line.
USAGE
axiom [flags]
CORE COMMANDS
ingest: Ingest structured data
query: Query data using APL
stream: Livestream data
MANAGEMENT COMMANDS
auth: Manage authentication state
config: Manage configuration
dataset: Manage datasets
ADDITIONAL COMMANDS
completion: Generate shell completion scripts
help: Help about any command
version: Print version
web: Open Axiom in the browser
FLAGS
-O, --auth-org-id string Organization ID to use
-T, --auth-token string Token to use
-C, --config string Path to configuration file to use
-D, --deployment string Deployment to use
-h, --help Show help for command
--no-spinner Disable the activity indicator
-v, --version Show axiom version
EXAMPLES
$ axiom auth login
$ axiom version
$ cat http-logs.json | axiom ingest http-logs
AUTHENTICATION
See 'axiom help credentials' for help and guidance on authentication.
ENVIRONMENT VARIABLES
See 'axiom help environment' for the list of supported environment variables.
LEARN MORE
Use 'axiom --help' for more information about a command.
Read the manual at https://axiom.co/reference/cli
```
## Command Reference [#command-reference]
Below are the commonly used commands on Axiom CLI
**Core Commands**
| Commands | Description |
| ---------------- | -------------------- |
| **axiom ingest** | Ingest data |
| **axiom query** | Query data using APL |
| **axiom stream** | Live stream data |
**Management Commands**
| Commands | Description |
| --------------------------- | ----------------------------------------- |
| **axiom auth login** | Login to Axiom |
| **axiom auth logout** | Logout of Axiom |
| **axiom auth select** | Select an Axiom environment configuration |
| **axiom auth status** | View authentication status |
| **axiom auth switch-org** | Switch the organization |
| **axiom auth update-token** | Update the token used to authenticate |
| **axiom config edit** | Edit the configuration file |
| **axiom config get** | Get a configuration value |
| **axiom config set** | Set a configuration value |
| **axiom config export** | Export the configuration values |
| **axiom dataset create** | Create a dataset |
| **axiom dataset delete** | Delete a dataset |
| **axiom dataset list** | List all datasets |
| **axiom dataset trim** | Trim a dataset to a given size |
| **axiom dataset update** | Update a dataset |
**Additional Commands**
| Commands | Description |
| ------------------------------- | ----------------------------------------------- |
| **axiom completion bash** | Generate shell completion script for bash |
| **axiom completion fish** | Generate shell completion script for fish |
| **axiom completion powershell** | Generate shell completion script for powershell |
| **axiom completion zsh** | Generate shell completion script for zsh |
| **axiom help** | Help about any command |
| **axiom version** | Print version |
| **axiom web** | Open Axiom in the browser |
## Get help [#get-help]
To get usage tips and learn more about available commands from within Axiom CLI, run the following:
```bash
axiom help
```
For more information about a specific command, run `help` with the name of the command.
```bash
axiom help auth
```
This also works for sub-commands.
```bash
axiom help auth status
```
**if you have questions, or any opinions you can [start an issue](https://github.com/axiomhq/cli/issues) on Axiom CLI’s open source repository.**
**You can also visit the [Discord community](https://axiom.co/discord) to start or join a discussion. Axiom would love to hear from you.**
---
# Manage datasets
Source: https://axiom.co/docs/reference/datasets
This reference article explains how to manage datasets in Axiom, including creating new datasets, importing data, and deleting datasets.
## What datasets are [#what-datasets-are]
Axiom’s datastore is tuned for the efficient collection, storage, and analysis of timestamped event data. An individual piece of data is an event, and a dataset is a collection of related events. Datasets contain incoming event data.
## Best practices for organizing datasets [#best-practices-for-organizing-datasets]
Use datasets to organize your data ready for querying based on the event schema. Common ways to separate include environment, signal type, and service.
### Separate by environment [#separate-by-environment]
If you work with data sourced from different environments, separate them into different datasets. For example, use one dataset for events from production and another dataset for events from your development environment.
You might be tempted to use a single `environment` attribute instead, but this risks causing confusion when results show up side-by-side in query results. Although some organizations choose to collect events from all environments in one dataset, they’ll often rely on applying an `environment` filter to all queries, which becomes a chore and is error-prone for newcomers.
### Separate by signal type [#separate-by-signal-type]
If you work with distributed applications, consider splitting your data into different datasets. For example:
* A dataset with traces for all services
* A dataset with app logs for all services
* A dataset with frontend web vitals
* A dataset with infrastructure logs
* A dataset with security logs
* A dataset with CI logs
If you look for a specific event in a distributed system, you are likely to know its signal type but not the related service. By splitting data into different datasets using the approach above, you can find data easily.
### Separate by service [#separate-by-service]
Another common practice is to separate datasets by service. This approach allows for easier access control management.
For example, you might separate engineering services with datasets like `kubernetes`, `billing`, or `vpn`, or include events from your wider company collectors like `product-analytics`, `security-logs`, or `marketing-attribution`.
This separation enables teams to focus on their relevant data and simplifies querying within a specific domain. It also works well with Axiom’s role-based access control feature as you can restrict access to sensitive datasets to those who need it.
While separating by service is beneficial, avoid over-segmentation. Creating a dataset for every microservice or function can lead to unnecessary complexity and management overhead. Instead, group related services or functions into logical datasets that align with your organizational structure or major system components.
When you work with OpenTelemetry trace data, keep all spans of a given trace in the same dataset. To investigate spans for different services, don’t send them to different datasets. Instead, keep the spans in the same dataset and filter on the `service.name` field. For more information, see [Send OpenTelemetry data to Axiom](/send-data/opentelemetry).
### Avoid the “kitchen sink” [#avoid-the-kitchen-sink]
While it might seem convenient to send all events to a single dataset, this “kitchen sink” approach is generally not advisable for several reasons:
* Field count explosion: As you add more event types to a single dataset, the number of fields grows rapidly. This can make it harder to understand the structure of your data and impacts query performance.
* Query inefficiency: With a large, mixed dataset, queries often require multiple filters to isolate the relevant data. This is tedious, but without those filters, queries take longer to execute since they scan through more irrelevant data.
* Schema conflicts: Different event types may have conflicting field names or data types, leading to unnecessary type coercion at query time.
* Access management: With all data in one dataset, it becomes challenging to provide granular access controls. You might end up giving users access to more data than they need.
Don’t create multiple Axiom organizations to separate your data. For example, don’t use a different Axiom organization for each deployment. If you’re on the Axiom Cloud (Personal) plan, this might go against [Axiom’s fair use policy](https://axiom.co/terms). Instead, separate data by creating a different dataset for each deployment within the same Axiom organization.
## Limits on ingested data [#limits-on-ingested-data]
For more information on limits and requirements imposed by Axiom, see [Limits](/reference/limits).
## Create dataset [#create-dataset]
To create a dataset using Console, follow these steps:
1. Click **Settings > Datasets and views**.
2. Click **New dataset**.
3. Name the dataset and add an optional description.
4. In **Kind**, select one of the following:
* Select **Events** if you plan to use the dataset for logs or traces data.
* Select **Metrics** if you plan to send OTel metrics data to the dataset.
5. In **Data retention**, select for how long to store your data in this dataset. For more information, see [Specify data retention period](#specify-data-retention-period).
6. In **Edge deployment**, select the edge deployment you want to use for this dataset. You can select from the list of edge deployments you have added to the organization. For more information, see [Edge deployments](/reference/edge-deployments) and [Add edge deployments](/reference/usage-billing#manage-add-ons).
7. Click **Save dataset**.
To create a dataset using the Axiom API, send a POST request to the [datasets endpoint](/restapi/endpoints/createDataset).
Dataset names are 1 to 128 characters in length. They only contain ASCII alphanumeric characters and the hyphen (`-`) character.
## Import data [#import-data]
You can import data to your dataset in one of the following formats:
* Newline delimited JSON (NDJSON)
* Arrays of JSON objects
* CSV
To import data to a dataset, follow these steps:
1. Click **Settings > Datasets and views**.
2. In the list, find the dataset where you want to import data, and then click **Import** on the right.
3. Optional: Specify the timestamp field. This is only necessary if your data contains a timestamp field and it’s different from `_time`.
4. Upload the file, and then click **Import**.
## Trim dataset [#trim-dataset]
Trimming permanently deletes data stored in blocks that are older than a date you specify. Note that this action doesn’t delete data that’s older than the specified date but shares a block with newer data.
This can be useful if your dataset contains too many fields or takes up too much storage space, and you want to reduce its size to ensure you stay within the [allowed limits](/reference/limits#pricing-based-limits).
Trimming a dataset deletes all data blocks before the specified date.
Trimming is an asynchronous operation. When you submit a trim request, Axiom queues the deletion and processes it in the background. The trimmed data may take several hours to be fully removed from your dataset. During this time:
* The trim operation appears as successful in the [audit log](/reference/audit-log).
* Query results may still include data that's queued for deletion.
* The storage usage displayed in your settings may not immediately reflect the reduction.
To trim a dataset, follow these steps:
1. Click **Settings > Datasets and views**.
2. In the list, find the dataset that you want to trim, and then click **Trim dataset** on the right.
3. Specify the date before which you want to delete blocks of data.
4. Enter the name of the dataset, and then click **Trim**.
## Vacuum fields [#vacuum-fields]
The data schema of your dataset is defined on read. Axiom continuously creates and updates the data structures during the data ingestion process. At the same time, Axiom only retains data for the [retention period you specify](#specify-data-retention-period). This means that the data schema can contain fields that you ingested into the dataset in the past, but these fields are no longer present in the data currently associated with the dataset. This can be an issue if the number of fields in the dataset exceeds the [allowed limits](/reference/limits#pricing-based-limits).
In this case, vacuuming fields in a dataset can help you reduce the number of fields associated with a dataset and stay within the allowed limits. Vacuuming fields resets the number of fields associated with a dataset to the fields that occur in events within your retention period. Technically, it wipes the data schema and rebuilds it from the data you currently have in the dataset, which is partly defined by the retention period. For example, you have ingested 500 fields over the last year and 50 fields in the last 95 days, which is your retention period. In this case, before vacuuming, your data schema contains 500 fields. After vacuuming, the dataset only contains 50 fields.
Vacuuming fields doesn’t delete any events from your dataset. To delete events, [trim the dataset](#trim-dataset). You can use trimming and vacuuming in combination. For example, if you accidentally ingested events with fields you didn’t want to send to Axiom, and these events are within your retention period, vacuuming alone doesn’t solve your problem. In this case, first trim the dataset to delete the events with the unintended fields, and then vacuum the fields to rebuild the data schema.
You can vacuum the fields of a dataset whose schema is locked. In this case, Axiom vacuums the fields and then regenerates the locked schema so that it matches the vacuumed fields. For more information, see [Lock dataset schema](#lock-dataset-schema).
You can only vacuum fields once per day for each dataset.
To vacuum fields, follow these steps:
1. Click **Settings > Datasets and views**.
2. In the list, find the dataset where you want to vacuum fields, and then click **Vacuum fields** on the right.
3. Select the checkbox, and then click **Vacuum**.
## Lock dataset schema [#lock-dataset-schema]
By default, the data schema of a dataset grows together with your data. When you ingest an event with a field that isn’t yet part of the data schema, Axiom adds the new field to the schema. Similarly, when you ingest an event where the value of an existing field has a new type, Axiom extends the field’s type to support both the existing and the new type.
Locking the schema of a dataset freezes its set of fields and the type of each field. While the schema is locked, Axiom discards the following during the data ingestion process:
* Fields that aren’t part of the locked schema.
* Field values whose type doesn’t match the type of the field in the locked schema and can’t be promoted to that type. For example, if the locked schema defines a field as a number and you ingest an event where the field contains a string, Axiom drops the field’s value from that event. Axiom keeps values that it can promote to the locked type. For example, if the locked type of a field is float, Axiom accepts integer values in that field. Null values never violate a locked schema.
Axiom still ingests the events themselves and only drops the fields that don’t match the locked schema. When Axiom drops a field, the response to the ingest request contains a warning message that identifies the field and the reason. For example:
```json
"messages": [
{
"priority": "warn",
"code": "schema_rules_drop:debug",
"count": 1,
"msg": "field \"debug\" dropped: not in declared schema"
},
{
"priority": "warn",
"code": "schema_rules_drop:status",
"count": 1,
"msg": "field \"status\" dropped: type rule violated (allowed integer, saw string)"
}
]
```
Axiom calculates ingest usage based on the data as it arrives. Fields that Axiom discards because the schema is locked still count towards your ingest usage.
Locking the schema can be useful if you want to protect a dataset with a well-defined schema from unexpected fields, ensure that fields have consistent types, or keep the number of fields within the [allowed limits](/reference/limits#pricing-based-limits). To reduce the number of fields already associated with a dataset, [vacuum its fields](#vacuum-fields).
Locking and unlocking the schema take effect immediately and don’t change the data stored in the dataset. When you unlock the schema, Axiom restores the default behavior and adds new fields to the schema during the data ingestion process. Unlocking doesn’t restore fields or values that Axiom discarded while the schema was locked.
* You can only lock the schema of event datasets. Metrics datasets don’t support schema locking.
* To lock or unlock the schema, you need update permissions for datasets. Without these permissions, Axiom doesn’t display the menu options below.
* You can’t lock the schema of a dataset that another organization has shared with your organization.
* You can’t lock the schema of a dataset that you have set up through the [Cloudflare Logpush](/apps/cloudflare-logpush), [Vercel](/apps/vercel), or [Netlify](/apps/netlify) integration.
To lock the schema of a dataset, follow these steps:
1. Go to the Datasets tab.
2. In the list, select the dataset whose schema you want to lock.
3. In the **Fields** panel, click **More > Lock schema**.
Axiom displays a **Schema locked** message. While the schema is locked, the title of the Fields panel is **Fields (Locked)**.
To unlock the schema of a dataset, follow these steps:
1. Go to the Datasets tab.
2. In the list, select the dataset whose schema you want to unlock.
3. In the **Fields** panel, click **More > Unlock schema**.
Axiom displays a **Schema unlocked** message.
## Share datasets [#share-datasets]
You can share your datasets with other Axiom organizations. The receiving organization:
* can query the shared dataset.
* can create other Axiom resources that rely on query access such as dashboards and monitors.
* can’t ingest data into the shared dataset.
* can‘t modify the shared dataset.
No ingest usage associated with the shared dataset accrues to the receiving organization. Query usage associated with the shared dataset accrues to the organization running the query.
To share a dataset with another Axiom organization:
1. Ensure you have the necessary privileges to share datasets. By default, only users with the Owner role can share datasets.
2. Click **Settings > Datasets and views**.
3. In the list, find the dataset that you want to share, and then click **Share dataset** on the right.
4. In the Sharing links section, click **+** to create a new sharing link.
5. Copy the URL and share it with the receiving user in the organization with which you want to share the dataset. For example, `https://app.axiom.co/s/dataset/{sharing-token}`.
6. Ask the receiving user to open the sharing link. When opening the link, the receiving user sees the name of the dataset and the email address of the Axiom user that created the sharing link. They click **Add dataset** to confirm that they want to receive the shared dataset.
### Delete sharing link [#delete-sharing-link]
Organizations can gain access to the dataset with an active sharing link. To deactivate the sharing link, delete the sharing link. Deleting a sharing link means that organizations that don’t have access to the dataset can’t use the sharing link to join the dataset in the future. Deleting a sharing link doesn’t affect the access of organizations that already have access to the shared dataset.
To delete a sharing link:
1. Click **Settings > Datasets and views**.
2. In the list, find the dataset, and then click **Share dataset** on the right.
3. To the right of the sharing link, click **Delete**.
4. Click **Delete sharing link**.
### Remove access to shared dataset [#remove-access-to-shared-dataset]
If your organization has previously shared a dataset with a receiving organization, and you want to remove the receiving organization’s access to the dataset, follow these steps:
1. Click **Settings > Datasets and views**.
2. In the list, find the dataset, and then click **Share dataset** on the right.
3. In the list, find the organization whose access you want to remove, and then click **Remove**.
4. Click **Remove access**.
### Remove shared dataset [#remove-shared-dataset]
If your organization has previously received access to a dataset from a sending organization, and you want to remove the shared dataset from your organization, follow these steps:
1. Ensure you have Delete permissions for the shared dataset.
2. Click **Settings > Datasets and views**.
3. In the list, click the shared dataset that you want to remove, and then click **Remove dataset**.
4. Enter the name of the dataset, and then click **Remove**.
This procedure only removes the shared dataset from your organization. The underlying dataset in the sending organization isn’t affected.
## Specify data retention period [#specify-data-retention-period]
The data retention period determines how long Axiom stores your data. By default, the data retention period is the same for all datasets. You can configure custom retention periods for individual datasets. As a result, Axiom automatically trims data after the specified time period instead of the default period. For example, this can be useful if your dataset contains sensitive event data that you don’t want to retain for a long time.
The retention period of a dataset is set to **Default** unless you select a different value. Datasets set to **Default** follow your organization’s [default dataset retention period](/reference/organization-settings#set-default-dataset-retention-period). Changing the default therefore also changes the retention period of these datasets, and deletes data in them if you lower the default. When you select any other retention period for a dataset, the dataset uses that custom retention period and becomes independent of the default.
The custom retention period you can set for an individual dataset depends on your plan:
* On the Axiom Cloud plan, you can set any retention period, including a period that’s longer than your organization’s default, or **Forever**.
* On other plans, the custom retention period must be shorter than the default data retention period. For example, if you’re on the Personal plan, the default data retention period is 30 days and you can only specify a shorter period.
For more information, see [Pricing-based limits](/reference/limits#pricing-based-limits) and [Set default dataset retention period](/reference/organization-settings#set-default-dataset-retention-period).
When you specify a data retention period for a dataset that’s shorter than the previous setting, all data older than the new retention period is automatically deleted. This process can’t be undone.
The retention period determines how long Axiom stores your data, but not how far back you can query it. How far back you can query is limited by the maximum query window of your organization, which is determined by your plan. The maximum query window doesn’t change when you change the default dataset retention period or the retention period of individual datasets.
To change the data retention period for a dataset, follow these steps:
1. Click **Settings > Datasets and views**.
2. In the list, find the dataset for which you want to change the retention period, and then click **Edit dataset retention** on the right.
3. Select a retention period, or select **Custom** to enter your own retention period in days. To make the dataset follow your organization’s default dataset retention period, select **Default**.
4. Click **Submit**.
## Delete dataset [#delete-dataset]
Deleting a dataset deletes all data contained in the dataset.
To delete a dataset, follow these steps:
1. Click **Settings > Datasets and views**.
2. In the list, click the dataset that you want to delete, and then click **Delete dataset**.
3. Enter the name of the dataset, and then click **Delete**.
## Manage apps and endpoints [#manage-apps-and-endpoints]
Apps allow you to enrich your Axiom organization with dedicated apps. For more information, see [Introduction to apps](/apps/introduction).
Endpoints allow you to easily integrate Axiom into your existing data flow using tools and libraries that you already know. With endpoints, you can build and configure your existing tooling to send data to Axiom so you can start monitoring your logs immediately.
### Edit app [#edit-app]
1. Click **Settings > Apps**.
2. Find the app in the list, and then click **More > Edit**.
### Disconnect app [#disconnect-app]
1. Click **Settings > Apps**.
2. Find the app in the list, and then click **More > Disconnect**.
### Create endpoint [#create-endpoint]
1. Click **Settings > Endpoints**.
2. Click **New endpoint**.
3. Click the type of endpoint you want to create.
4. Name the endpoint.
5. Select the dataset where you want to send data.
6. Copy the URL displayed for the newly created endpoint. This is the target URL where you send the data.
### Delete endpoint [#delete-endpoint]
1. Click **Settings > Endpoints**.
2. Find the endpoint in the list, and then click **Delete endpoint** on the right.
---
# Edge deployments
Source: https://axiom.co/docs/reference/edge-deployments
Axiom is a unified data platform built on edge deployments.
When you create an organization, you choose a primary edge deployment like `US East 1 (AWS)`, and your event data is stored at rest in that infrastructure. You manage everything through Axiom’s Console at `app.axiom.co` regardless of which edge deployment you select.
This edge deployment model provides flexibility that isn’t possible with separate regional instances. Within a single organization, you can create datasets in different edge deployments while maintaining unified management and billing.
## Available edge deployments [#available-edge-deployments]
Axiom currently supports the following edge deployments:
The base domain of all other API endpoints is `api.axiom.co`.
Axiom prioritizes new edge deployments based on interest. [Contact Axiom](https://www.axiom.co/contact) to discuss your requirements.
## Current capabilities [#current-capabilities]
Your organization’s default edge deployment determines where Axiom stores event data at rest and where queries execute. When you run a query, both the execution and results remain within your selected edge deployment.
| Operation | Behavior |
| :------------------ | :----------------------- |
| **Data ingest** | Selected edge deployment |
| **Data storage** | Selected edge deployment |
| **Query execution** | Selected edge deployment |
| **Query results** | Selected edge deployment |
| **Account data** | US infrastructure |
When you select an edge deployment, event data is ingested, stored, and queried entirely within that edge deployment. Query results are returned directly from the edge without routing through other infrastructure. Account management operations (user settings, billing, organization configuration) are processed through Axiom’s centralized US infrastructure.
### Compliance considerations [#compliance-considerations]
Axiom’s edge deployment architecture supports data-sovereignty requirements by keeping event data ingestion, storage, query execution, and query results within your selected edge deployment. This architecture is designed to meet frameworks that require both storage and processing of data to take place entirely within a designated region.
Account management operations (user authentication, billing, organization settings) are processed through Axiom’s centralized US infrastructure. Evaluate whether this meets your specific compliance requirements. For most data-sovereignty frameworks, the critical factor is where event data is stored and processed, which remains entirely within your selected edge deployment.
### Determine your organization’s edge deployment [#determine-your-organizations-edge-deployment]
In multi-region organizations, you can specify the edge deployment for each dataset. This means that you can select where Axiom stores your event data when creating a new dataset. You can't change the edge deployment of a dataset after creating the dataset.
The default edge deployment of your organization is the option automatically selected when you create a new dataset. You can’t change your default edge deployment after creating your organization.
To determine your organization’s default edge deployment:
1. Click **Settings > General**.
2. Find your organization’s default edge deployment in the **Edge deployment** section.
## Multi-edge organizations [#multi-edge-organizations]
To create datasets in an edge deployment other than your organization’s default edge deployment:
1. [Buy the add-on](/reference/usage-billing#manage-add-ons) associated with the new edge deployment.
2. When you [create a new dataset](/reference/datasets#create-dataset), select the new edge deployment.
Add-on must remain active to retain access to datasets located outside your organization’s default edge deployment.
In multi-edge organizations, each query can only access data from a single edge deployment. Wildcard operators in queries only match datasets within the organization’s default edge deployment.
## SDKs and edge deployments [#sdks-and-edge-deployments]
Axiom SDKs will support edge deployments through environment variables. The SDKs will default to `US East 1 (AWS)`. Until Axiom updates all SDKs, configure ingest endpoints manually.
## Axiom-hosted MCP server [#axiom-hosted-mcp-server]
The Axiom-hosted MCP Server is located in the US. When you query your data using the Axiom MCP Server using a remote MCP connection, query results are routed through US infrastructure.
For more information, see [Axiom MCP Server](/console/intelligence/mcp-server).
---
# Limits
Source: https://axiom.co/docs/reference/limits
Axiom applies certain limits and requirements to guarantee good service across the platform. Some of these limits depend on your pricing plan, and some of them are applied system-wide. This reference article explains all limits and requirements applied by Axiom.
Limits are necessary to prevent potential issues that could arise from the ingestion of excessively large events or data structures that are too complex. Limits help maintain system performance, allow for effective data processing, and manage resources effectively.
## Pricing-based limits [#pricing-based-limits]
The table below summarizes the limits applied to each pricing plan. For more details on pricing and contact information, see the [Axiom pricing page](https://axiom.co/pricing).
| | Personal | Axiom Cloud |
| :-------------------------------------------------------- | :------------------ | :------------------- |
| Always Free storage | 25 GB | 100 GB |
| Always Free data loading | 500 GB / month | 1,000 GB / month |
| Always Free query compute | 10 GB-hours / month | 100 GB-hours / month |
| Maximum data loading | 500 GB / month | – |
| Maximum data retention | 30 days | Custom |
| [Datasets](/reference/datasets) | 3 | 100 \* |
| Fields per dataset | 256 | 1,024 \* |
| Users | 1 | 1,000 \* |
| [Monitors](/monitor-data/monitors) | 3 | 500 \* |
| [Notifiers](/monitor-data/notifiers-overview) | Email, Discord | All supported |
| [Supported edge deployments](/reference/edge-deployments) | US | US |
\* Soft limit that can be increased upon request.
If you’re on the Axiom Cloud plan and you exceed the Always Free allowances outlined above, additional charges apply based on your usage above the allowance. For more information, see the [Axiom pricing page](https://axiom.co/pricing).
All plans include unlimited bandwidth, API access, and data sources subject to the [Fair Use Policy](https://axiom.co/terms).
To see how much of your allowance each dataset uses, go to **Settings > Usage**.
For more information on how to save on data loading, data retention, and querying costs, see [Optimize usage](/reference/optimize-usage).
### Restrictions on datasets and fields [#restrictions-on-datasets-and-fields]
Axiom restricts the number of datasets and the number of fields in your datasets. The number of datasets and fields you can use is based on your pricing plan and explained in the table above.
If you ingest a new event that would exceed the allowed number of fields in a dataset, Axiom returns an error and rejects the event. To prevent this error, ensure that the number of fields in your events are within the allowed limits.
To reduce the number of fields in a dataset, use one of the following approaches:
* [Trim the dataset](/reference/datasets#trim-dataset) and [vacuum its fields](/reference/datasets#vacuum-fields).
* Use [map fields](/apl/data-types/map-fields).
To prevent new fields from being added to an event dataset, [lock its schema](/reference/datasets#lock-dataset-schema).
## System-wide limits [#system-wide-limits]
The following limits are applied to all accounts, irrespective of the pricing plan.
### Limits on ingested data [#limits-on-ingested-data]
The table below summarizes the limits Axiom applies to each data ingest. These limits are independent of your pricing plan.
| | Limit |
| ------------------------- | --------- |
| Maximum field size | 1 MB |
| Maximum events in a batch | 10,000 |
| Maximum field name length | 200 bytes |
If you try to ingest data that exceeds these limits, Axiom does the following:
* Replaces strings that are too long with ``.
* Replaces binary with ``.
* Truncates maps and slices that nest deeper than 100 levels and replaces them with `nil` at the cut-off level.
* Converts the following float values to `nil`:
* NaN
* +Infty
* -Infty
### Special fields [#special-fields]
Axiom creates the following two fields automatically for a new dataset:
* `_time` is the timestamp of the event. If the data you ingest doesn’t have a `_time` field, Axiom assigns the time of the data ingest to the events. If you ingest data using the [Ingest data](/restapi/endpoints/ingestToDataset) API endpoint, you can specify the timestamp field with the [timestamp-field](/restapi/endpoints/ingestToDataset#parameter-timestamp-field) parameter.
* `_sysTime` is the time when you ingested the data.
In most cases, use `_time` to define the timestamp of events. In rare cases, if you experience clock skews on your event-producing systems, `_sysTime` can be useful.
### Reserved field names [#reserved-field-names]
Axiom reserves the following field names for internal use:
* `_blockInfo`
* `_cursor`
* `_rowID`
* `_source`
* `_sysTime`
Don’t ingest data that contains these fields names. If you try to ingest a field with a reserved name, Axiom renames the ingested field to `_user_FIELDNAME`. For example, if you try to ingest the field `_sysTime`, Axiom renames it to `_user_sysTime`.
In general, avoid ingesting field names that start with `_`.
### Requirements for timestamp field [#requirements-for-timestamp-field]
The most important field requirement is about the timestamp.
All events stored in Axiom must have a `_time` timestamp field. If the data you ingest doesn’t have a `_time` field, Axiom assigns the time of the data ingest to the events. To specify the timestamp yourself, include a `_time` field in the ingested data.
If you include the `_time` field in the ingested data, follow these requirements:
* Timestamps are specified in the `_time` field.
* The `_time` field contains timestamps in a valid time format. Axiom accepts many date strings and timestamps without knowing the format in advance, including Unix Epoch, RFC3339, or ISO 8601.
* The `_time` field is a field with UTF-8 encoding.
* The `_time` field isn’t used for any other purpose.
### Requirements for log level fields [#requirements-for-log-level-fields]
The Stream and Query tabs allow you to easily detect warnings and errors in your logs by highlighting the severity of log entries in different colors. As a prerequisite, specify the log level in the data you send to Axiom.
For Open Telemetry logs, specify the log level in the following fields:
* `severity`
* `severityNumber`
* `severityText`
For AWS Lambda logs, specify the log level in the following fields:
* `record.error`
* `record.level`
* `record.severity`
* `type`
For logs from other sources, specify the log level in the following fields:
* `level`
* `@level`
* `severity`
* `@severity`
* `status.code`
## Temporary account-specific limits [#temporary-account-specific-limits]
If you send a large amount of data in a short amount of time and with a high frequency of API requests, Axiom may temporarily restrict or disable your ability to send data to Axiom. This is to prevent abuse of the platform and to guarantee consistent and high-quality service to all customers. In this case, Axiom kindly asks you to reconsider your approach to data collection. For example, to reduce the total number of API requests, try sending your data in larger batches. This adjustment both streamlines Axiom operations and improves the efficiency of your data ingest. If you often experience these temporary restrictions and have a good reason for changing these limits, please [contact Support](https://axiom.co/contact).
---
# Optimize usage
Source: https://axiom.co/docs/reference/optimize-usage
## Optimize data loading [#optimize-data-loading]
When you send data via the [Ingest data](/restapi/endpoints/ingestToDataset) API endpoint, ingest volume is based on the uncompressed size of the HTTP request. In this case, body size dominates and metadata overhead is usually negligible.
If you ingest data via API, using the CSV format comes with the following trade-offs:
* **Pro:** CSV doesn’t repeat field names. This means lower ingest volume and lower ingest costs.
* **Con:** Every value is a string. You need to do type casting at query time. This means higher cost and latency.
* **Con:** Numbers as strings don’t compress well. This means higher storage and query costs.
For higher volume, using a [dedicated ingestion method](/send-data/methods) is a better choice than sending data via API.
## Optimize storage [#optimize-storage]
The amount of storage you use depends on the following:
* The amount of data you ingest to Axiom.
* The data retention period you specify.
The data retention period defines how long Axiom stores your data. After this period, Axiom trims the data and it doesn’t count towards your storage costs. You can define a custom data retention period for each dataset. For more information, see [Specify data retention period](/reference/datasets#specify-data-retention-period).
## Optimize queries [#optimize-queries]
Axiom measures the resources used to execute queries in terms of GB-hours.
### What GB-hours are [#what-gb-hours-are]
When you run queries, your usage of the Axiom platform is measured in query-hours. The unit of this measurement is GB-hours which reflects the duration (measured in milliseconds) serverless functions are running to execute your query multiplied by the amount of memory (GB) allocated to execution. This metric is important for monitoring and managing your usage against the monthly allowance included in your plan.
### How Axiom measures query-hours [#how-axiom-measures-query-hours]
Axiom uses serverless computing to execute queries efficiently. The consumption of serverless compute resources is measured along two dimensions:
* Time: The duration (in milliseconds) for which the serverless function is running to execute your query.
* Memory allocation: The amount of memory (in GB) allocated to the serverless function during execution.
### What counts as a query [#what-counts-as-a-query]
In calculating query costs, Axiom considers any request that queries your data as a query. For example, the following all count as queries:
* You initiate a query in the Axiom user interface.
* You query your data with an API token or a personal access token.
* Your match monitor runs a query to determine if any new events match your criteria.
Each query is charged at the same rate, irrespective of its origin.
Each monitor run counts towards your query costs. For this reason, the frequency (how often the monitor runs) can have a slight effect on query costs.
### Run queries and understand costs [#run-queries-and-understand-costs]
When you run queries on Axiom, the cost in GB-hours is determined by the shape and size of the events in your dataset and the volume of events scanned to return a query result. After executing a query, you can find the associated query cost in the response header labeled as `X-Axiom-Query-Cost-Gbms`.
### Determine query cost [#determine-query-cost]
Send a `POST` request to the [Run query](/restapi/endpoints/queryApl) endpoint with the following configuration:
* `Content-Type` header with the value `application/json`.
* `Authorization` header with the value `Bearer API_TOKEN`. Replace `API_TOKEN` with your Axiom API token.
* In the body of your request, enter your query in JSON format. For example:
```json
{
"apl": "telegraf | count",
"startTime": "2024-01-11T19:25:00Z",
"endTime": "2024-02-13T19:25:00Z"
}
```
`apl` specifies the Axiom Processing Language (APL) query to run. In this case, `"telegraf | count"` indicates that you query the `telegraf` dataset and use the `count` operator to aggregate the data.
`startTime` and `endTime` define the time range of your query. In this case, `"2024-01-11T19:25:00Z"` is the start time, and `"2024-02-13T19:25:00Z"` is the end time, both in ISO 8601 format. This time range limits the query to events recorded within these specific dates and times.
In the response to your request, the information about the query cost in GB-milliseconds is in the `X-Axiom-Query-Cost-Gbms` header.
### Example of GB-hour calculation [#example-of-gb-hour-calculation]
As an example, a typical query analyzing 1 million events might consume approximately 1 GB-second. There are 3,600 seconds in an hour which means that an organization can run 3,600 of these queries before reaching 1 GB-hour of query usage. This is an example and the actual usage depends on the complexity of the query and the input data.
### Plan and GB-hours allowance [#plan-and-gb-hours-allowance]
Your GB-hours allowance depends on your pricing plan. To learn more about the plan offerings and find the one that best suits your needs, see [Axiom Pricing](https://axiom.co/pricing).
### Optimize queries to lower costs [#optimize-queries-to-lower-costs]
This section explains ways you can optimize your queries to save on query costs. For more information optimizing your queries for performance, see [Optimize performance](/reference/performance).
#### Optimize the order of field-specific filters [#optimize-the-order-of-field-specific-filters]
Field-specific filters narrow your query results to events where a field has a given value. For example, the APL query `where ["my-field"] == "axiom"` filters for events where the `my-field` field takes the value `axiom`.
Include field-specific filters near the beginning of your query for modest savings in query costs. For more information, see [Poor filter order in queries](/reference/performance#poor-filter-order-in-queries)
#### Optimize `search` operator and non-field-specific filters [#optimize-search-operator-and-non-field-specific-filters]
Non-field-specific filters narrow your query results by searching across multiple datasets and fields for a given value. Examples of non-field-specific filters are the `search` operator and equivalent expressions such as `where * contains` or `where * has`.
Using non-field-specific filters can have a significant impact on query costs. For more information, see [Use the `search` operator efficiently](/apl/tabular-operators/search-operator#use-the-search-operator-efficiently) and [Regular expressions when simple filters suffice](/reference/performance#regular-expressions-when-simple-filters-suffice).
### Optimize dashboard refresh rates [#optimize-dashboard-refresh-rates]
Each time your dashboard refreshes, it runs a query on your data which results in query costs. Selecting a short refresh rate (such as 15 seconds) for a long time range (such as 90 days) means that your dashboard frequently runs large queries in the background.
To optimize query costs, choose a refresh rate that's appropriate for the time range of your dashboard. For more information, see [Select refresh rate](/dashboards/configure#select-refresh-rate).
## Monitor costs proactively [#monitor-costs-proactively]
Proactive cost monitoring helps you identify unexpected cost increases before they impact your budget. Use the following strategies to stay on top of your usage.
### Use Axiom Skills [#use-axiom-skills]
Use the [Control costs skill](/console/intelligence/skills/control-costs) to automate cost control tasks. The skill helps you:
* Create a cost monitoring dashboard.
* Set up cost alert monitors.
* Analyze your datasets to identify data that isn't queried and is a good candidate for dropping at the collector level.
### Track data ingest [#track-data-ingest]
The [audit log](/reference/audit-log) captures hourly ingestion data for each dataset.
Identify which datasets contribute most to your ingestion costs:
```kusto
['axiom-audit']
| where action == "usageCalculated"
| extend ingest_gb = tolong(['properties.hourlyIngestBytes']) / pow(1024, 3)
| summarize IngestGB = sum(ingest_gb) by bin_auto(_time), tostring(['properties.dataset'])
| sort by IngestGB desc
```
Determine total data ingest across all datasets over time:
```kusto
['axiom-audit']
| where action == "usageCalculated"
| extend ingest_gb = tolong(['properties.hourlyIngestBytes']) / pow(1024, 3)
| summarize TotalIngestGB = sum(ingest_gb) by bin_auto(_time)
```
### Create a cost monitoring dashboard [#create-a-cost-monitoring-dashboard]
Build a dashboard that displays your key cost metrics at a glance:
1. **Ingest over time**: Track daily or hourly ingestion trends per dataset.
2. **Query costs**: Monitor which queries and monitors consume the most resources.
3. **High-cost queries**: List queries that exceed a cost threshold.
For dashboard creation, see [Create a dashboard](/dashboards/create).
### Set up cost alerts [#set-up-cost-alerts]
Create monitors to alert you when costs exceed expected thresholds:
* **Ingest spike alert**: Notify when hourly ingestion exceeds a threshold.
* **Query cost alert**: Notify when a single query exceeds a cost threshold.
* **Daily cost summary**: Send a daily digest of total costs.
Example monitor query for ingestion spikes:
```kusto
['axiom-audit']
| where action == "usageCalculated"
| extend ingest_gb = tolong(['properties.hourlyIngestBytes']) / pow(1024, 3)
| summarize HourlyIngestGB = sum(ingest_gb) by bin(_time, 1h)
| where HourlyIngestGB > 10
```
Adjust the threshold for `HourlyIngestGB` based on your expected ingestion patterns. For more information, see [Threshold monitors](/monitor-data/threshold-monitors).
### Set a spending limit [#set-a-spending-limit]
Set a monthly spending limit to automatically pause cost-generating actions when you reach a specified amount. For more information, see [Spending limit](/reference/usage-billing#spending-limit).
### Set refresh rate for dashboards [#set-refresh-rate-for-dashboards]
To optimize query costs, choose a refresh rate that’s appropriate for the time range of your dashboard. For more information, see [Select refresh rate](/dashboards/configure#select-refresh-rate).
---
# Configure Axiom organization
Source: https://axiom.co/docs/reference/organization-settings
## Determine organization ID [#determine-organization-id]
1. Click **Settings > General**.
2. Find organization ID in the **ID** section.
## Determine organization edge deployment [#determine-organization-edge-deployment]
1. Click **Settings > General**.
2. Find your organization’s default edge deployment in the **Edge deployment** section.
For more information, see [Edge deployments](/reference/edge-deployments).
## Set default dataset retention period [#set-default-dataset-retention-period]
The default dataset retention period determines how long Axiom stores data in datasets that don’t have their own custom retention period. Datasets without a custom retention period inherit this value. You can only set the default dataset retention period on the Axiom Cloud plan.
Changing the default dataset retention period affects **existing datasets**, not only datasets you create later. When you lower the default, Axiom immediately deletes all data older than the new period in every dataset that inherits the default. This process can’t be undone.
Before you lower the default, set a custom retention period on each dataset whose data you want to keep. For more information, see [Specify data retention period](/reference/datasets#specify-data-retention-period).
The default dataset retention period doesn’t affect the maximum query window of your organization. The maximum query window is determined by your plan and stays the same when you change the default dataset retention period.
To store data for longer, increase the default dataset retention period. Increasing the default doesn’t delete any data.
To set the default dataset retention period, follow these steps:
1. Click **Settings > General**.
2. Click next to the **Default dataset retention** section.
3. Specify the default dataset retention period.
4. Click **Submit**.
## Turn Axiom AI on or off [#turn-axiom-ai-on-or-off]
## Delete organization [#delete-organization]
This is a destructive action. After you delete your organization, you lose access to all data within that org.
To delete your organization:
1. Back up your data. You aren’t able to access the data after deleting the org.
2. Click **Settings > General**
3. Click **Delete organization**.
---
# Optimize performance
Source: https://axiom.co/docs/reference/performance
Axiom is optimized for storing and querying timestamped event data. However, certain ingest and query practices can degrade performance and increase cost. This page explains pitfalls and provides guidance on how you can avoid them to keep your Axiom queries fast and efficient.
## Summary of pitfalls [#summary-of-pitfalls]
| Practice | Severity | Impact |
| :-------------------------------------------------------------------------------------------------------------------------- | :------- | :------------------------------------------------------------- |
| [Mixing unrelated data in datasets](#mixing-unrelated-data-in-datasets) | Critical | Combining unrelated data inflates schema, slows queries |
| [Excessive backfilling, big difference between \_time and \_sysTime](#excessive-backfilling-and-large-time-vs-systime-gaps) | Critical | Creates overlapping blocks, breaks time-based indexing |
| [Large number of fields in a dataset](#large-number-of-fields-in-a-dataset) | High | Very high dimensionality slows down query performance |
| [Failing to use \_time](#failing-to-use-the-_time-field-for-event-timestamps) | High | No efficient time-based filtering |
| [Query exceeds memory constraints](#query-exceeds-memory-constraints) | High | Queries can exceed memory limits and fail with HTTP 432 errors |
| [Overly wide queries (project \*)](#overly-wide-queries-returning-more-fields-than-needed) | High | Returns massive unneeded data |
| [Mixed data types in the same field](#mixing-unrelated-data-in-datasets) | Moderate | Reduces compression, complicates queries |
| [Using regex when simpler filters suffice](#regular-expressions-when-simple-filters-suffice) | Moderate | More CPU-heavy scanning |
| [Overusing runtime JSON parsing (parse\_json)](#overusing-runtime-json-parsing-parse_json) | Moderate | CPU overhead, no indexing on nested fields |
| [Virtual fields for simple transformations](#virtual-fields-for-simple-transformations) | Low | Extra overhead for trivial conversions |
| [Poor filter order in queries](#poor-filter-order-in-queries) | Low | Suboptimal scanning of data |
## Mixing unrelated data in datasets [#mixing-unrelated-data-in-datasets]
### Problem [#problem]
A “kitchen-sink” dataset is one in which events from multiple, unrelated apps or services get lumped together, often resulting in:
* **Excessive width (too many fields)**: Adding more and more unique fields bloats the schema, reducing query throughput.
* **Mixed data types in the same field**: For example, some events store `user_id` as a string, while others store it as a number in the same `user_id` field.
* **Unrelated schemas in a single dataset**: Fields that make sense for one app might be `null` or typed differently for another.
These issues reduce compression efficiency and force Axiom to scan more data than necessary.
### Why it matters [#why-it-matters]
* **Slower queries**: Each query must scan wider blocks of data and handle inconsistent field types.
* **Higher resource usage**: Wide schemas reduce row packing in blocks, harming throughput and potentially raising costs.
* **Harder data exploration**: When fields differ drastically between events, discovering the correct fields or shaping queries becomes more difficult.
### How to fix it [#how-to-fix-it]
* **Keep datasets narrowly focused:** Group data from the same app or service in its own dataset. For example, keep `k8s_logs` separate from `web_traffic`.
* **Avoid mixing data types for the same field:** Enforce consistent types during ingest. If a field is numeric, always send numeric values.
* **Consider using map fields:** If you have sparse or high-cardinality nested data, consider storing it in a single map (object) field instead of flattening every key. This reduces the total number of top-level fields. Axiom’s [map fields](/apl/data-types/map-fields#map-fields) are optimized for large objects.
## Excessive backfilling and large `_time` vs. `_sysTime` gaps [#excessive-backfilling-and-large-_time-vs-_systime-gaps]
### Problem [#problem-1]
Axiom’s `_time` index is critical for query performance. Ideally, incoming events for a block lie in a closely bounded time range. However, backfilling large amounts of historical data after the fact (especially out of chronological order) creates wide time overlaps in blocks. If `_time` is far from `_sysTime` (the time the event was ingested), Axiom’s time index effectiveness is weakened.
### Why it matters [#why-it-matters-1]
* **Poor performance on time-based queries**: Blocks must be scanned despite time filters, because many blocks overlap the query time window.
* **Inefficient block filtering**: Queries that filter on time must scan blocks that contain data from a wide time range.
* **Large data merges**: Compaction processes that rely on time ordering become less efficient.
### How to fix it [#how-to-fix-it-1]
* **Minimize backfill:** Try to ingest events close to their actual creation time whenever possible. Ingest events close to the time they occur.
* **Backfill in dedicated batches:** If you must backfill older data, do it in dedicated batches that don’t mix with live data.
* **Use discrete backfill intervals:** When backfilling data, ingest one segment at a time (for example, day-by-day).
* **Avoid wide time ranges in a single batch:** If you are sending data for a 24-hour period, avoid mixing in data that’s weeks or months older.
* **Be aware of ingestion concurrency:** Avoid mixing brand-new events with extremely old events in the same ingest request.
Future improvements: Axiom’s roadmap includes an initiative which aims to mitigate the impact of poorly clustered time data by performing incremental time-based compaction. Until then, avoid mixing large historical ranges with live ingest whenever possible.
## Large number of fields in a dataset [#large-number-of-fields-in-a-dataset]
### Problem [#problem-2]
Slow query performance in datasets with very high dimensionality (with more than several thousand fields).
### Why it matters [#why-it-matters-2]
Axiom stores event data in a tuned format. As a result:
* The number of distinct values (cardinality) in your data impacts performance because low-cardinality fields compress better than high-cardinality fields.
* The number of fields in a dataset (dimensionality) impacts performance.
* The volume of data collected impacts performance.
### How to fix it [#how-to-fix-it-2]
Scoping the number of fields in a dataset below a few thousand can help you achieve the best performance in Axiom.
## Failing to use the `_time` field for event timestamps [#failing-to-use-the-_time-field-for-event-timestamps]
### Problem [#problem-3]
Axiom’s core optimizations rely on `_time` for indexing and time-based queries. If you store event timestamps in a different field (for example, `timestamp` or `created_at`) and use that field in time filters, you can’t leverage Axiom’s time-based optimizations.
### Why it matters [#why-it-matters-3]
* **No time-based indexing**: Every block must be scanned because your custom timestamp field is invisible to the time index.
### How to fix it [#how-to-fix-it-3]
* **Always use `_time`:** Configure your ingest pipelines so that Axiom sets `_time` to the actual event timestamp.
* If you have a custom field like `created_at`, rename it to `_time` at ingest.
* Verify that your ingestion library or agent is correctly populating `_time`.
* **Use Axiom’s native time filters:** Rely on `where _time >= ... and _time <= ...` or the built-in time range selectors in the query UI.
## Handling mixed types in the same field [#handling-mixed-types-in-the-same-field]
### Problem [#problem-4]
A single field sometimes stores different data types across events (for instance, strings in some events and integers in others). This is typically a side effect of using “kitchen-sink” ingestion or inconsistent parsing logic in your code.
### Why it matters [#why-it-matters-4]
* **Reduced compression**: Storing multiple types in the same field (variant field) is less efficient than storing a single type.
* **Complex queries**: You might need frequent casting or conditional logic in queries (`tostring()` calls, etc.).
### How to fix it [#how-to-fix-it-4]
* **Standardize your types at ingest:** If a field is semantically an integer, always send it as an integer.
* **Use consistent schemas across services:** If multiple apps write to the same dataset, agree on a schema and data types.
* **Perform corrections at the source:** If you discover your data has been mixed historically, stop ingesting mismatched types. Over time, new blocks reflect the corrected types even though historical blocks remain mixed.
## Query exceeds memory constraints [#query-exceeds-memory-constraints]
### Problem [#problem-5]
Some queries can exceed the available memory budget on query workers, causing them to fail with an HTTP 432 error (`StatusQueryMemoryExceeded`). This typically happens when queries use memory-intensive operations on large datasets or wide schemas. For example, when you use `parse_json()` on large strings or `pack(*)` on datasets with many fields.
### Why it matters [#why-it-matters-5]
* **Query failures**: Queries that exceed memory limits are terminated and return an error instead of results.
* **Resource constraints**: Query workers have fixed memory allocations. Memory-intensive operations can exhaust these allocations.
* **User experience**: Failed queries provide no results and require query optimization to succeed.
When a query exceeds memory limits, Axiom returns an HTTP 432 error with a message indicating memory constraints.
### How to fix it [#how-to-fix-it-5]
#### Optimize `parse_json()` usage [#optimize-parse_json-usage]
`parse_json()` can consume significant memory when parsing large JSON strings, especially when applied to many rows.
* **Use map fields at ingest time:** Instead of storing JSON as strings and parsing at query time, ingest JSON data as [map fields](/apl/data-types/map-fields#map-fields). Map fields are stored columnar and don’t require runtime parsing:
Instead of:
```kusto
['logs']
| extend parsed = parse_json(json_payload)
| where parsed.status == "error"
```
Ingest `json_payload` as a map field and query directly:
```kusto
['logs']
| where json_payload.status == "error"
```
* **Extract frequently used fields:** Extract only the fields you need rather than parsing entire objects:
```kusto
['logs']
| extend status = parse_json(json_payload).status
| where status == "error"
```
* **Filter before parsing:** Apply filters to reduce the dataset size before using `parse_json()`:
```kusto
['logs']
| where json_payload contains "error"
| extend parsed = parse_json(json_payload)
| where parsed.status == "error"
```
#### Optimize `pack(*)` usage [#optimize-pack-usage]
`pack(*)` creates a dictionary from all fields in each row, which can consume significant memory on wide datasets (datasets with many fields).
* **Use `pack` with specific fields:** Instead of packing all fields, pack only the fields you need:
Instead of:
```kusto
['logs']
| extend event = pack(*)
```
Use:
```kusto
['logs']
| extend event = pack('timestamp', _time, 'level', level, 'message', message)
```
Or use `pack_dictionary()` for explicit key-value pairs:
```kusto
['logs']
| extend event = pack_dictionary('timestamp', _time, 'level', level, 'message', message)
```
* **Use `project` to reduce fields first:** If you need to pack multiple fields, reduce the dataset width first:
```kusto
['logs']
| project _time, level, message, user_id
| extend event = pack(*)
```
* **Use map fields at ingest time** instead of using `pack(*)`. Instead of storing JSON as strings and parsing at query time, ingest JSON data as [map fields](/apl/data-types/map-fields#map-fields).
## Overly wide queries returning more fields than needed [#overly-wide-queries-returning-more-fields-than-needed]
### Problem [#problem-6]
By default, Axiom’s query engine projects all fields (`project *`) for each matching event. This can return large amounts of unneeded data, especially in wide datasets with many fields.
### Why it matters [#why-it-matters-6]
* **High I/O and memory usage**: Unnecessary data is scanned, read, and returned.
* **Slower queries**: Time is wasted processing fields you never use.
### How to fix it [#how-to-fix-it-6]
* **Use `project` or `project-keep`**
Specify exactly which fields you need. For example:
```kusto
dataset
| where status == 500
| project timestamp, error_code, user_id
```
* **Use `project-away` if you only need to exclude a few fields:** If you need 90% of the fields but want to exclude the largest ones, for instance:
```kusto
dataset
| project-away debug_payload, large_object_field
```
* **Limit your results**
If you only need a sample of events for debugging, use a lower `limit` value (such as 10) instead of the default 1000.
## Regular expressions when simple filters suffice [#regular-expressions-when-simple-filters-suffice]
### Problem [#problem-7]
Regular expressions (`matches`, `regex`) can be powerful, but they’re also expensive to evaluate, especially on large datasets.
### Why it matters [#why-it-matters-7]
* **High CPU usage**: Regex filters require complex per-row matching.
* **Slower queries**: Axiom scans large swaths of data with less efficient matching.
### How to fix it [#how-to-fix-it-7]
* **Use direct string filters**
Instead of:
```kusto
dataset
| where message matches "[Ff]ailed"
```
Use:
```kusto
dataset
| where message contains "failed"
```
* **Use `search` for substring search:**
To find `foobar` in all fields, use:
```kusto
dataset
| search "foobar"
```
`search` matches text in all fields. To find text in a specific field, a more efficient solution is to use the following:
```kusto
dataset
| where FIELD contains_cs "foobar"
```
In this example, `cs` stands for case-sensitive.
## Overusing runtime JSON parsing (`parse_json`) [#overusing-runtime-json-parsing-parse_json]
### Problem [#problem-8]
Some ingestion pipelines place large JSON payloads into a string field, deferring parsing until query time with `parse_json()`. This is both CPU-intensive and slower than columnar operations.
### Why it matters [#why-it-matters-8]
* **Repeated parsing overhead**: You pay a performance penalty on each query.
* **Limited indexing**: Axiom can’t index nested fields if they’re only known at query time.
### How to fix it [#how-to-fix-it-8]
* **Ingest as map fields:** Axiom’s new [map field type](/apl/data-types/map-fields#map-fields) can store object fields column by column, preserving structure and optimizing for nested queries. This allows indexing of specific nested keys.
* **Extract top-level fields where possible:** If a certain nested field is frequently used for filtering or grouping, consider promoting it to its own top-level field (for faster scanning and filtering). For more information, see [Extract top-level fields from log body](/send-data/kubernetes#extract-top-level-fields-from-log-body).
* **Avoid `parse_json()` in query:** If your JSON can’t be flattened entirely, ingest it into a map field. Then query subfields directly:
```kusto
dataset
| where data_map.someKey == "someValue"
```
## Virtual fields for simple transformations [#virtual-fields-for-simple-transformations]
### Problem [#problem-9]
You can create virtual fields (for example, `extend converted = toint(some_field)`) to transform data at query time. While sometimes necessary, every additional virtual field imposes overhead.
### Why it matters [#why-it-matters-9]
* **Increased CPU**: Each virtual field requires interpretation by Axiom’s expression engine.
* **Slower queries**: Overuse of `extend` for trivial or frequently repeated operations can add up.
### How to fix it [#how-to-fix-it-9]
* **Avoid unnecessary casting:** If a field must be an integer, handle it at ingest time.
**Example:** Instead of
```kusto
dataset
| extend str_user_id = tostring(mixed_user_id)
| where str_user_id contains "123"
```
Use:
```kusto
| where mixed_user_id contains "123"
```
The filter automatically matches string values in mixed fields.
* **Reserve virtual fields for truly dynamic or derived logic**
If you frequently need a computed value, store it at ingest or keep the transformations minimal.
## Poor filter order in queries [#poor-filter-order-in-queries]
### Problem [#problem-10]
Axiom’s query engine doesn’t currently reorder your `where` clauses optimally. This means the sequence of filters in your query can matter.
### Why it matters [#why-it-matters-10]
* **Unnecessary scans**: If you use selective filters last, the engine may process many rows before discarding them.
* **Longer execution times**: CPU usage and scan times increase.
### How to fix it [#how-to-fix-it-10]
* **Put the most selective filters first:**
Example:
```kusto
dataset
| where user_id == 1234
| where log_level == "ERROR"
```
If `user_id == 1234` discards most rows, apply it before `log_level == "ERROR"`.
* **Profile your filters:** Experiment with which filters discard the most rows to find the most selective conditions.
---
# Configure user profile
Source: https://axiom.co/docs/reference/profile
## Change name [#change-name]
1. Click **Settings > Profile**.
2. Enter your name in the **Name** section.
## View contact details and base role [#view-contact-details-and-base-role]
1. Click **Settings > Profile**.
2. Find your your contact details and base role in the **Email** and **Role** sections.
## Change timezone [#change-timezone]
1. Click **Settings > Profile**.
2. Select your timezone in the **Timezone** section.
## Change editor mode [#change-editor-mode]
The editor mode determines the style of the APL query editor. To change this:
1. Click **Settings > Profile**.
2. Select your preferred editor in the **Editor mode** section.
## Select default method for null values [#select-default-method-for-null-values]
When you visualize your data, you can select how Axiom treats missing or undefined values in the chart. For more information, see [Configure dashboard elements](/dashboard-elements/configure#values). When you select a default method to deal with null values, Axiom uses this method in every new chart you create.
To select the default method for null values:
1. Click **Settings > Profile**.
2. Select your preferred editor in the **default method for null values** section.
## Manage personal access tokens [#manage-personal-access-tokens]
Create and delete personal access tokens (PATs). For more information, see [Personal access tokens](/reference/tokens#personal-access-tokens-pat).
## View and manage active sessions [#view-and-manage-active-sessions]
1. Click **Settings > Profile**.
2. View active sessions in the **Sessions** section.
3. Optional: To log out of a session, find the session in the list, and then click **Delete session** on the right.
## Delete user account [#delete-user-account]
This is a destructive action. After you delete your user account, you can’t recover it.
To delete your user account:
1. Click **Settings > Profile**.
2. Click **Delete account**.
---
# Semantic conventions
Source: https://axiom.co/docs/reference/semantic-conventions
[OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/) specify standard attribute names and values for different kinds of operations and data.
## Trace attributes in Axiom [#trace-attributes-in-axiom]
The OTel trace attributes you send to Axiom are available under the following fields:
* Attributes that follow semantic conventions are nested fields under the `attributes` field. For example, `attributes.http.method` or `attributes.http.url`.
* Resource attributes that follow semantic conventions are nested fields under the `resource` field. For example, `resource.host.name` or `resource.host.id`.
* Custom attributes that don’t match any semantic conventions are nested fields under the `attributes.custom` map field.
For more information on map fields and querying nested fields, see [Map fields](/apl/data-types/map-fields).
## Supported versions [#supported-versions]
For the versions of OTel semantic conventions that Axiom supports, see [System requirements](/reference/system-requirements#opentelemetry).
(Recommended) When you send OTel data to Axiom, include the version of the OTel semantic conventions that your data follows. This ensures that your data is properly shaped in Axiom.
If you don’t define the version and Axiom can’t detect it, Axiom defaults to the version specified in [System requirements](/reference/system-requirements#opentelemetry). In this case, Axiom nests attributes that don’t match the semantic conventions of the default version under the `attributes.custom` map field.
## Semantic conventions upgrades [#semantic-conventions-upgrades]
To guarantee the best logging experience with OTel, Axiom regularly updates the list of supported versions of semantic conventions and sometimes the default version. These updates can change the shape of your data. Axiom announces these changes in the [Changelog](https://axiom.co/changelog) and you might need to take action:
* If you send data to Axiom using an unsupported version of OTel semantic conventions, be aware that the shape of your data can change when Axiom adds support for new versions.
* When you send OTel data to Axiom, include the version of the OTel semantic conventions that your data follows. This ensures that your data is properly shaped in Axiom and it won’t be affected when Axiom changes the default version.
In addition, the shape of your data can change when you choose to migrate to a newer version of OTel semantic conventions.
See the sections below for more details on how your data can change and the actions you need to take when this happens.
### Changes to list of supported versions [#changes-to-list-of-supported-versions]
After Axiom adds support for new versions of OTel semantic conventions, the shape of your data can change when the following are all true:
* Before the update, you sent data to Axiom using an unsupported version of OTel semantic conventions.
* After the update, the version of OTel semantic conventions that you used becomes supported.
In this case, the shape of your data can change:
* Before the update, attributes that Axiom couldn’t match to the previously supported semantic conventions were nested under the `attributes.custom` map field.
* After the update, Axiom matches these attributes to the newly supported semantic conventions. The newly recognized attributes are nested under the `attributes` or `resource` fields, similarly to all other attributes that follow semantic conventions.
When the shape of your data changes, you need to [take action](#take-action-when-shape-of-data-changes).
### Changes to default version [#changes-to-default-version]
If you don’t specify the version of the OTel semantic conventions that your data follows when you send OTel data to Axiom, Axiom interprets the data using the default version.
After Axiom changes the default version of OTel semantic conventions, the shape of your data can change when you don’t specify the version of the OTel semantic conventions in the data you send to Axiom. For this reason, to prevent changes to your data, include the version of the OTel semantic conventions that your data follows when you send OTel data to Axiom. This ensures that your data is properly shaped in Axiom and it won’t be affected when Axiom changes the default version.
When Axiom updates the default version of OTel semantic conventions, the shape of your data can change:
* Before the update, attributes that become supported between the old and the new default versions are nested under the `attributes.custom` field. After the update, these attributes are nested under the `attributes` or `resource` fields.
* Before the update, attributes that became deprecated between the old and the new default versions are nested under the `attributes` or `resource` fields. After the update, these attributes are nested under the `attributes.custom` field.
When the shape of your data changes, you need to [take action](#take-action-when-shape-of-data-changes).
### Migrate to new version [#migrate-to-new-version]
When you choose to migrate to a newer version of OTel semantic conventions, the shape of your data can change. Some attributes can become supported, deprecated, renamed, or relocated.
When the shape of your data changes, you need to [take action](#take-action-when-shape-of-data-changes).
### Determine changes between versions [#determine-changes-between-versions]
To determine the changes between different versions of OTel semantic conventions, compare the [schema files](https://github.com/open-telemetry/semantic-conventions/tree/main/schemas) or the [changelog](https://github.com/open-telemetry/semantic-conventions/releases) in the OTel documentation. This informs you about how the shape of your data can change as a result of semantic conventions upgrades.
## Take action when shape of data changes [#take-action-when-shape-of-data-changes]
When some attributes are relocated or renamed, the shape of your data changes and you need to take action.
For example, assume that Axiom supports OTel semantic conventions up to version 1.25. You send data to Axiom that follows version 1.32 and you don’t specify the version. On June 12th 2025, Axiom adds support for versions up to 1.32 and makes version 1.32 the default. The following happens with the attribute `db.system.name`:
* You send the attribute `db.system.name` to Axiom because your data follows version 1.32. The shape of the data you send to Axiom doesn’t change during the update.
* Before the update, Axiom interpreted your data using the old default version 1.25. It didn’t recognize `db.system.name` and nested it under `attributes.custom`.
* After the update, Axiom interprets your data using the new default version 1.32. It recognizes `db.system.name` properly and nests it under `attributes`.
As a result, the attribute is relocated from `['attributes.custom']['db.system.name']` to `['attributes.db.system.name']`. You need to update all saved queries, dashboards, or monitors that reference the attribute. You have the following options:
* [Update queries to reference the new location](#reference-new-location)
* [Update queries to reference both locations](#reference-both-locations)
### Reference new location [#reference-new-location]
When you update affected queries to reference the new location, the query results only include data you send after the semantic conventions upgrade.
For example, a saved query references the old location before the update:
```kusto
['otel-demo-traces']
| where ['service.name'] == "frontend"
| project ['attributes.custom']['db.system.name']
```
After the update, change the query to the following:
```kusto
['otel-demo-traces']
| where ['service.name'] == "frontend"
| project ['attributes.db.system.name']
```
### Reference both locations [#reference-both-locations]
To ensure that affected queries include data from the attribute that you send before and after the semantic conventions upgrade, use [coalesce](/apl/scalar-functions/string-functions#coalesce) in your query. This function evaluates a list of expressions and returns the first non-null value. In this case, pass the old and the new locations of the attribute to the `coalesce` function.
For example, a saved query references the old location before the update:
```kusto
['otel-demo-traces']
| where ['service.name'] == "frontend"
| project ['attributes.custom']['db.system.name']
```
After the update, change the query to the following:
```kusto
['otel-demo-traces']
| where ['service.name'] == "frontend"
| project coalesce(['attributes.custom']['db.system.name'], ['attributes.db.system.name'])
```
---
# Role-Based Access Control
Source: https://axiom.co/docs/reference/settings
Role-Based Access Control (RBAC) allows you to manage and restrict access to your data and resources efficiently. You can control access to your data with the following:
* [Groups](#groups)
* [Roles](#roles)
* [Users](#users)
* [Directory Sync](#directory-sync)
* [Single Sign-On (SAML SSO)](#single-sign-on-saml-sso)
Role-Based Access Control (RBAC), Directory Sync, and Single Sign-On (SAML SSO) are available as add-ons on the Axiom Cloud plan. For more information, see [Manage add-ons](/reference/usage-billing#manage-add-ons).
## Groups [#groups]
Groups connect users with roles, making it easier to manage access control at scale. For example, you can create groups for areas of your business like Security, Infrastructure, or Business Analytics, with specific roles assigned to serve the unique needs of these domains.
A user’s complete set of capabilities is derived from the additive union of their base role, plus any roles assigned through group membership.
### Create new group [#create-new-group]
1. Click **Settings > Groups**
2. Click **New group**.
3. Enter the name and description of the group.
4. Click **Add users** to add users to the group.
5. Click **Add roles** to add roles to the group.
## Roles [#roles]
Roles are sets of capabilities that define which actions a user can perform at both the organization and dataset levels.
### Default roles [#default-roles]
The default roles are the following:
* **Owner:** Assigns all capabilities across the entire Axiom platform.
* **Admin:** Assigns administrative capabilities except for Billing capabilities, which are reserved for Owners.
* **User:** Assigns standard access for regular users.
* **Read-only:** Assigns read capabilities for datasets, plus read access on various resources like dashboards, monitors, notifiers, users, queries, saved queries, and virtual fields.
* **None:** Assigns zero capabilities, useful for adopting the principle of least privilege when inviting new users. You can build up specific capabilities for these users by assigning their role to a group.
### Create custom role [#create-custom-role]
1. Ensure you have create permission for the access control capability. By default, this capability is assigned to the Owner and Admin roles.
2. Click **Settings > Roles**.
3. Click **New role**.
4. Enter the name and description of the role.
5. Assign permissions (create, read, update, and delete) across capabilities (access control, API tokens, dashboards, datasets, etc.).
### Assign capabilities to roles [#assign-capabilities-to-roles]
You can assign organization-level and dataset-level capabilities to roles. You can assign create, read, update, or delete (CRUD) permissions to most capabilities.
Organization-level capabilities define access for various parts of your Axiom organization:
* **Access control:** Full CRUD.
* **Annotations:** Full CRUD.
* **API tokens:** Full CRUD.
* **Apps:** Full CRUD.
* **Audit log:** Read only.
* **Billing:** Read and update only.
* **Dashboards:** Full CRUD.
* **Datasets:** Full CRUD.
* **Endpoints:** Full CRUD.
* **Monitors:** Full CRUD.
* **Notifiers:** Full CRUD.
* **Shared access keys:** Read and update only.
* **Users:** Full CRUD.
* **Views:** Full CRUD.
The table below describes these organization-level capabilities:
| Capability | Create | Read | Update | Delete |
| ------------------ | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| Access control | User can create custom roles and groups. | User can view the list of existing roles and groups. | User can update the and description of roles and groups, and modify permissions. | User can delete custom roles or groups. |
| Annotations | User can create annotations. | User can view the list of existing annotations in an organization. | User can modify annotations. | User can delete annotations. |
| API tokens | User can create an API token with access to the datasets their user has access to. | User can access the list of tokens that have been in their organization. | User can regenerate a token from the list of tokens in an organization. | User can delete API tokens created in their organization. |
| Apps | User can create a new app. | Users can access the list of installed apps in their organization. | Users can modify the existing apps in their organization. | User can disconnect apps installed in their organization. |
| Audit log | — | Users can access the audit log in an organization. | — | — |
| Billing | — | User can access billing settings. | User can change the organization plan. | — |
| Dashboards | User can create new dashboards. | User can access their own dashboards and those created by other users in their organization. | User can modify dashboard titles and descriptions. User can add, resize, and delete charts from dashboards. | User can delete a dashboard from their organization. |
| Datasets | User can create a new dataset. | Users can access the list of datasets in an organization, and their associated fields. | User can trim a dataset, and modify dataset fields. | User can delete a dataset from their organization. |
| Endpoints | User can create a new endpoint. | User can access the list of existing endpoints in an organization. | Users can rename an endpoint and modify which dataset data is ingested into. | User can delete an endpoint from their organization. |
| Monitors | User can create a monitor. | User can access the list of monitors in their organization. User can also review the monitor status. | Users can modify a monitor configuration in their organization. | Users can delete monitors that have been created in their organization. |
| Notifiers | User can create a new notifier in their organization. | User can access the list of notifiers in their organization. | User can update existing notifiers in their organization. User can snooze a notifier. | User can delete notifiers that have been created in their organization. |
| Shared access keys | — | User can access shared access keys in their organization. | User can update shared access keys in their organization. | — |
| Users | Users can invite new users to an organization. | User can access the list of users that are part of their organization. | User can update user roles and information within the organization. | Users can remove other users from their organization and delete their own account. |
| Views | User can create new views. | User can access the list of views in an organization in their organization. | User can modify views. | User can delete views from their organization. |
Dataset-level capabilities provide fine-grained control over access to datasets. You can assign the following capabilities for all datasets or individual datasets:
* **Data:** Delete only.
* **Ingest:** Create only.
* **Query:** Read only.
* **Share:** Create, read, and update only.
* **Saved queries:** Full CRUD.
* **Trim:** Update only.
* **Vacuum:** Update only.
* **Virtual fields:** Full CRUD.
The table below describes these dataset-level capabilities:
| Datasets | Create | Read | Update | Delete |
| -------------- | ------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------- | --------------------------------------------- |
| Data | — | — | — | — |
| Ingest | User can ingest events to datasets. | — | — | User can delete data from datasets. |
| Query | — | User can query events from datasets. | — | — |
| Share | User can share datasets. | User can access the list of shared datasets in their organization. | User can modify an existing shared dataset in their organization. | — |
| Saved queries | User can create a saved query for datasets. | User can access the list of saved queries in their organization. | User can modify an existing saved query in their organization. | User can delete a saved query from a dataset. |
| Trim | — | — | User can trim datasets. | — |
| Vacuum | — | — | User can vacuum datasets. | — |
| Virtual fields | User can create a new virtual field. | User can see the list of virtual fields. | User can modify the definition of a virtual field. | User can delete a virtual field. |
## Users [#users]
Users in Axiom are the individual accounts that have access to an Axiom organization. You assign a base role to users when you invite them to join your organization. For organizations with the role-based access control (RBAC) add-on, additional roles can be added through group membership.
### Assign roles to users [#assign-roles-to-users]
1. Click **Settings > Users**.
2. Find the user in the list, and then assign a role to them on the right.
Access for a user is the additive union of capabilities assigned through their default role, plus any capabilities included in roles assigned through group membership.
### Delete users [#delete-users]
This is a destructive action. After you delete a user, you can’t recover their account.
1. Click **Settings > Users**.
2. Find the user in the list, and then click **Delete user** on the right.
## Directory Sync [#directory-sync]
Directory Sync automatically mirrors user account data between a central directory, such as Active Directory, and connected applications. When the status of an employee changes, all systems are automatically updated.
For this feature, Axiom relies on WorkOS. For more information, see [Directory Sync](https://workos.com/directory-sync) and [Supported vendors](https://workos.com/docs/integrations) in the WorkOS documentation.
## Single Sign-On (SAML SSO) [#single-sign-on-saml-sso]
To simplify access management and enhance security, Security Assertion Markup Language-based Single Sign-On (SAML SSO) allows you to keep access grants up-to-date with support for the industry standard SCIM protocol.
Axiom supports secure, centralized user authentication through both types of flow for SAML-based SSO:
* IdP-initiated flow (identity-provider-initiated flow)
* SP-initiated flow (service-provider-initiated flow)
Two-factor authentication (2FA) is a security feature that requires users to provide two forms of identification before accessing their accounts. You can turn on 2FA for users logging in through SAML SSO and enforce it through your identity provider. Axiom doesn’t offer 2FA natively.
For this feature, Axiom relies on WorkOS:
1. Axiom provisions an organization for you in WorkOS, connects it to your Axiom organization, and turns on SSO.
2. Axiom provides you with a setup link to your WorkOS organization.
3. You follow the instructions using the setup link. The setup requires the following attributes for your users:
* `idp_id`
* `first_name`
* `last_name`
* `email`
For more information, see [Enterprise Single Sign-On](https://workos.com/single-sign-on) and [Supported vendors](https://workos.com/docs/integrations) in the WorkOS documentation.
---
# System requirements
Source: https://axiom.co/docs/reference/system-requirements
## Browsers and platforms [#browsers-and-platforms]
The Axiom web app supports the latest versions of the following browsers and platforms:
| | Android | iOS | macOS | Linux | Windows |
| :------ | :------ | :-- | :---- | :---- | :------ |
| Chrome | ✓ | ✓ | ✓ | ✓ | ✓ |
| Edge | ✓ | ✓ | ✓ | ✓ | ✓ |
| Firefox | ✓ | ✓ | ✓ | ✓ | ✓ |
| Safari | - | ✓ | ✓ | - | - |
Some actions in the Dashboards tab, such as moving dashboard elements, aren’t supported in mobile view.
## OpenTelemetry [#opentelemetry]
### Semantic conventions [#semantic-conventions]
Axiom supports the following versions of OTel semantic conventions:
| Version | Date when supported added | Schema in OTel docs |
| :--------------- | :------------------------ | :---------------------------------------------------------------------------------------- |
| 1.37.0 | 11-09-2025 | [1.37.0](https://github.com/open-telemetry/semantic-conventions/blob/main/schemas/1.37.0) |
| 1.36.0 | 11-09-2025 | [1.36.0](https://github.com/open-telemetry/semantic-conventions/blob/main/schemas/1.36.0) |
| 1.34.0 | 01-07-2025 | [1.34.0](https://github.com/open-telemetry/semantic-conventions/blob/main/schemas/1.34.0) |
| 1.33.0 (default) | 01-07-2025 | [1.33.0](https://github.com/open-telemetry/semantic-conventions/blob/main/schemas/1.33.0) |
| 1.32.0 | 12-06-2025 | [1.32.0](https://github.com/open-telemetry/semantic-conventions/blob/main/schemas/1.32.0) |
| 1.31.0 | 12-06-2025 | [1.31.0](https://github.com/open-telemetry/semantic-conventions/blob/main/schemas/1.31.0) |
| 1.30.0 | 12-06-2025 | [1.30.0](https://github.com/open-telemetry/semantic-conventions/blob/main/schemas/1.30.0) |
| 1.28.0 | 12-06-2025 | [1.28.0](https://github.com/open-telemetry/semantic-conventions/blob/main/schemas/1.28.0) |
| 1.27.0 | 12-06-2025 | [1.27.0](https://github.com/open-telemetry/semantic-conventions/blob/main/schemas/1.27.0) |
| 1.26.0 | 03-07-2024 | [1.26.0](https://github.com/open-telemetry/semantic-conventions/blob/main/schemas/1.26.0) |
| 1.25.0 | 26-04-2024 | [1.25.0](https://github.com/open-telemetry/semantic-conventions/blob/main/schemas/1.25.0) |
| 1.24.0 | 19-01-2024 | [1.24.0](https://github.com/open-telemetry/semantic-conventions/blob/main/schemas/1.24.0) |
| 1.23.1 | 26-03-2024 | [1.23.1](https://github.com/open-telemetry/semantic-conventions/blob/main/schemas/1.23.1) |
| 1.23.0 | 26-03-2024 | [1.23.0](https://github.com/open-telemetry/semantic-conventions/blob/main/schemas/1.23.0) |
| 1.22.0 | 26-03-2024 | [1.22.0](https://github.com/open-telemetry/semantic-conventions/blob/main/schemas/1.22.0) |
| 1.21.0 | 26-03-2024 | [1.21.0](https://github.com/open-telemetry/semantic-conventions/blob/main/schemas/1.21.0) |
Version 1.29.0 and version 1.35.0 of OTel semantic conventions aren’t supported.
For more information, see [Semantic conventions](/reference/semantic-conventions).
### Logs, traces, and metrics [#logs-traces-and-metrics]
| OpenTelemetry component | Support |
| ------------------------------------------------------------------ | ------- |
| [Logs](https://opentelemetry.io/docs/concepts/signals/logs/) | ✓ |
| [Traces](https://opentelemetry.io/docs/concepts/signals/traces/) | ✓ |
| [Metrics](https://opentelemetry.io/docs/concepts/signals/metrics/) | ✓ |
---
# Authenticate API requests with tokens
Source: https://axiom.co/docs/reference/tokens
This reference article explains how you can authenticate your requests to the Axiom API with tokens.
## Why authenticate with tokens [#why-authenticate-with-tokens]
You can use the Axiom API and CLI to programmatically ingest and query data, and manage settings and resources. For example, you can create new API tokens and change existing datasets with API requests. To prove that these requests come from you, you must include forms of authentication called tokens in your API requests. Axiom offers two types of tokens:
* [API tokens](#api-tokens) let you control the actions that can be performed with the token. For example, you can specify that requests authenticated with a certain API token can only query data from a particular dataset.
* [Personal access tokens (PATs)](#personal-access-tokens-pat) provide full control over your Axiom account. Requests authenticated with a PAT can perform every action you can perform in Axiom. When possible, use API tokens instead of PATs.
Keep tokens confidential. Anyone with these forms of authentication can perform actions on your behalf such as sending data to your Axiom dataset.
When working with tokens, use the principle of least privilege:
* Assign only those privileges to API tokens that are necessary to perform the actions that you want.
* When possible, use API tokens instead of PATs because PATs have full control over your Axiom account.
For more information on how to use tokens in API requests, see [Get started with Axiom API](/restapi/introduction).
## API tokens [#api-tokens]
You can use two types of API tokens in Axiom:
* Basic API tokens let you ingest data to Axiom. When you create a basic API token, you select the datasets that you allow the basic API token to access.
* Advanced API tokens let you perform a wide range of actions in Axiom beyond ingesting data. When you create an advanced API token, you select which actions you allow the advanced API token to perform. For example, you can create an advanced API token that can only query data from a particular dataset and another that has wider privileges such as creating datasets and changing existing monitors.
After creating an API token, you can’t change the privileges assigned to that API token.
### Create basic API token [#create-basic-api-token]
1. Click **Settings > API tokens**, and then click **New API token**.
2. Name your API token.
3. Optional: Give a description to the API token and set an expiration date.
4. In **Token permissions**, click **Basic**.
5. In **Dataset access**, select the datasets where this token can ingest data.
6. Click **Create**.
7. Copy the API token that appears and store it securely. It won’t be displayed again.
### Create advanced API token [#create-advanced-api-token]
1. Click **Settings > API tokens**, and then click **New API token**.
2. Name your API token.
3. Optional: Give a description to the API token and set an expiration date.
4. In **Token permissions**, click **Advanced**.
5. In **Advanced permissions**, choose one of the following options:
* Select **Custom** and define your own permission set. This allows you to select the datasets that this token can access and the actions it can perform. In **Org level permissions**, you can select the actions the token can perform that affect your whole Axiom organisation. For example, creating users and changing existing notifiers.
* Select one of the presets to auto-populate a predefined set of capabilities. For example, **Preset - Evaluations** allows an SDK to write evaluation traces and query comparison data.
6. Click **Create**.
7. Copy the API token that appears and store it securely. It won’t be displayed again.
### Regenerate API token [#regenerate-api-token]
Similarly to passwords, it’s recommended to change API tokens regularly and to set an expiration date after which the token becomes invalid. When a token expires, you can regenerate it.
To regenerate an advanced API token, follow these steps:
1. Click **Settings > API tokens**.
2. In the list, select the API token you want to regenerate.
3. Click **Regenerate token**.
4. Copy the regenerated API token that appears and store it securely. It won’t be displayed again.
5. Update all the API requests where you use the API token with the regenerated token.
### Delete API token [#delete-api-token]
1. Click **Settings > API tokens**.
2. In the list, hold the pointer over the API token you want to delete.
3. To the right, click **Delete**.
## Personal access tokens (PAT) [#personal-access-tokens-pat]
Personal access tokens (PATs) provide full control over your Axiom account. Requests authenticated with a PAT can perform every action you can perform in Axiom. When possible, use API tokens instead of PATs.
### Create PAT [#create-pat]
1. Click **Settings > Profile**.
2. In the **Personal tokens** section, click **New token**.
3. Name the PAT.
4. Optional: Give a description to the PAT.
5. Copy the PAT that appears and store it securely. It wont be displayed again.
### Delete PAT [#delete-pat]
1. Click **Settings > Profile**.
2. In the list, find the PAT that you want to delete.
3. To the right of the PAT, click **Delete**.
## Determine organization ID [#determine-organization-id]
If you authenticate requests with a PAT, you must include the organization ID in the requests. For more information on including the organization ID in the request, see [Axiom API](/restapi/introduction) and [Axiom CLI](/reference/cli).
Determine the organization ID in one of the following ways:
* Click **Settings**, and then copy the organization ID in the top right corner.
* Click **Settings > General**, and then find the **ID** section.
* Go to the [Axiom app](https://app.axiom.co/) and check the URL. For example, in the URL `https://app.axiom.co/axiom-abcd/datasets`, the organization ID is `axiom-abcd`.
---
# Usage and billing
Source: https://axiom.co/docs/reference/usage-billing
## Billing [#billing]
To view details about your next and last bill, go to **Settings > Billing**.
The Current breakdown section explains what you paid for in your last bill and what you can expect to pay for in your next bill.
### View billing history [#view-billing-history]
To view details about your billing history:
1. Go to **Settings > Billing**.
2. In the Current breakdown section, click **Billing history**.
### Compute credits [#compute-credits]
You can save on compute spending by pre-purchasing compute credits. This means that you purchase credits in advance at a discount. The more credits you purchase, the higher the discount rate. For more information, see [Pricing](https://axiom.co/pricing).
Your compute credits can cover the following costs:
* Data loading
* Query compute
Your compute credits can’t cover the following costs:
* Your pricing plan
* Add-ons
* Storage
To purchase compute credits:
1. Ensure you have permissions to modify billing. For more information, see [Role-Based Access Control](/reference/settings).
2. Go to **Settings > Billing**.
3. In the Compute credits section, click **Add credits**.
4. Enter the amount of credits you want to purchase.
5. Review the total cost.
6. Click **Buy credits**.
7. Complete the purchase.
### Spending limit [#spending-limit]
Setting a monthly spending limit allows you to control costs.
Axiom continuously tracks your current billing cycle’s projected costs. To determine your accumulated charges, Axiom considers all costs, including the base cost of your plan, usage costs, and the costs of any add-ons you have purchased. When your accumulated charges reach your spending limit, Axiom automatically pauses actions that generate further costs. This can cause interruptions to your service.
For example, if you have used up the query compute allowance included in your pricing plan, you can’t run queries and your monitors can’t query your data. At the same time, if you haven’t yet used up the data loading allowance, you can still ingest data.
Even if you reach your spending limit, you can still initiate purchases manually such as buying add-ons and credits.
To set a monthly spending limit:
1. Ensure you have permissions to modify billing. For more information, see [Role-Based Access Control](/reference/settings).
2. Go to **Settings > Billing**.
3. In the Limit section, click **Manage limit**.
4. Turn on **Set monthly spending limit**.
5. Enter the maximum monthly spending.
6. Click **Save changes**.
If your organization reaches the spend limit, Axiom sends an email to the owner of your organization. To avoid further interruptions to your service, increase the spend limit. Your service resumes until usage-related costs reach the amount by which you increased the limit. The spend limit resets in the next billable period as usual.
## Pricing plan [#pricing-plan]
### View plan details [#view-plan-details]
To view details about your current plan:
1. Go to **Settings > Plan**.
2. Click **License**.
This page gives you an overview of the allowances included in your plan and your current monthly usage.
### Upgrade or downgrade plan [#upgrade-or-downgrade-plan]
To upgrade or downgrade your plan:
1. Ensure you have permissions to modify billing. For more information, see [Role-Based Access Control](/reference/settings).
2. Go to **Settings > Plan**.
3. Click **More > Upgrade** or **More > Downgrade**.
### Manage add-ons [#manage-add-ons]
You can manage the following add-ons:
* Additional edge deployments
* [Audit log](/reference/audit-log)
* [Directory Sync](/reference/settings#directory-sync)
* [Role-Based Access Control (RBAC)](/reference/settings)
* [SAML Single Sign-On (SSO)](/reference/settings#single-sign-on-saml-sso)
To manage add-ons:
1. Ensure you have permissions to modify billing. For more information, see [Role-Based Access Control](/reference/settings).
2. Go to **Settings > Plan**.
3. Click the add-on area.
4. Select or deselect add-ons.
5. Review the updated base monthly cost.
6. Click **Update add-ons**.
## Usage [#usage]
To view details about your organization’s total usage of Axiom:
1. Click **Settings > Usage**.
2. To select the period for which you want to display usage, click **Time range**.
The Usage page displays:
* **Ingest**: The total amount of data ingested to Axiom.
* **Query**: The total amount of query compute resources used to run queries.
* **Fields**: The number of fields in each of your datasets.
### Monitor usage in detail [#monitor-usage-in-detail]
For more granular usage monitoring, query the [audit log](/reference/audit-log). The audit log captures hourly usage data and allows you to:
* Track ingestion by dataset over time.
* Identify high-cost queries.
* Monitor which monitors consume the most query resources.
* Detect ingestion spikes.
---
# LLM observability
Source: https://axiom.co/docs/use-cases/llm-observability
Axiom provides first-class observability for generative AI applications. Instrument your app using OpenTelemetry and Axiom automatically provisions a GenAI dashboard and waterfall trace view for every dataset containing AI telemetry.
## Instrument your app [#instrument-your-app]
## Visualize traces in Console [#visualize-traces-in-console]
Visualizing and making sense of this telemetry data is a core part of the Axiom Console experience:
* A dedicated **AI traces waterfall view** visualizes single and multi-step LLM workflows, with clear input/output inspection at each stage.
* A pre-built **GenAI OTel dashboard** automatically appears for any dataset receiving AI telemetry. It features elements for tracking cost per invocation, time-to-first-token, call counts by model, and error rates.
### Access AI traces waterfall view [#access-ai-traces-waterfall-view]
1. Click the Query tab.
2. Create an APL query about your GenAI dataset. For example:
```kusto
['otel-demo-genai']
| where ['attributes.gen_ai.operation.name'] == "chat"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-genai'%5D%20%7C%20where%20%5B'attributes.gen_ai.operation.name'%5D%20%3D%3D%20'chat'%22%7D)
3. In the list of trace IDs, click the trace you want to explore.
4. Explore how spans within the trace are related to each other in the waterfall view. To only display AI spans, click **AI spans** in the top left.
### Access GenAI dashboard [#access-genai-dashboard]
Axiom automatically creates the GenAI dashboard if the field `attributes.gen_ai.operation.name` is present in your data.
To access the GenAI dashboard:
1. Click the Dashboards tab.
2. Click the dashboard **Generative AI Overview (DATASET\_NAME)** where `DATASET_NAME` is the name of your GenAI dataset.
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/dashboards/genai.otel-demo-genai)
The GenAI dashboard provides you with important insights about your GenAI app such as:
* Vitals about requests, broken down by operation, capability, and step.
* Token usage and cost analysis
* Error analysis
* Comparison of performance and reliability of different AI models
## What’s next? [#whats-next]
* Use [GenAI APL functions](/apl/scalar-functions/genai-functions) to query and analyze your LLM data.
* See [GenAI attributes](/use-cases/llm-observability/gen-ai-attributes) for the full list of OpenTelemetry attributes Axiom recognizes.
---
# Send data from Amazon Data Firehose to Axiom
Source: https://axiom.co/docs/send-data/aws-firehose
Amazon Data Firehose is a service for delivering real-time streaming data to different destinations. Send event data from Amazon Data Firehose to Axiom to analyse and monitor your data efficiently.
* [Create an account on AWS Cloud](https://signin.aws.amazon.com/signup?request_type=register).
## Setup [#setup]
1. In Axiom, determine the ID of the dataset you’ve created.
2. In Amazon Data Firehose, create an HTTP endpoint destination. For more information, see the [Amazon Data Firehose documentation](https://docs.aws.amazon.com/firehose/latest/dev/create-destination.html#create-destination-http).
3. Set HTTP endpoint URL to `https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME/firehose`.
4. Set the access key to the Axiom API token.
You have configured Amazon Data Firehose to send data to Axiom. Go to the Axiom UI and ensure your dataset receives events properly.
---
# Send data from AWS FireLens to Axiom
Source: https://axiom.co/docs/send-data/aws-firelens
AWS FireLens is a log routing feature for Amazon ECS. It lets you use popular open-source logging projects [Fluent Bit](https://fluentbit.io/) or [Fluentd](https://www.fluentd.org/) with Amazon ECS to route your logs to various AWS and partner monitoring solutions like Axiom without installing third-party agents on your tasks.
FireLens integrates with your Amazon ECS tasks and services seamlessly, so you can send logs from your containers to Axiom seamlessly.
## Use AWS FireLens with Fluent Bit and Axiom [#use-aws-firelens-with-fluent-bit-and-axiom]
Here’s a basic configuration for using FireLens with Fluent Bit to forward logs to Axiom:
## Fluent Bit configuration for Axiom [#fluent-bit-configuration-for-axiom]
You’ll typically define this in a file called `fluent-bit.conf`:
```ini
[SERVICE]
Log_Level info
[INPUT]
Name forward
Listen 0.0.0.0
Port 24224
[OUTPUT]
Name http
Match *
Host AXIOM_DOMAIN
Port 443
URI /v1/ingest/DATASET_NAME
Format json_lines
tls On
format json
json_date_key _time
json_date_format iso8601
Header Authorization Bearer API_TOKEN
```
Read more about [Fluent Bit configuration here](/send-data/fluent-bit)
## ECS task definition with FireLens [#ecs-task-definition-with-firelens]
You’ll want to include this within your ECS task definition, and reference the FireLens configuration type and options:
```json
{
"family": "myTaskDefinition",
"containerDefinitions": [
{
"name": "log_router",
"image": "amazon/aws-for-fluent-bit:latest",
"essential": true,
"firelensConfiguration": {
"type": "fluentbit",
"options": {
"config-file-type": "file",
"config-file-value": "/fluent-bit/etc/fluent-bit.conf"
}
}
},
{
"name": "myApp",
"image": "my-app-image",
"logConfiguration": {
"logDriver": "awsfirelens"
}
}
]
}
```
## Use AWS FireLens with Fluentd and Axiom [#use-aws-firelens-with-fluentd-and-axiom]
Create the `fluentd.conf` file and add your configuration:
```bash
@type forward
port 24224
bind 0.0.0.0
@type http
headers {"Authorization": "Bearer API_TOKEN"}
data_type json
endpoint https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME
sourcetype ecs
```
Read more about [Fluentd configuration here](/send-data/fluentd)
## ECS Task Definition for Fluentd [#ecs-task-definition-for-fluentd]
The task definition would be similar to the Fluent Bit example, but using Fluentd and its configuration:
```json
{
"family": "fluentdTaskDefinition",
"containerDefinitions": [
{
"name": "log_router",
"image": "YOUR_ECR_REPO_URI:latest",
"essential": true,
"memory": 512,
"cpu": 256,
"firelensConfiguration": {
"type": "fluentd",
"options": {
"config-file-type": "file",
"config-file-value": "/path/to/your/fluentd.conf"
}
}
},
{
"name": "myApp",
"image": "my-app-image",
"essential": true,
"memory": 512,
"cpu": 256,
"logConfiguration": {
"logDriver": "awsfirelens",
"options": {
"Name": "forward",
"Host": "log_router",
"Port": "24224"
}
}
}
]
}
```
By efficiently routing logs with FireLens and analyzing them with Axiom, businesses and development teams can save on operational overheads and reduce time spent on troubleshooting.
---
# Send data from AWS IoT to Axiom
Source: https://axiom.co/docs/send-data/aws-iot-rules
* Create an AWS account with permissions to create and manage IoT rules, Lambda functions, and IAM roles.
## Create AWS Lambda function [#create-aws-lambda-function]
Create a Lambda function with Python runtime and the following content. For more information, see the [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/getting-started.html#getting-started-create-function). The Lambda function acts as an intermediary to process data from AWS IoT and send it to Axiom.
```python
import os # Import the os module to access environment variables
import json # Import the json module to handle JSON data
import requests # Import the requests module to make HTTP requests
def lambda_handler(event, context):
# Retrieve the dataset name and the Axiom domain from the environment variables
dataset_name = os.environ['DATASET_NAME']
axiom_domain = os.environ['AXIOM_DOMAIN']
# Construct the Axiom API URL using the dataset name
axiom_api_url = f"https://{axiom_domain}/v1/ingest/{dataset_name}"
# Retrieve the Axiom API token from the environment variable
api_token = os.environ['API_TOKEN']
# Define the headers for the HTTP request to Axiom
headers = {
"Authorization": f"Bearer {api_token}", # Set the Authorization header with the token
"Content-Type": "application/json", # Specify the content type as JSON
"X-Axiom-Dataset": dataset_name # Include the dataset name in the headers
}
# Create the payload for the HTTP request
payload = {
"tags": {"source": "aws-iot"}, # Add a tag to indicate the source of the data
"events": [{"timestamp": event['timestamp'], "attributes": event}] # Include the event data
}
# Send a POST request to the Axiom API with the headers and payload
response = requests.post(axiom_api_url, headers=headers, data=json.dumps(payload))
# Return the status code and a confirmation message
return {
'statusCode': response.status_code, # Return the HTTP status code from the Axiom API response
'body': json.dumps('Log sent to Axiom!') # Return a confirmation message as JSON
}
```
In the environment variables section of the Lambda function configuration, add the following environment variables:
This example uses Python for the Lambda function. To use another language, change the code above accordingly.
## Create AWS IoT rule [#create-aws-iot-rule]
Create an IoT rule with an SQL statement similar to the example below that matches the MQTT messages. For more information, see the [AWS documentation](https://docs.aws.amazon.com/iot/latest/developerguide/iot-create-rule.html).
```sql
SELECT * FROM 'iot/topic'
```
In **Rule actions**, select the action to send a message to a Lambda function, and then choose the Lambda function you created earlier.
## Check logs in Axiom [#check-logs-in-axiom]
Use the AWS IoT Console, AWS CLI, or an MQTT client to publish messages to the topic that matches your rule. For example, `iot/topic`.
In Axiom, go to the Stream tab and select the dataset you specified in the Lambda function. You now see your logs from your IoT devices in Axiom.
---
# Send data from AWS to Axiom using AWS Distro for OpenTelemetry
Source: https://axiom.co/docs/send-data/aws-lambda-dot
This page explains how to auto-instrument and monitor applications running on AWS Lambda using the AWS Distro for OpenTelemetry (ADOT). ADOT is an OpenTelemetry collector layer managed by and optimized for AWS.
Alternatively, you can use the Axiom Lambda Extension to send Lambda function logs and platform events to Axiom. For more information, see [AWS Lambda](/send-data/aws-lambda).
Axiom detects the extension and provides you with quick filters and a dashboard. For more information on how this enriches your Axiom organization, see [AWS Lambda app](/apps/lambda).
## ADOT Lambda collector layer [#adot-lambda-collector-layer]
[AWS Distro for OpenTelemetry Lambda](https://aws-otel.github.io/docs/getting-started/lambda) provides a plug-and-play user experience by automatically instrumenting a Lambda function. It packages OpenTelemetry together with an out-of-the-box configuration for AWS Lambda and OTLP in an easy-to-setup layer. You can turn on and off OpenTelemetry for your Lambda function without changing your code.
With the ADOT collector layer, you can send telemetry data to Axiom with a simple configuration.
## Set up ADOT Lambda layer [#set-up-adot-lambda-layer]
This example creates a new Lambda function and applies the ADOT Lambda layer to it with the proper configuration.
You can deploy your Lambda function with the choice of your runtime. This example uses the Python3.10 runtime.
Create a new Lambda function with the following content. For more information on creating Lambda functions, see the [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/getting-started.html).
```python
import json
print('Loading function')
def lambda_handler(event, context):
#print("Received event: " + json.dumps(event, indent=2))
print("value1 = " + event['key1'])
print("value2 = " + event['key2'])
print("value3 = " + event['key3'])
return event['key1'] # Echo back the first key value
#raise Exception('Something went wrong')
```
Add a new ADOT Lambda layer to your function with the following ARN (Amazon Resource Name). For more information on adding layers to your function, see the [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/adding-layers.html).
```bash
arn:aws:lambda:AWS_REGION:901920570463:layer:aws-otel-python-ARCH-VERSION
```
* Replace `AWS_REGION` with the AWS Region to send the request to. For example, `us-west-1`.
* Replace `ARCH` with the system architecture type. For example, `arm64`.
* Replace `VERSION` with the latest version number specified in the [AWS documentation](https://aws-otel.github.io/docs/getting-started/lambda/lambda-python). For example, `ver-1-25-0:1`.
The configuration file is a YAML file that contains the configuration for the OpenTelemetry collector. Create the configuration file `/var/task/collector.yaml` with the following content. This tells the collector to receive telemetry data from the OTLP receiver and export it to Axiom.
```yaml
receivers:
otlp:
protocols:
grpc:
http:
exporters:
otlphttp:
compression: zstd
endpoint: https://AXIOM_DOMAIN
headers:
authorization: Bearer API_TOKEN
x-axiom-dataset: DATASET_NAME
service:
pipelines:
logs:
receivers: [otlp]
exporters: [otlphttp]
traces:
receivers: [otlp]
exporters: [otlphttp]
```
Set the following environment variables. For more information on setting environment variables, see the [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html).
```bash
AWS_LAMBDA_EXEC_WRAPPER: /opt/otel-instrument
OPENTELEMETRY_COLLECTOR_CONFIG_FILE: /var/task/collector.yaml
```
* `AWS_LAMBDA_EXEC_WRAPPER` wraps the function handler with the OpenTelemetry Lambda wrapper. This layer enables the auto-instrumentation for your Lambda function by initializing the OpenTelemetry agent and handling the lifecycle of spans.
* `OPENTELEMETRY_COLLECTOR_CONFIG_FILE` specified the location of the collector configuration file.
As the app runs, it sends traces to Axiom. To view the traces:
1. In Axiom, click the **Stream** tab.
2. Click your dataset.
---
# Send data from AWS Lambda to Axiom
Source: https://axiom.co/docs/send-data/aws-lambda
Use the Axiom Lambda Extension to send logs and platform events of your Lambda function to Axiom.
Alternatively, you can use the AWS Distro for OpenTelemetry to send Lambda function logs and platform events to Axiom. For more information, see [AWS Lambda Using OTel](/send-data/aws-lambda-dot).
Axiom detects the extension and provides you with quick filters and a dashboard. For more information on how this enriches your Axiom organization, see [AWS Lambda app](/apps/lambda).
The Axiom Lambda Extension is an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/axiom-lambda-extension).
* [Create an account on AWS Cloud](https://signin.aws.amazon.com/signup?request_type=register).
## Setup [#setup]
1. [Install the Axiom Lambda extension](#installation).
2. Ensure everything works properly in Axiom.
3. [Turn off the permissions for Amazon CloudWatch](#turn-off-cloudwatch-logging).
The last step is important because after you install the Axiom Lambda extension, the Lambda service still sends logs to Amazon CloudWatch Logs. You need to manually turn off Amazon CloudWatch logging.
## Installation [#installation]
To install the Axiom Lambda Extension, choose one of the following methods:
* [AWS CLI](#install-with-aws-cli)
* [Terraform](#install-with-terraform)
* [AWS Lambda function UI](#install-with-aws-lambda-function-ui)
The layer `VERSION` in the ARN isn’t the same as the extension release version on the [GitHub Releases](https://github.com/axiomhq/axiom-lambda-extension/releases) page. AWS assigns layer version numbers automatically, one per publish, so the two numbering schemes differ. For example, extension release v16 is published as layer version `17` in all supported regions. After you install or upgrade, [verify the extension version](#verify-the-extension-version) that your functions run.
### Install with AWS CLI [#install-with-aws-cli]
Add the extension as a layer with the AWS CLI:
```bash
aws lambda update-function-configuration --function-name my-function \
--layers arn:aws:lambda:AWS_REGION:694952825951:layer:axiom-extension-ARCH:VERSION
```
* Replace `AWS_REGION` with the AWS Region to send the request to. For example, `us-west-1`.
* Replace `ARCH` with the system architecture type. For example, `arm64`.
* Replace `VERSION` with the latest Lambda layer version. For example, `17`, which contains extension release v16. The layer version doesn’t match the release version on the [GitHub Releases](https://github.com/axiomhq/axiom-lambda-extension/releases) page because AWS assigns layer version numbers automatically.
Add the Axiom dataset name and API token to the list of environment variables. For more information on setting environment variables, see the [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html).
```bash
AXIOM_TOKEN: API_TOKEN
AXIOM_DATASET: DATASET_NAME
AXIOM_URL: AXIOM_DOMAIN
```
You have installed the Axiom Lambda Extension. Go to the Axiom UI and ensure your dataset receives events properly.
### Install with Terraform [#install-with-terraform]
Choose one of the following to install the Axiom Lambda Extension with Terraform:
* Use plain Terraform code
```tf
resource "aws_lambda_function" "test_lambda" {
filename = "lambda_function_payload.zip"
function_name = "lambda_function_name"
role = aws_iam_role.iam_for_lambda.arn
handler = "index.test"
runtime = "nodejs14.x"
ephemeral_storage {
size = 10240 # Min 512 MB and the Max 10240 MB
}
environment {
variables = {
AXIOM_TOKEN = "API_TOKEN"
AXIOM_DATASET = "DATASET_NAME"
AXIOM_URL = "AXIOM_DOMAIN"
}
}
layers = [
"arn:aws:lambda:AWS_REGION:694952825951:layer:axiom-extension-ARCH:VERSION"
]
}
```
Replace `AWS_REGION` with the AWS Region to send the request to. For example, `us-west-1`.
Replace `ARCH` with the system architecture type. For example, `arm64`.
Replace `VERSION` with the latest Lambda layer version. For example, `17`, which contains extension release v16. The layer version doesn’t match the release version on the [GitHub Releases](https://github.com/axiomhq/axiom-lambda-extension/releases) page because AWS assigns layer version numbers automatically.
* Use the [AWS Lambda Terraform module](https://registry.terraform.io/modules/terraform-aws-modules/lambda/aws/latest)
```tf
module "lambda_function" {
source = "terraform-aws-modules/lambda/aws"
function_name = "my-lambda1"
description = "My awesome lambda function"
handler = "index.lambda_handler"
runtime = "python3.8"
source_path = "../src/lambda-function1"
layers = [
"arn:aws:lambda:AWS_REGION:694952825951:layer:axiom-extension-ARCH:VERSION"
]
environment_variables = {
AXIOM_TOKEN = "API_TOKEN"
AXIOM_DATASET = "DATASET_NAME"
AXIOM_URL = "AXIOM_DOMAIN"
}
}
```
Replace `AWS_REGION` with the AWS Region to send the request to. For example, `us-west-1`.
Replace `ARCH` with the system architecture type. For example, `arm64`.
Replace `VERSION` with the latest Lambda layer version. For example, `17`, which contains extension release v16. The layer version doesn’t match the release version on the [GitHub Releases](https://github.com/axiomhq/axiom-lambda-extension/releases) page because AWS assigns layer version numbers automatically.
You have installed the Axiom Lambda Extension. Go to the Axiom UI and ensure your dataset receives events properly.
### Install with AWS Lambda function UI [#install-with-aws-lambda-function-ui]
Add a new layer to your Lambda function with the following ARN (Amazon Resource Name). For more information on adding layers to your function, see the [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/adding-layers.html).
```bash
arn:aws:lambda:AWS_REGION:694952825951:layer:axiom-extension-ARCH:VERSION
```
Replace `AWS_REGION` with the AWS Region to send the request to. For example, `us-west-1`.
Replace `ARCH` with the system architecture type. For example, `arm64`.
Replace `VERSION` with the latest Lambda layer version. For example, `17`, which contains extension release v16. The layer version doesn’t match the release version on the [GitHub Releases](https://github.com/axiomhq/axiom-lambda-extension/releases) page because AWS assigns layer version numbers automatically.
Add the Axiom dataset name and API token to the list of environment variables. For more information on setting environment variables, see the [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html).
```bash
AXIOM_TOKEN: API_TOKEN
AXIOM_DATASET: DATASET_NAME
```
You have installed the Axiom Lambda Extension. Go to the Axiom UI and ensure your dataset receives events properly.
## Verify the extension version [#verify-the-extension-version]
The extension stamps its release version on every event it sends to Axiom. After you install or upgrade, check which release your functions actually run:
```kusto
['DATASET_NAME']
| where isnotnull(['axiom.awsLambdaExtensionVersion'])
| summarize count() by ['axiom.awsLambdaExtensionVersion']
```
The result is the extension release version, such as `v16`. If the result shows an older release than you expect, the layer version in your function configuration points to an older build. Update the layer ARN to the latest layer version, and then redeploy your function.
## Turn off Amazon CloudWatch logging [#turn-off-amazon-cloudwatch-logging]
After you install the Axiom Lambda extension, the Lambda service still sends logs to CloudWatch Logs. You need to manually turn off Amazon CloudWatch logging.
To turn off Amazon CloudWatch logging, deny the Lambda function access to Amazon CloudWatch by editing the permissions:
1. In the AWS Lambda function UI, go to **Configuration > Permissions**.
2. In the **Execution role** section, click the role related to Amazon CloudWatch Logs.
3. In the **Permissions** tab, select the role, and then click **Remove**.
### Requirements for log level fields [#requirements-for-log-level-fields]
The Stream and Query tabs allow you to easily detect warnings and errors in your logs by highlighting the severity of log entries in different colors. As a prerequisite, specify the log level in the data you send to Axiom. For Open Telemetry logs, specify the log level in the following fields:
* `record.error`
* `record.level`
* `record.severity`
* `type`
## Best practices for production workloads [#best-practices-for-production-workloads]
The Axiom Lambda Extension runs inside your Lambda execution environment and shares its CPU, memory, and network with your function. It favors the performance of your function over guaranteed log delivery: it buffers events in memory and, when Axiom is unreachable for a sustained period, drops the oldest buffered events. The buffer only lives as long as the execution environment. For low-to-medium volume workloads that tolerate occasional loss, sending directly from the extension to Axiom works well.
For high-volume, high-concurrency, or delivery-critical workloads, don’t send directly to Axiom from your functions. Decouple your functions from Axiom with a durable collector that you run on separate infrastructure. Your functions hand logs to a nearby collector and return immediately, and the collector owns buffering, batching, backpressure, and retries before forwarding to Axiom. This keeps your function durations predictable and prevents log loss when Axiom is briefly unreachable.
### Forward to a collector you manage (recommended) [#forward-to-a-collector-you-manage-recommended]
Run a durable log pipeline such as Vector or the OpenTelemetry Collector on always-on infrastructure you manage, such as Amazon ECS or EC2. The collector forwards to Axiom: Vector through its native [Axiom sink](/send-data/vector), and the OpenTelemetry Collector over OTLP, which Axiom ingests [natively](/send-data/opentelemetry). The sizing and operation of the collector are up to you.
Get logs from your functions to this collector in one of the following ways:
* **Axiom Lambda Extension.** Set the `AXIOM_URL` environment variable to your collector’s endpoint. The extension sends gzip-compressed NDJSON to the `/v1/datasets/DATASET_NAME/ingest` path of that URL, so any intermediary that accepts the [Axiom ingest API](/restapi/ingest) and forwards to Axiom works.
* **OpenTelemetry Lambda layer.** Use the [OpenTelemetry Lambda layer](https://github.com/open-telemetry/opentelemetry-lambda/blob/main/collector/README.md) as a replacement for the Axiom Lambda Extension, and configure its exporter to forward to your collector. For more information, see [Send OpenTelemetry data to Axiom](/send-data/opentelemetry).
### Use Amazon CloudWatch as the buffer [#use-amazon-cloudwatch-as-the-buffer]
If you prefer not to run collector infrastructure, use AWS-native tooling to decouple delivery. Keep CloudWatch logging turned on (skip the [turn-off step](#turn-off-amazon-cloudwatch-logging) above) and ship logs with the [Axiom CloudWatch Forwarder](/send-data/cloudwatch). CloudWatch durably stores the logs, and the forwarder delivers them to Axiom fully decoupled from your functions, at the cost of CloudWatch pricing and some added latency.
## Troubleshooting [#troubleshooting]
* Ensure the Axiom API token has permission to ingest data into the dataset.
* Check the function logs on the AWS console. The Axiom Lambda Extension logs any errors with setup or ingest.
For testing purposes, set the `PANIC_ON_API_ERR` environment variable to `true`. This means that the Axiom Lambda Extension crashes if it can’t connect to Axiom.
---
# Send data from AWS to Axiom
Source: https://axiom.co/docs/send-data/aws-overview
For most AWS services, the fastest and easiest way to send logs to Axiom is the [Axiom CloudWatch Forwarder](/send-data/cloudwatch). It’s subscribed to one or more of your CloudWatch Log Groups and runs as a Lambda function. To determine which AWS service sends logs to Amazon CloudWatch and/or Amazon S3, see the [AWS Documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AWS-logs-and-resource-policy.html).
## Choose the best method to send data [#choose-the-best-method-to-send-data]
To choose the best method to send data from AWS services to Axiom, consider that Amazon CloudWatch Logs captures three main types of logs:
* **Service logs**: More than 30 AWS services, including Amazon API Gateway, AWS Lambda, AWS CloudTrail, can send service logs to CloudWatch.
* **Vended logs**: Automatically published by certain AWS services like Amazon VPC and Amazon Route 53.
* **Custom logs**: Logs from your own applications, on-premise resources, and other clouds.
You can only send vended logs to Axiom through Amazon CloudWatch. Use the [Axiom CloudWatch Forwarder](/send-data/cloudwatch) to send vended logs from Amazon CloudWatch to Axiom for richer insights. After sending vended logs to Axiom, shorten the retention period for these logs in Amazon CloudWatch to cut costs even more.
For service logs and custom logs, you can skip Amazon CloudWatch altogether and send them to Axiom using open-source collectors like [Fluent Bit](/send-data/fluent-bit), [Fluentd](/send-data/fluentd) and [Vector](/send-data/vector). Completely bypassing Amazon CloudWatch results in significant cost savings.
## Amazon services exclusively supported by Axiom CloudWatch Forwarder [#amazon-services-exclusively-supported-by-axiom-cloudwatch-forwarder]
To send data from the following Amazon services to Axiom, use the [Axiom CloudWatch Forwarder](/send-data/cloudwatch).
* Amazon API Gateway
* Amazon Aurora MySQL
* Amazon Chime
* Amazon CloudWatch
* Amazon CodeWhisperer
* Amazon Cognito
* Amazon Connect
* AWS AppSync
* AWS Elastic Beanstalk
* AWS CloudHSM
* AWS CloudTrail
* AWS CodeBuild
* AWS DataSync
* AWS Elemental MediaTailor
* AWS Fargate
* AWS Glue
To send evaluation event logs from Amazon CloudWatch to Axiom, you can also use [Amazon Data Firehose](/send-data/aws-firehose).
## Amazon services supported by other methods [#amazon-services-supported-by-other-methods]
The table below summarizes the methods you can use to send data from the other supported Amazon services to Axiom.
| Supported Amazon service | Supported methods to send data to Axiom |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Amazon Bedrock | [Axiom CloudWatch Forwarder](/send-data/cloudwatch) [AWS S3 Forwarder](/send-data/aws-s3) [Amazon Data Firehose](/send-data/aws-firehose) |
| Amazon CloudFront | [AWS S3 Forwarder](/send-data/aws-s3) |
| Amazon Data Firehose | [Amazon Data Firehose](/send-data/aws-firehose) |
| Amazon Elastic Container Service | [Fluentbit](/send-data/fluent-bit) |
| Amazon Elastic Load Balancing (ELB) | [Fluentbit](/send-data/fluent-bit) |
| Amazon ElastiCache (Redis OSS) | [Axiom CloudWatch Forwarder](/send-data/cloudwatch) [Amazon Data Firehose](/send-data/aws-firehose) |
| Amazon EventBridge Pipes | [Axiom CloudWatch Forwarder](/send-data/cloudwatch) [AWS S3 Forwarder](/send-data/aws-s3) [Amazon Data Firehose](/send-data/aws-firehose) |
| Amazon FinSpace | [Axiom CloudWatch Forwarder](/send-data/cloudwatch) [AWS S3 Forwarder](/send-data/aws-s3) [Amazon Data Firehose](/send-data/aws-firehose) |
| Amazon S3 | [AWS S3 Forwarder](/send-data/aws-s3) [Vector](/send-data/vector) |
| Amazon Virtual Private Cloud (VPC) | [AWS S3 Forwarder](/send-data/aws-s3) |
| AWS Fault Injection Service | [AWS S3 Forwarder](/send-data/aws-s3) |
| AWS FireLens | [AWS FireLens](/send-data/aws-firelens) |
| AWS Global Accelerator | [AWS S3 Forwarder](/send-data/aws-s3) |
| AWS IoT Core | [AWS IoT](/send-data/aws-iot-rules) |
| AWS Lambda | [AWS Lambda](/send-data/aws-lambda) |
To request support for AWS services not listed above, please [reach out to Axiom](https://axiom.co/contact).
---
# Send data from AWS S3 to Axiom
Source: https://axiom.co/docs/send-data/aws-s3
This page explains how to set up an AWS Lambda function to send logs from an S3 bucket to Axiom. The Lambda function triggers when a new log file is uploaded to an S3 bucket, processes the log data, and sends it to Axiom.
* Create an AWS account with permissions to create and manage S3 buckets, Lambda functions, and IAM roles. For more information, see the [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/with-s3-example.html).
## Package the requests module [#package-the-requests-module]
Before creating the Lambda function, package the requests module so it can be used in the function:
1. Create a new directory.
2. Install the requests module into the current directory using pip.
3. Zip the contents of the directory.
4. Add your Lambda function file to the zip file.
## Create AWS Lambda function [#create-aws-lambda-function]
Create a Lambda function with Python runtime and upload the packaged zip file containing the requests module and your function code below:
```py
import os
import json
import boto3
import requests
import csv
import io
import ndjson
import re
def lambda_handler(event, context):
# Extract the bucket name and object key from the event
bucket = event['Records'][0]['s3']['bucket']['name']
key = event['Records'][0]['s3']['object']['key']
try:
# Fetch the log file from S3
s3 = boto3.client('s3')
obj = s3.get_object(Bucket=bucket, Key=key)
except Exception as e:
print(f"Error fetching from S3: {str(e)}")
raise e
# Read the log data from the S3 object
log_data = obj['Body'].read().decode('utf-8')
# Determine the file format and parse accordingly
file_extension = os.path.splitext(key)[1].lower()
if file_extension == '.csv':
csv_data = csv.DictReader(io.StringIO(log_data))
json_logs = list(csv_data)
elif file_extension == '.txt':
log_lines = log_data.strip().split("\n")
json_logs = [{'message': line} for line in log_lines]
elif file_extension == '.log':
# IMPORTANT: Log files can be in various formats (JSON, XML, syslog, etc.)
try:
# First, try to parse as JSON (either one JSON object per line or a JSON array)
if log_data.strip().startswith('[') and log_data.strip().endswith(']'):
# Appears to be a JSON array
json_logs = json.loads(log_data)
else:
# Try parsing as NDJSON (one JSON object per line)
try:
json_logs = ndjson.loads(log_data)
except:
# If not valid NDJSON, check if each line might be JSON
log_lines = log_data.strip().split("\n")
json_logs = []
for line in log_lines:
try:
# Try to parse each line as JSON
parsed_line = json.loads(line)
json_logs.append(parsed_line)
except:
# Create a dictionary and let json module handle the escaping
message_dict = {'message': line}
json_logs.append(message_dict)
except:
# If JSON parsing fails, default to treating as plain text
log_lines = log_data.strip().split("\n")
json_logs = [{'message': line} for line in log_lines]
print("Warning: Log file format could not be determined. Treating as plain text.")
elif file_extension == '.ndjson' or file_extension == '.jsonl':
json_logs = ndjson.loads(log_data)
else:
print(f"Unsupported file format: {file_extension}")
return
# Prepare Axiom API request
dataset_name = os.environ['DATASET_NAME']
axiom_edge_domain = os.environ['AXIOM_DOMAIN']
axiom_api_url = f"https://{axiom_edge_domain}/v1/ingest/{dataset_name}"
api_token = os.environ['API_TOKEN']
axiom_headers = {
"Authorization": f"Bearer {api_token}",
"Content-Type": "application/json"
}
try:
response = requests.post(axiom_api_url, headers=axiom_headers, json=json_logs)
if response.status_code != 200:
print(f"Failed to send logs to Axiom: {response.text}")
else:
print(f"Successfully sent logs to Axiom. Response: {response.text}")
except Exception as e:
print(f"Error sending to Axiom: {str(e)}")
print(f"Processed {len(json_logs)} log entries")
```
In the environment variables section of the Lambda function configuration, add the following environment variables:
This example uses Python for the Lambda function. To use another language, change the code above accordingly.
### Understanding log format [#understanding-log-format]
The `.log` extension doesn't guarantee any specific format. Log files might contain:
* JSON (single object or array)
* NDJSON/JSONL (one JSON object per line)
* Syslog format
* XML
* Application-specific formats (Apache, Nginx, ELB, etc.)
* Custom formats with quoted strings and special characters
The example code includes format detection for common formats, but you’ll need to customize this based on your specific log structure.
#### Example: Custom parser for structured logs [#example-custom-parser-for-structured-logs]
For logs with a specific structure (like AWS ELB logs), you have to implement a custom parser. Here’s a simplified example:
```py
import shlex
import re
class Parser:
def parse_line(self, line):
try:
line = re.sub(r"[\[\]]", "", line)
data = shlex.split(line)
result = {
"protocol": data[0],
"timestamp": data[1],
"client_ip_port": data[2],
# ...more fields...
}
return result
except Exception as e:
raise e
```
## Configure S3 to trigger Lambda [#configure-s3-to-trigger-lambda]
In the Amazon S3 console, select the bucket where your log files are stored. Go to the properties tab, find the event notifications section, and create an event notification. Select All object create events as the event type and choose the Lambda function you created earlier as the destination. For more information, see the [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/with-s3-example.html).
## Upload a test log file [#upload-a-test-log-file]
Ensure the log file you upload to the S3 bucket is in the correct format, such as JSON or newline-delimited JSON (NDJSON) or CSV. Here’s an example:
```json
[
{
"_time":"2021-02-04T03:11:23.222Z",
"data":{"key1":"value1","key2":"value2"}
},
{
"data":{"key3":"value3"},
"attributes":{"key4":"value4"}
},
{
"tags": {
"server": "aws",
"source": "wordpress"
}
}
]
```
After uploading a test log file to your S3 bucket, the Lambda function automatically processes the log data and sends it to Axiom. In Axiom, go to the Stream tab and select the dataset you specified in the Lambda function. You now see the logs from your S3 bucket in Axiom.
---
# Send data from CloudFront to Axiom
Source: https://axiom.co/docs/send-data/cloudfront
Use the Axiom CloudFront Lambda to send CloudFront logs to Axiom using AWS S3 bucket and Lambda. After you set this up, you can observe your static and dynamic content and run deep queries on your CloudFront distribution logs efficiently and properly.
The Axiom CloudFront Lambda is an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/axiom-cloudfront-lambda).
* [Create an account on AWS Cloud](https://signin.aws.amazon.com/signup?request_type=register).
## Setup [#setup]
1. Select one of the following:
* If you already have an S3 bucket for your CloudFront data, [launch the base stack on AWS](https://us-east-2.console.aws.amazon.com/cloudformation/home?region=us-east-2#/stacks/create/template?stackName=CloudFront-Axiom\&templateURL=https://axiom-cloudformation-stacks.s3.amazonaws.com/axiom-cloudfront-lambda-base-cloudformation-stack.yaml).
* If you don’t have an S3 bucket for your CloudFront data, [launch the stack on AWS](https://us-east-2.console.aws.amazon.com/cloudformation/home?region=us-east-2#/stacks/create/template?stackName=CloudFront-Axiom\&templateURL=https://axiom-cloudformation-stacks.s3.amazonaws.com/axiom-cloudfront-lambda-cloudformation-stack.yaml) that creates an S3 bucket for you.
2. Add the name of the Axiom dataset where you want to send data.
3. Enter the Axiom API token you have previously created.
## Configuration [#configuration]
To configure your CloudFront distribution:
1. In AWS, select your origin domain.
2. In **Origin access**, select **Legacy access identities**, and then select your origin access identity in the list.
3. In **Bucket policy**, select **Yes, update the bucket policy**.
4. In **Standard logging**, select **On**. This means that your data is delivered to your S3 bucket.
5. Click **Create Distribution**, and then click **Run your Distribution**.
Go back to Axiom to see the CloudFront distribution logs.
---
# Send data from Amazon CloudWatch to Axiom
Source: https://axiom.co/docs/send-data/cloudwatch
Axiom CloudWatch Forwarder is a set of easy-to-use AWS CloudFormation stacks designed to forward logs from Amazon CloudWatch to Axiom. It includes a Lambda function to handle the forwarding and stacks to create Amazon CloudWatch log group subscription filters for both existing and future log groups.
Axiom CloudWatch Forwarder includes templates for the following CloudFormation stacks:
* **Forwarder** creates a Lambda function that forwards logs from Amazon CloudWatch to Axiom.
* **Subscriber** runs once to create subscription filters on Forwarder for Amazon CloudWatch log groups specified by a combination of names, prefix, and regular expression filters.
* **Listener** creates a Lambda function that listens for new log groups and creates subscription filters for them on Forwarder. This way, you don’t have to create subscription filters manually for new log groups.
* **Unsubscriber** runs once to remove subscription filters on Forwarder for Amazon CloudWatch log groups specified by a combination of names, prefix, and regular expression filters.
The Axiom CloudWatch Forwarder is an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/axiom-cloudwatch-forwarder).
* [Create an account on AWS Cloud](https://signin.aws.amazon.com/signup?request_type=register).
## Installation [#installation]
To install the Axiom CloudWatch Forwarder, choose one of the following:
* [Cloudformation stacks](#install-with-cloudformation-stacks)
* [Terraform module](#install-with-terraform-module)
### Install with Cloudformation stacks [#install-with-cloudformation-stacks]
1. [Launch the Forwarder stack template on AWS](https://console.aws.amazon.com/cloudformation/home?#/stacks/new?stackName=axiom-cloudwatch-forwarder\&templateURL=https://axiom-cloudformation.s3.amazonaws.com/stacks/axiom-cloudwatch-forwarder-v1.1.1-cloudformation-stack.yaml). Copy the Forwarder Lambda ARN because it’s referenced in the Subscriber stack.
2. [Launch the Subscriber stack template on AWS](https://console.aws.amazon.com/cloudformation/home?#/stacks/new?stackName=axiom-cloudwatch-subscriber\&templateURL=https://axiom-cloudformation.s3.amazonaws.com/stacks/axiom-cloudwatch-subscriber-v1.1.1-cloudformation-stack.yaml).
3. [Launch the Listener stack template on AWS](https://console.aws.amazon.com/cloudformation/home?#/stacks/new?stackName=axiom-cloudwatch-listener\&templateURL=https://axiom-cloudformation.s3.amazonaws.com/stacks/axiom-cloudwatch-listener-v1.1.1-cloudformation-stack.yaml).
### Install with Terraform module [#install-with-terraform-module]
Create a new Forwarder module in your Terraform file in the following way:
```hcl
module "forwarder" {
source = "axiomhq/axiom-cloudwatch-forwarder/aws//modules/forwarder"
axiom_dataset = "DATASET_NAME"
axiom_token = "API_TOKEN"
prefix = "axiom-cloudwatch-forwarder"
}
```
Alternatively, create a dataset with the [Axiom Terraform provider](/apps/terraform#create-dataset).
Create a new Subscriber module in your Terraform file in the following way:
```hcl
module "subscriber" {
source = "axiomhq/axiom-cloudwatch-forwarder/aws//modules/subscriber"
prefix = "axiom-cloudwatch-forwarder"
forwarder_lambda_arn = module.forwarder.lambda_arn
log_groups_prefix = "/aws/lambda/"
}
```
Create a new Listener module in your Terraform file in the following way:
```hcl
module "listener" {
source = "axiomhq/axiom-cloudwatch-forwarder/aws//modules/listener"
prefix = "axiom-cloudwatch-forwarder"
forwarder_lambda_arn = module.forwarder.lambda_arn
log_groups_prefix = "/aws/lambda/"
}
```
In your terminal, go to the folder of your main Terraform file, and then run `terraform init`.
Run `terraform plan` to check the changes, and then run `terraform apply`.
## Filter Amazon CloudWatch log groups [#filter-amazon-cloudwatch-log-groups]
The Subscriber and Unsubscriber stacks allow you to filter the log groups by a combination of names, prefix, and regular expression filters. If no filters are specified, the stacks subscribe to or unsubscribe from all log groups. You can also whitelist a specific set of log groups using filters in the CloudFormation stack parameters. The log group names, prefix, and regular expression filters included are additive, meaning the union of all provided inputs is matched.
### Example [#example]
For example, you have the following list of log groups:
```
/aws/lambda/function-foo
/aws/lambda/function-bar
/aws/eks/cluster/cluster-1
/aws/rds/instance-baz
```
* To subscribe to the Lambda log groups exclusively, use a prefix filter with the value of `/aws/lambda`.
* To subscribe to EKS and RDS log groups, use a list of names with the value of `/aws/eks/cluster/cluster-1,/aws/rds/instance-baz`.
* To subscribe to the EKS log group and all Lambda log groups, use a combination of prefix and names list.
* To use the regular expression filter, write a regular expression to match the log group names. For example, `\/aws\/lambda\/.*` matches all Lambda log groups.
* To subscribe to all log groups, leave the filters empty.
## Listener architecture [#listener-architecture]
The optional Listener stack does the following:
* Creates an Amazon S3 bucket for AWS CloudTrail.
* Creates a trail to capture the creation of new log groups.
* Creates an event rule to pass those creation events to an Amazon EventBridge event bus.
* Sends an event via EventBridge to a Lambda function when a new log group is created.
* Creates a subscription filter for each new log group.
## Remove subscription filters [#remove-subscription-filters]
To remove subscription filters for one or more log groups, [launch the Unsubscriber stack template on AWS](https://console.aws.amazon.com/cloudformation/home?#/stacks/new?stackName=axiom-cloudwatch-subscriber\&templateURL=https://axiom-cloudformation.s3.amazonaws.com/stacks/axiom-cloudwatch-unsubscriber-v1.1.1-cloudformation-stack.yaml).
The log group filtering works the same way as the Subscriber stack. You can filter the log groups by a combination of names, prefix, and regular expression filters.
Alternatively, to turn off log forwarding to Axiom, create a new Unsubscriber module in your Terraform file in the following way:
```hcl
module "unsubscriber" {
source = "axiomhq/axiom-cloudwatch-forwarder/aws//modules/unsubscriber"
prefix = "axiom-cloudwatch-forwarder"
forwarder_lambda_arn = module.forwarder.lambda_arn
log_groups_prefix = "/aws/lambda/"
}
```
---
# Send data from Convex to Axiom
Source: https://axiom.co/docs/send-data/convex
[Convex](https://convex.dev) lets you manage the backend of your app (database, server, and more) from a centralized cloud interface. Set up a log stream in Convex to send your app’s logs to Axiom and make it your single source of truth about events.
* [Create a Convex account](https://www.convex.dev/login) with a Professional plan.
* Set up your app with Convex. For example, follow one of the quickstart guides in the [Convex documentation](https://docs.convex.dev/quickstarts).
## Configure Convex log streams [#configure-convex-log-streams]
To send data from Convex to Axiom, set up a Convex log stream:
In your Convex dashboard, configure the Axiom integration. For more information, see the [Convex documentation about configuring an integration](https://docs.convex.dev/production/integrations/#configuring-an-integration).
During this process, you need the following:
* **Dataset name**: The name of the Axiom dataset where you want to send data.
* **API key**: Your Axiom API token.
* **Attributes** (optional): A list of key-value pairs to include in all events your app sends to Axiom. Attributes are useful for adding context like project name, environment, or team information to all your logs.
Convex verifies the connection to Axiom and displays a confirmation when the log stream is active.
For more information, see the [Convex documentation about log streams](https://docs.convex.dev/production/integrations/log-streams#axiom).
## Benefits of streaming Convex logs to Axiom [#benefits-of-streaming-convex-logs-to-axiom]
The Convex log streaming integration with Axiom provides several key benefits:
* **Complete observability**: Monitor all function executions, errors, and performance metrics in one place
* **Historical analysis**: Store and analyze logs beyond Convex’s recent logs view
* **Advanced querying**: Use APL to create complex queries and aggregations
* **Real-time monitoring**: Set up alerts and monitors for proactive issue detection
* **Custom dashboards**: Build tailored visualizations for your specific use cases
* **Team collaboration**: Share insights and dashboards with your development team
For more information, see [Connect Axiom with Convex](/apps/convex).
---
# Send data from Cribl to Axiom
Source: https://axiom.co/docs/send-data/cribl
Cribl is a data processing framework often used with machine data. It allows you to parse, reduce, transform, and route data to and from various systems in your infrastructure.
You can send logs from Cribl LogStream to Axiom using HTTP or Syslog destination.
## Set up log forwarding from Cribl to Axiom using the HTTP destination [#set-up-log-forwarding-from-cribl-to-axiom-using-the-http-destination]
Below are the steps to set up and send logs from Cribl to Axiom using the HTTP destination:
1. Create a new HTTP destination in Cribl LogStream:
Open Cribl’s UI and navigate to **Destinations > HTTP**. Click on `+` Add New to create a new destination.
2. Configure the destination:
* **Name:** Choose a name for the destination.
* **Endpoint URL:** The URL of your Axiom log ingest endpoint `https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME`.
* **Method:** Choose `POST`.
* **Event Breaker:** Set this to One Event Per Request or CRLF (Carriage Return Line Feed), depending on how you want to separate events.
3. Headers:
You may need to add some headers. Here is a common example:
* **Content-Type:** Set this to `application/json`.
* **Authorization:** Set this to `Bearer API_TOKEN`.
4. Body:
In the Body Template, input `{{_raw}}`. This forwards the raw log event to Axiom.
5. Save and enable the destination:
After you’ve finished configuring the destination, save your changes and make sure the destination is enabled.
## Set up log forwarding from Cribl to Axiom using the Syslog destination [#set-up-log-forwarding-from-cribl-to-axiom-using-the-syslog-destination]
### Create Syslog endpoint [#create-syslog-endpoint]
### Configure destination in Cribl [#configure-destination-in-cribl]
1. Create a new Syslog destination in Cribl LogStream:
Open Cribl’s UI and navigate to **Destinations > Syslog**. Click on `+` Add New to create a new destination.
2. Configure the destination:
* **Name:** Choose a name and output ID for the destination.
* **Protocol:** Choose the protocol for the Syslog messages. Select the TCP protocol.
* **Destination Address:** Input the address of the Axiom endpoint to which you want to send logs. This address is generated from your Syslog endpoint in Axiom and follows this format: `tcp+tls://qsfgsfhjsfkbx9.syslog.axiom.co:6514`.
* **Destination Port:** Enter the port number on which the Axiom endpoint is listening for Syslog messages which is `6514`
* **Format:** Choose the Syslog message format. `RFC3164` is a common format and is generally recommended.
* **Facility:** Choose the facility code to use in the Syslog messages. The facility code represents the type of process that’s generating the Syslog messages.
* **Severity:** Choose the severity level to use in the Syslog messages. The severity level represents the importance of the Syslog messages.
3. Configure the Message:
* **Timestamp Format:** Choose the timestamp format to use in the Syslog messages.
* **Application Name Field:** Enter the name of the field to use as the app name in the Syslog messages.
* **Message Field:** Enter the name of the field to use as the message in the Syslog messages. Typically, this would be `_raw`.
* **Throttling:** Enter the throttling value. Throttling is a mechanism to control the data flow rate from the source (Cribl) to the destination (in this case, an Axiom Syslog Endpoint).
4. Save and enable the destination
After you’ve finished configuring the destination, save your changes and make sure the destination is enabled.
---
# Send data from Elastic Beats to Axiom
Source: https://axiom.co/docs/send-data/elastic-beats
[Elastic Beats](https://www.elastic.co/beats/) serves as a lightweight platform for data shippers that transfer information from the source to Axiom and other tools based on the configuration. Before shipping data, it collects metrics and logs from different sources, which later are deployed to your Axiom deployments.
There are different [Elastic Beats](https://www.elastic.co/beats/) you could use to ship logs. Axiom’s documentation provides a detailed step by step procedure on how to use each Beats.
To ensure compatibility with Axiom, use the following versions:
* For Elastic Beats log shippers such as Filebeat, Metricbeat, Heartbeat, Auditbeat, and Packetbeat, use their open-source software (OSS) version 8.12.1 or lower.
* For Winlogbeat, use the OSS version 7.17.22 or lower.
* For Journalbeat, use the OSS version 7.15.2 or lower.
If you get a 400 error when you use the field name `_time` or when you override the [`timestamp` field](/reference/limits), use the query parameter `?timestamp-field` to set a field as the time field.
## Filebeat [#filebeat]
[Filebeat](https://www.elastic.co/beats/filebeat) is a lightweight shipper for logs. It helps you centralize logs and files, and can read files from your system.
Filebeats is useful for workloads, system, app log files, and data logs you would like to ingest to Axiom in some way.
In the logging case, it helps centralize logs and files in a structured pattern by reading from your various apps, services, workloads, and VMs, then shipping to your Axiom deployments.
### Installation [#installation]
Visit the [Filebeat OSS download page](https://www.elastic.co/downloads/beats/filebeat-oss) to install Filebeat. For more information, check out Filebeat’s [official documentation](https://www.elastic.co/guide/en/beats/filebeat/current/index.html)
When downloading Filebeats, install the OSS version being that the non-oss version doesn’t work with Axiom.
### Configuration [#configuration]
Axiom lets you ingest data with the ElasticSearch bulk ingest API.
In order for Filebeat to work, disable index lifecycle management (ILM). To do so, `add setup.ilm.enabled: false` to the `filebeat.yml` configuration file.
```yaml
setup.ilm.enabled: false
filebeat.inputs:
- type: log
# Specify the path of the system log files to be sent to Axiom deployment.
paths:
- $PATH_TO_LOG_FILE
output.elasticsearch:
hosts: ['https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic']
api_key: 'axiom:API_TOKEN'
allow_older_versions: true
```
## Metricbeat [#metricbeat]
[Metricbeat](https://www.elastic.co/beats/metricbeat) is a lightweight shipper for metrics.
Metricbeat is installed on your systems and services and used for monitoring their performance, as well as different remote packages/utilities running on them.
### Installation [#installation-1]
Visit the [MetricBeat OSS download page](https://www.elastic.co/downloads/beats/metricbeat-oss) to install Metricbeat. For more information, check out Metricbeat’s [official documentation](https://www.elastic.co/guide/en/beats/metricbeat/current/index.html)
### Configuration [#configuration-1]
```yaml
setup.ilm.enabled: false
metricbeat.config.modules:
path:
-$PATH_TO_LOG_FILE
metricbeat.modules:
- module: system
metricsets:
- filesystem
- cpu
- load
- fsstat
- memory
- network
output.elasticsearch:
hosts: ["https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic"]
# Specify Axiom API token
api_key: 'axiom:API_TOKEN'
allow_older_versions: true
```
### Send AWS RDS metric set to Axiom [#send-aws-rds-metric-set-to-axiom]
The RDS metric set enables you to monitor your AWS RDS service. [RDS metric set](https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-metricset-aws-rds.html) fetches a set of metrics from Amazon RDS and Amazon Aurora DB. With Amazon RDS, users can monitor network throughput, I/O for read, write, and/or metadata operations, client connections, and burst credit balances for their DB instances and send the data to Axiom.
```yaml
setup.ilm.enabled: false
metricbeat.config.modules:
path:
-$PATH_TO_LOG_FILE
metricbeat.modules:
- module: aws
period: 60s
metricsets:
- rds
access_key_id: ''
secret_access_key: ''
session_token: ''
# Add other AWS configurations if needed
output.elasticsearch:
hosts: ["https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic"]
api_key: 'axiom:API_TOKEN'
allow_older_versions: true
```
## Winlogbeat [#winlogbeat]
[Winlogbeat](https://www.elastic.co/guide/en/beats/winlogbeat/current/index.html) is an open-source Windows specific event-log shipper that’s installed as a Windows service. It can be used to collect and send event logs to Axiom.
Winlogbeat reads from one or more event logs using Windows APIs, filters the events based on user-configured criteria, then sends the event data to the configured outputs.
You can Capture:
* app events
* hardware events
* security events
* system events
### Installation [#installation-2]
Visit the [Winlogbeat download page](https://www.elastic.co/downloads/beats/winlogbeat) to install Winlogbeat. For more information, check out Winlogbeat’s [official documentation](https://www.elastic.co/guide/en/beats/winlogbeat/current/winlogbeat-installation-configuration.html)
* Extract the contents of the zip file into `C:\Program Files`.
* Rename the `winlogbeat-$version` directory to Winlogbeat
* Open a PowerShell prompt as an Administrator and run
```bash
PS C:\Users\Administrator> cd C:\Program Files\Winlogbeat
PS C:\Program Files\Winlogbeat> .\install-service-winlogbeat.ps1
```
### Configuration [#configuration-2]
Configuration for Winlogbeat Service is found in the `winlogbeat.yml` file in `C:\Program Files\Winlogbeat.`
Edit the `winlogbeat.yml` configuration file found in `C:\Program Files\Winlogbeat` to send data to Axiom.
The `winlogbeat.yml` file contains the configuration on which windows events and service it should monitor and the time required.
```yaml
winlogbeat.event_logs:
- name: Application
- name: System
- name: Security
logging.to_files: true
logging.files:
path: C:\ProgramData\Winlogbeat\Logs
logging.level: info
output.elasticsearch:
hosts: ['https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic']
# token should be an API token
api_key: 'axiom:API_TOKEN'
allow_older_versions: true
```
#### Validate configuration [#validate-configuration]
```bash
# Check if your configuration is correct
PS C:\Program Files\Winlogbeat> .\winlogbeat.exe test config -c .\winlogbeat.yml -e
```
#### Start Winlogbeat [#start-winlogbeat]
```bash
PS C:\Program Files\Winlogbeat> Start-Service winlogbeat
```
You can view the status of your service and control it from the Services management console in Windows.
To launch the management console, run this command:
```bash
PS C:\Program Files\Winlogbeat> services.msc
```
#### Stop Winlogbeat [#stop-winlogbeat]
```bash
PS C:\Program Files\Winlogbeat> Stop-Service winlogbeat
```
### Ignore older Winlogbeat configuration [#ignore-older-winlogbeat-configuration]
The `ignore_older` option in the Winlogbeat configuration is used to ignore older events.
Winlogbeat reads from the Windows event log system. When it starts up, it starts reading from a specific point in the event log. By default, Winlogbeat starts reading new events created after Winlogbeat started.
However, you might want Winlogbeat to read some older events as well. For instance, if you restart Winlogbeat, you might want it to continue where it left off, rather than skipping all the events that were created while it wasn’t running. In this case, you can use the `ignore_older` option to specify how old events Winlogbeat should read. The `ignore_older` option takes a duration as a value. Any events that are older than this duration are ignored. The duration is a string of a number followed by a unit. Units can be one of `ms` (milliseconds), `s` (seconds), `m` (minutes), `h` (hours) or `d` (days).
```yaml
winlogbeat.event_logs:
- name: Application
ignore_older: 72h
output.elasticsearch:
hosts: ['https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic']
protocol: "https"
ssl.verification_mode: "full"
# token should be an API token
api_key: 'axiom:API_TOKEN'
allow_older_versions: true
```
You can start Winlogbeat from the command line by running `.\winlogbeat.exe -c winlogbeat.yml` in the Winlogbeat installation directory.
### Add verification modes and processors [#add-verification-modes-and-processors]
Verification mode refers to the SSL/TLS verification performed when Winlogbeat connects to your output destination, for instance, a Logstash instance, ElasticSearch instance or an Axiom instance. You can add your verification modes, additional processors data, and multiple windows event logs to you configurations and send the logs to Axiom. The configuration is specified in the`winlogbeat.event_logs` configuration option.
```yaml
winlogbeat.event_logs:
- name: Application
ignore_older: 72h
- name: Security
- name: System
output.elasticsearch:
hosts: ['https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic']
# token should be an API token
api_key: 'axiom:API_TOKEN'
allow_older_versions: true
ssl.verification_mode: "certificate"
processors:
- add_host_metadata: ~
- add_cloud_metadata: ~
logging.level: info
logging.to_files: true
logging.files:
path: C:/ProgramData/winlogbeat/Logs
name: winlogbeat
keepfiles: 7
permissions: 0600
```
You can start Winlogbeat from the command line by running `.\winlogbeat.exe -c winlogbeat.yml` in the Winlogbeat installation directory.
For more information on Winlogbeat event logs, visit the Winlogbeat [documentation](https://www.elastic.co/guide/en/beats/winlogbeat/current/index.html).
## Heartbeat [#heartbeat]
[Heartbeat](https://www.elastic.co/guide/en/beats/heartbeat/current/heartbeat-overview.html) is a lightweight shipper for uptime monitoring.
It monitors your services and sends response time to Axiom. It lets you periodically check the status of your services and determine whether they’re available.
Heartbeat is useful when you need to verify that you’re meeting your service level agreements for service uptime.
Heartbeat currently supports monitors for checking hosts via:
* ICMP (v4 and v6) echo requests: Use the `icmp monitor` when you simply want to check whether a service is available. This monitor requires root access.
* TCP: Use the TCP monitor to connect `via TCP.` You can optionally configure this monitor to verify the endpoint by sending and/or receiving a custom payload.
* HTTP: Use the HTTP monitor to connect `via HTTP.` You can optionally configure this monitor to verify that the service returns the expected response, such as a specific status code, response header, or content.
### Installation [#installation-3]
Visit the [Heartbeat download page](https://www.elastic.co/guide/en/beats/heartbeat/current/heartbeat-installation-configuration.html#installation) to install Heartbeat on your system.
### Configuration [#configuration-3]
Heartbeat provides monitors to check the status of hosts at set intervals. Heartbeat currently provides monitors for ICMP, TCP, and HTTP.
You configure each monitor individually. In `heartbeat.yml`, specify the list of monitors that you want to enable. Each item in the list begins with a dash (-).
The example below configures Heartbeat to use three monitors: an ICMP monitor, a TCP monitor, and an HTTP monitor deployed instantly to Axiom.
```yaml
# Disable index lifecycle management (ILM)
setup.ilm.enabled: false
heartbeat.monitors:
- type: icmp
schedule: '*/5 * * * * * *'
hosts: ['myhost']
id: my-icmp-service
name: My ICMP Service
- type: tcp
schedule: '@every 5s'
hosts: ['myhost:12345']
mode: any
id: my-tcp-service
- type: http
schedule: '@every 5s'
urls: ['http://example.net']
service.name: apm-service-name
id: my-http-service
name: My HTTP Service
output.elasticsearch:
hosts: ['https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic']
# token should be an API token
api_key: 'axiom:API_TOKEN'
allow_older_versions: true
```
## Auditbeat [#auditbeat]
Auditbeat is a lightweight shipper that ships events in real time to Axiom for further analysis. It Collects your Linux audit framework data and monitor the integrity of your files. It’s also used to evaluate the activities of users and processes on your system.
You can also use Auditbeat to detect changes to critical files, like binaries and configuration files, and identify potential security policy violations.
### Installation [#installation-4]
Visit the [Auditbeat download page](https://www.elastic.co/downloads/beats/auditbeat) to install Auditbeat on your system.
### Configuration [#configuration-4]
Auditbeat uses modules to collect audit information:
* Auditd
* File integrity
* System
By default, Auditbeat uses a configuration that’s tailored to the operating system where Auditbeat is running.
To use a different configuration, change the module settings in `auditbeat.yml.`
The example below configures Auditbeat to use the `file_integrity` module configured to generate events whenever a file in one of the specified paths changes on disk. The events contains the file metadata and hashes, and it’s deployed instantly to Axiom.
```yaml
# Disable index lifecycle management (ILM)
setup.ilm.enabled: false
auditbeat.modules:
- module: file_integrity
paths:
- /usr/bin
- /sbin
- /usr/sbin
- /etc
- /bin
- /usr/local/sbin
output.elasticsearch:
hosts: ['https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic']
# token should be an API token
api_key: 'axiom:API_TOKEN'
allow_older_versions: true
```
## Packetbeat [#packetbeat]
Packetbeat is a real-time network packet analyzer that you can integrate with Axiom to provide an app monitoring and performance analytics system between the servers of your network.
With Axiom you can use Packetbeat to capture the network traffic between your app servers, decode the app layer protocols (HTTP, MySQL, Redis, PGSQL, Thrift, MongoDB, and so on), and correlate the requests with the responses.
Packetbeat sniffs the traffic between your servers, and parses the app-level protocols on the fly directly into Axiom.
Currently, Packetbeat supports the following protocols:
* ICMP (v4 and v6)
* DHCP (v4)
* DNS
* HTTP
* AMQP 0.9.1
* Cassandra
* MySQL
* PostgreSQL
* Redis
* Thrift-RPC
* MongoDB
* MemCache
* NFS
* TLS
* SIP/SDP (beta)
### Installation [#installation-5]
Visit the [Packetbeat download page](https://www.elastic.co/downloads/beats/packetbeat) to install Packetbeat on your system.
### Configuration [#configuration-5]
In `packetbeat.yml`, configure the network devices and protocols to capture traffic from.
To see a list of available devices for `packetbeat.yml` configuration , run:
| OS type | Command |
| ------- | -------------------------------------------------------------- |
| DEB | Run `packetbeat devices` |
| RPM | Run `packetbeat devices` |
| MacOS | Run `./packetbeat devices` |
| Brew | Run `packetbeat devices` |
| Linux | Run `./packetbeat devices` |
| Windows | Run `PS C:\Program Files\Packetbeat> .\packetbeat.exe devices` |
Packetbeat supports these sniffer types:
* `pcap`
* `af_packet`
In the protocols section, configure the ports where Packetbeat can find each protocol. If you use any non-standard ports, add them here. Otherwise, use the default values:
```yaml
# Disable index lifecycle management (ILM)
setup.ilm.enabled: false
packetbeat.interfaces.auto_promisc_mode: true
packetbeat.flows:
timeout: 30s
period: 10s
protocols:
dns:
ports: [53]
include_authorities: true
include_additionals: true
http:
ports: [80, 8080, 8081, 5000, 8002]
memcache:
ports: [11211]
mysql:
ports: [3306]
pgsql:
ports: [5432]
redis:
ports: [6379]
thrift:
ports: [9090]
mongodb:
ports: [27017]
output.elasticsearch:
hosts: ['https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic']
# api_key should be your API token
api_key: 'axiom:API_TOKEN'
allow_older_versions: true
```
For more information on configuring Packetbeats, visit the [documentation](https://www.elastic.co/guide/en/beats/packetbeat/current/configuring-howto-packetbeat.html).
## Journalbeat [#journalbeat]
Journalbeat is a lightweight shipper for forwarding and centralizing log data from [systemd journals](https://www.freedesktop.org/software/systemd/man/systemd-journald.service.html) to a log management tool like Axiom.
Journalbeat monitors the journal locations that you specify, collects log events, and eventually forwards the logs to Axiom.
### Installation [#installation-6]
Visit the [Journalbeat download page](https://www.elastic.co/guide/en/beats/journalbeat/current/journalbeat-installation-configuration.html) to install Journalbeat on your system.
### Configuration [#configuration-6]
Before running Journalbeat, specify the location of the systemd journal files and configure how you want the files to be read.
The example below configures Journalbeat to use the `path` of your systemd journal files. Each path can be a directory path (to collect events from all journals in a directory), or a path configured to deploy logs instantly to Axiom.
```yaml
# Disable index lifecycle management (ILM)
setup.ilm.enabled: false
journalbeat.inputs:
- paths:
- "/dev/log"
- "/var/log/messages/my-journal-file.journal"
seek: head
journalbeat.inputs:
- paths: []
include_matches:
- "CONTAINER_TAG=redis"
- "_COMM=redis"
- "container.image.tag=redis"
- "process.name=redis"
output.elasticsearch:
hosts: ['https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic']
# token should be an API token
api_key: 'axiom:API_TOKEN'
allow_older_versions: true
```
For more information on configuring Journalbeat, visit the [documentation](https://www.elastic.co/guide/en/beats/journalbeat/current/configuration-journalbeat-options.html).
---
# Send data from Elastic Bulk API to Axiom
Source: https://axiom.co/docs/send-data/elasticsearch-bulk-api
Axiom is a log management platform that offers an Elasticsearch Bulk API emulation to facilitate migration from Elasticsearch or integration with tools that support the Elasticsearch Bulk API.
Using the Elastic Bulk API and Axiom in your app provides a robust way to store and manage logs.
The Elasticsearch Bulk API expects the timestamp to be formatted as `@timestamp`, not `_time`. For example:
```json
{"index": {"_index": "myindex", "_id": "1"}}
{"@timestamp": "2024-01-07T12:00:00Z", "message": "axiom elastic bulk", "severity": "INFO"}
```
## Send logs to Axiom using the Elasticsearch Bulk API and Go [#send-logs-to-axiom-using-the-elasticsearch-bulk-api-and-go]
To send logs to Axiom using the Elasticsearch Bulk API and Go, use the `net/http` package to create and send the HTTP request.
### Prepare your data [#prepare-your-data]
The data needs to be formatted as per the Bulk API’s requirements. Here’s a simple example of how to prepare your data:
```json
data :=
{"index": {"_index": "myindex", "_id": "1"}}
{"@timestamp": "2023-06-06T12:00:00Z", "message": "axiom elastic bulk", "severity": "INFO"}
{"index": {"_index": "myindex", "_id": "2"}}
{"@timestamp": "2023-06-06T12:00:01Z", "message": "axiom elastic bulk api", "severity": "ERROR"}
```
### Send data to Axiom [#send-data-to-axiom]
Get an Axiom [API token](/reference/tokens) for the Authorization header, and create a [dataset](/reference/datasets).
```go
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"net/http"
)
func main() {
data := []byte(`{"index": {"_index": "myindex", "_id": "1"}}
{"@timestamp": "2023-06-06T12:00:00Z", "message": "axiom elastic bulk", "severity": "INFO"}
{"index": {"_index": "myindex", "_id": "2"}}
{"@timestamp": "2023-06-06T12:00:01Z", "message": "axiom elastic bulk api", "severity": "ERROR"}
`)
// Create a new request using http
req, err := http.NewRequest("POST", "https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic/_bulk", bytes.NewBuffer(data))
if err != nil {
log.Fatalf("Error creating request: %v", err)
}
// Add authorization header to the request
req.Header.Add("Authorization", "Bearer API_TOKEN")
req.Header.Add("Content-Type", "application/x-ndjson")
// Send request using http.Client
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Error on response: %v", err)
}
defer resp.Body.Close()
// Read and print the response body
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Error reading response body: %v", err)
}
fmt.Printf("Response status: %s\nResponse body: %s\n", resp.Status, string(body))
}
```
## Send logs to Axiom using the Elasticsearch Bulk API and Python [#send-logs-to-axiom-using-the-elasticsearch-bulk-api-and-python]
To send logs to Axiom using the Elasticsearch Bulk API and Python, use the built-in `requests` library.
### Prepare your data [#prepare-your-data-1]
The data sent needs to be formatted as per the Bulk API’s requirements. Here’s a simple example of how to prepare the data:
```json
data = """
{"index": {"_index": "myindex", "_id": "1"}}
{"@timestamp": "2023-06-06T12:00:00Z", "message": "Log message 1", "severity": "INFO"}
{"index": {"_index": "myindex", "_id": "2"}}
{"@timestamp": "2023-06-06T12:00:01Z", "message": "Log message 2", "severity": "ERROR"}
"""
```
### Send data to Axiom [#send-data-to-axiom-1]
Obtain an Axiom [API token](/reference/tokens) for the Authorization header, and [dataset](/reference/datasets).
```py
import requests
import json
data = """
{"index": {"_index": "myindex", "_id": "1"}}
{"@timestamp": "2024-01-07T12:00:00Z", "message": "axiom elastic bulk", "severity": "INFO"}
{"index": {"_index": "myindex", "_id": "2"}}
{"@timestamp": "2024-01-07T12:00:01Z", "message": "Log message 2", "severity": "ERROR"}
"""
# Replace these with your actual dataset name and API token
dataset = "DATASET_NAME"
api_token = "API_TOKEN"
# The URL for the bulk API
url = f'https://AXIOM_DOMAIN:443/v1/datasets/{dataset}/elastic/_bulk'
try:
response = requests.post(
url,
data=data,
headers={
'Content-Type': 'application/x-ndjson',
'Authorization': f'Bearer {api_token}'
}
)
response.raise_for_status()
except requests.HTTPError as http_err:
print(f'HTTP error occurred: {http_err}')
print('Response:', response.text)
except Exception as err:
print(f'Other error occurred: {err}')
else:
print('Success!')
try:
print(response.json())
except json.JSONDecodeError:
print(response.text)
```
## Send logs to Axiom using the Elasticsearch Bulk API and JavaScript [#send-logs-to-axiom-using-the-elasticsearch-bulk-api-and-javascript]
Use the axios library in JavaScript to send logs to Axiom using the Elasticsearch Bulk API.
### Prepare your data [#prepare-your-data-2]
The data sent needs to be formatted as per the Bulk API’s requirements. Here’s a simple example of how to prepare the data:
```json
let data = `
{"index": {"_index": "myindex", "_id": "1"}}
{"@timestamp": "2023-06-06T12:00:00Z", "message": "Log message 1", "severity": "INFO"}
{"index": {"_index": "myindex", "_id": "2"}}
{"@timestamp": "2023-06-06T12:00:01Z", "message": "Log message 2", "severity": "ERROR"}
`;
```
### Send data to Axiom [#send-data-to-axiom-2]
Obtain an Axiom [API token](/reference/tokens) for the Authorization header, and [dataset](/reference/datasets).
```js
const axios = require('axios');
// Axiom elastic API URL
const AxiomApiUrl = 'https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic/_bulk';
// Your Axiom API token
const AxiomToken = 'API_TOKEN';
// The logs data retrieved from Elasticsearch
const logs = [
{"index": {"_index": "myindex", "_id": "1"}},
{"@timestamp": "2023-06-06T12:00:00Z", "message": "axiom logging", "severity": "INFO"},
{"index": {"_index": "myindex", "_id": "2"}},
{"@timestamp": "2023-06-06T12:00:01Z", "message": "axiom log data", "severity": "ERROR"}
];
// Convert the logs to a single string with newline separators
const data = logs.map(log => JSON.stringify(log)).join('\n') + '\n';
axios.post(AxiomApiUrl, data, {
headers: {
'Content-Type': 'application/x-ndjson',
'Authorization': `Bearer ${AxiomToken}`
}
})
.then((response) => {
console.log('Response Status:', response.status);
console.log('Response Data:', response.data);
})
.catch((error) => {
console.error('Error:', error.response ? error.response.data : error.message);
});
```
## Send logs to Axiom using the Elasticsearch Bulk API and PHP [#send-logs-to-axiom-using-the-elasticsearch-bulk-api-and-php]
To send logs from PHP to Axiom using the Elasticsearch Bulk API, make sure you have installed the necessary PHP libraries: [Guzzle](https://docs.guzzlephp.org/en/stable/overview.html) for making HTTP requests and [JsonMachine](https://packagist.org/packages/halaxa/json-machine) for handling newline-delimited JSON data.
### Prepare your data [#prepare-your-data-3]
The data sent needs to be formatted as per the Bulk API’s requirements. Here’s a simple example of how to prepare the data:
```json
$data = << 'https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic/_bulk', // Update with your Axiom host
'timeout' => 2.0,
]);
// Your Axiom API token
$AxiomToken = 'API_TOKEN';
// The logs data retrieved from Elasticsearch
// Note: Replace this with your actual code to retrieve logs from Elasticsearch
$logs = [
["@timestamp" => "2023-06-06T12:00:00Z", "message" => "axiom logger", "severity" => "INFO"],
["@timestamp" => "2023-06-06T12:00:01Z", "message" => "axiom logging elasticsearch", "severity" => "ERROR"]
];
$events = array_map(function ($log) {
return [
'@timestamp' => $log['@timestamp'],
'attributes' => $log
];
}, $logs);
// Create the payload for Axiom
$payload = [
'tags' => [
'source' => 'myapplication',
'host' => 'myhost'
],
'events' => $events
];
try {
$response = $client->post('', [
'headers' => [
'Authorization' => 'Bearer ' . $AxiomToken,
'Content-Type' => 'application/x-ndjson',
],
'json' => $payload,
]);
// handle response here
$statusCode = $response->getStatusCode();
$content = $response->getBody();
echo "Status code: $statusCode \nContent: $content";
} catch (\Exception $e) {
// handle exception here
echo "Error: " . $e->getMessage();
}
```
---
# Send data from Fluent Bit to Axiom
Source: https://axiom.co/docs/send-data/fluent-bit
Fluent Bit is an open-source log processor and forwarder that allows you to collect any data like metrics and logs from different sources, enrich them with filters, and send them to multiple destinations like Axiom.
* [Install Fluent Bit](https://docs.fluentbit.io/manual/installation/getting-started-with-fluent-bit).
## Configure Fluent Bit [#configure-fluent-bit]
1. Set up the Fluent Bit configuration file based on the [Fluent Bit documentation](https://docs.fluentbit.io/manual/administration/configuring-fluent-bit/classic-mode/configuration-file).
2. In the Fluent Bit configuration file, use the HTTP output plugin with the following configuration. For more information on the plugin, see the [Fluent Bit documentation](https://docs.fluentbit.io/manual/pipeline/outputs/http).
```ini
[OUTPUT]
Name http
Match *
Host AXIOM_DOMAIN
Port 443
URI /v1/ingest/DATASET_NAME
Header Authorization Bearer API_TOKEN
Compress gzip
Format json
JSON_Date_Key _time
JSON_Date_Format iso8601
TLS On
```
---
# Send data from Fluentd to Axiom
Source: https://axiom.co/docs/send-data/fluentd
Fluentd is an open-source log collector that allows you to collect, aggregate, process, analyze, and route log files.
With Fluentd, you can collect logs from multiple sources and ship it instantly into Axiom
## Installation [#installation]
Visit the [Fluentd download page](https://www.fluentd.org/download) to install Fluentd on your system.
## Configuration [#configuration]
Fluentd lifecycle consist of five different components which are:
* Setup: Configure your `fluent.conf` file.
* Inputs: Define your input listeners.
* Filters: Create a rule to allow or disallow an event.
* Matches: Send output to Axiom when input data match and pair specific data from your data input within your configuration.
* Labels: Groups filters and simplifies tag handling.
When setting up Fluentd, the configuration file `.conf` is used to connect its components.
## Configuring Fluentd using the HTTP output plugin [#configuring-fluentd-using-the-http-output-plugin]
The example below shows a Fluentd configuration that sends data to Axiom using the [HTTP output plugin](https://docs.fluentd.org/output/http):
```xml
@type forward
port 24224
@type http
endpoint https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME
# Authorization Bearer should be an ingest token
headers {"Authorization": "Bearer API_TOKEN"}
json_array false
open_timeout 3
@type json
flush_interval 5s
```
## Configuring Fluentd using the OpenSearch output plugin [#configuring-fluentd-using-the-opensearch-output-plugin]
The example below shows a Fluentd configuration that sends data to Axiom using the [OpenSearch plugin](https://docs.fluentd.org/output/opensearch):
```xml
@type tail
@id input_tail
@type apache2
path /var/log/*.log
tag td.logs
@type opensearch
@id out_os
@log_level info
include_tag_key true
include_timestamp true
host "#{ENV['FLUENT_OPENSEARCH_HOST'] || 'AXIOM_DOMAIN'}"
port "#{ENV['FLUENT_OPENSEARCH_PORT'] || '443'}"
path "#{ENV['FLUENT_OPENSEARCH_PATH']|| '/v1/datasets/DATASET_NAME/elastic'}"
scheme "#{ENV['FLUENT_OPENSEARCH_SCHEME'] || 'https'}"
ssl_verify "#{ENV['FLUENT_OPENSEARCH_SSL_VERIFY'] || 'true'}"
ssl_version "#{ENV['FLUENT_OPENSEARCH_SSL_VERSION'] || 'TLSv1_2'}"
user "#{ENV['FLUENT_OPENSEARCH_USER'] || 'axiom'}"
password "#{ENV['FLUENT_OPENSEARCH_PASSWORD'] || 'xaat-xxxxxxxxxx-xxxxxxxxx-xxxxxxx'}"
index_name "#{ENV['FLUENT_OPENSEARCH_INDEX_NAME'] || 'fluentd'}"
```
## Configure buffer interval with filter patterns [#configure-buffer-interval-with-filter-patterns]
The example below shows a Fluentd configuration to hold logs in memory with specific flush intervals, size limits, and how to exclude specific logs based on patterns.
```xml
# Collect common system logs
@type tail
@id system_logs
@type none
path /var/log/*.log
pos_file /var/log/fluentd/system.log.pos
read_from_head true
tag system.logs
# Collect Apache2 logs (if they’re located in /var/log/apache2/)
@type tail
@id apache_logs
@type apache2
path /var/log/apache2/*.log
pos_file /var/log/fluentd/apache2.log.pos
read_from_head true
tag apache.logs
# Filter to exclude certain patterns (optional)
@type grep
key message
pattern /exclude_this_pattern/
# Send logs to Axiom
@type opensearch
@id out_os
@log_level info
include_tag_key true
include_timestamp true
host "#{ENV['FLUENT_OPENSEARCH_HOST'] || 'AXIOM_DOMAIN'}"
port "#{ENV['FLUENT_OPENSEARCH_PORT'] || '443'}"
path "#{ENV['FLUENT_OPENSEARCH_PATH']|| '/v1/datasets/DATASET_NAME/elastic'}"
scheme "#{ENV['FLUENT_OPENSEARCH_SCHEME'] || 'https'}"
ssl_verify "#{ENV['FLUENT_OPENSEARCH_SSL_VERIFY'] || 'true'}"
ssl_version "#{ENV['FLUENT_OPENSEARCH_SSL_VERSION'] || 'TLSv1_2'}"
user "#{ENV['FLUENT_OPENSEARCH_USER'] || 'axiom'}"
password "#{ENV['FLUENT_OPENSEARCH_PASSWORD'] || 'xaat-xxxxxxxxxx-xxxxxxxxx-xxxxxxx'}"
index_name "#{ENV['FLUENT_OPENSEARCH_INDEX_NAME'] || 'fluentd'}"
@type memory
flush_mode interval
flush_interval 10s
chunk_limit_size 5M
retry_max_interval 30
retry_forever true
```
## Collect and send PHP logs to Axiom [#collect-and-send-php-logs-to-axiom]
The example below shows a Fluentd configuration that sends PHP data to Axiom.
```xml
# Collect PHP logs
@type tail
@id php_logs
@type multiline
format_firstline /^\[\d+-\w+-\d+ \d+:\d+:\d+\]/
format1 /^\[(?\d+-\w+-\d+ \d+:\d+:\d+)\] (?.*)/
path /var/log/php*.log
pos_file /var/log/fluentd/php.log.pos
read_from_head true
tag php.logs
# Send PHP logs to Axiom
@type opensearch
@id out_os
@log_level info
include_tag_key true
include_timestamp true
host "#{ENV['FLUENT_OPENSEARCH_HOST'] || 'AXIOM_DOMAIN'}"
port "#{ENV['FLUENT_OPENSEARCH_PORT'] || '443'}"
path "#{ENV['FLUENT_OPENSEARCH_PATH']|| '/v1/datasets/DATASET_NAME/elastic'}"
scheme "#{ENV['FLUENT_OPENSEARCH_SCHEME'] || 'https'}"
ssl_verify "#{ENV['FLUENT_OPENSEARCH_SSL_VERIFY'] || 'true'}"
ssl_version "#{ENV['FLUENT_OPENSEARCH_SSL_VERSION'] || 'TLSv1_2'}"
user "#{ENV['FLUENT_OPENSEARCH_USER'] || 'axiom'}"
password "#{ENV['FLUENT_OPENSEARCH_PASSWORD'] || 'xaat-xxxxxxxxxx-xxxxxxxxx-xxxxxxx'}"
index_name "#{ENV['FLUENT_OPENSEARCH_INDEX_NAME'] || 'php-logs'}"
@type memory
flush_mode interval
flush_interval 10s
chunk_limit_size 5M
retry_max_interval 30
retry_forever true
```
## Collect and send Scala logs to Axiom [#collect-and-send-scala-logs-to-axiom]
The example below shows a Fluentd configuration that sends Scala data to Axiom
```xml
# Collect Scala logs
@type tail
@id scala_logs
@type multiline
format_firstline /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}/
format1 /^(?\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}) \[(?.*)\] (?\w+) (?[\w\.$]+) - (?.*)/
path /var/log/scala-app.log
pos_file /var/log/fluentd/scala.log.pos
read_from_head true
tag scala.logs
# Send Scala logs using HTTP plugin to Axiom
@type http
endpoint "#{ENV['FLUENT_HTTP_ENDPOINT'] || 'https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME'}"
headers {"Authorization": "Bearer #{ENV['FLUENT_HTTP_TOKEN'] || ''}"}
@type json
@type memory
flush_mode interval
flush_interval 10s
chunk_limit_size 5M
retry_max_interval 30
retry_forever true
```
## Send virtual machine logs to Axiom using the HTTP output plugin [#send-virtual-machine-logs-to-axiom-using-the-http-output-plugin]
The example below shows a Fluentd configuration that sends data from your virtual machine to Axiom using the `apache` source type.
```xml
@type tail
@id input_tail
@type apache2
path /var/log/**/*.log
pos_file /var/log/fluentd/fluentd.log.pos
tag vm.logs
read_from_head true
@type record_transformer
hostname "#{Socket.gethostname}"
service "vm_service"
@type http
@id out_http_axiom
@log_level info
endpoint "#{ENV['AXIOM_URL'] || 'https://api.axiom.co'}"
path "/v1/ingest/DATASET_NAME"
ssl_verify "#{ENV['AXIOM_SSL_VERIFY'] || 'true'}"
Authorization "Bearer API_TOKEN"
Content-Type "application/json"
@type json
@type memory
flush_mode interval
flush_interval 5s
chunk_limit_size 5MB
retry_forever true
```
The example below shows a Fluentd configuration that sends data from your virtual machine to Axiom using the `nginx` source type.
```xml
@type tail
@id input_tail
@type nginx
path /var/log/nginx/access.log, /var/log/nginx/error.log
pos_file /var/log/fluentd/nginx.log.pos
tag nginx.logs
read_from_head true
@type record_transformer
hostname "#{Socket.gethostname}"
service "nginx"
@type http
@id out_http_axiom
@log_level info
endpoint "#{ENV['AXIOM_URL'] || 'https://api.axiom.co'}"
path "/v1/ingest/DATASET_NAME"
ssl_verify "#{ENV['AXIOM_SSL_VERIFY'] || 'true'}"
Authorization "Bearer API_TOKEN"
Content-Type "application/json"
@type json
@type memory
flush_mode interval
flush_interval 5s
chunk_limit_size 5MB
retry_forever true
```
---
# Send data from Heroku Log Drains to Axiom
Source: https://axiom.co/docs/send-data/heroku-log-drains
Use [Heroku Log Drains](https://devcenter.heroku.com/articles/log-drains) to send data from your Heroku apps and deployments to Axiom.
With Heroku Log Drains, you can only send data to the US East 1 (AWS) edge deployment. For more information, see [Edge deployments](/reference/edge-deployments).
* [Create an account on Heroku](https://heroku.com/).
* [Download and install the Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli).
* [Create a Heroku app](https://devcenter.heroku.com/articles/creating-apps).
## Setup [#setup]
Configure Heroku Log Drains:
```bash
heroku drains:add "https://axiom:API_TOKEN@api.axiom.co/v1/datasets/DATASET_NAME/ingest" -a HEROKU_APPLICATION_NAME
```
Replace `HEROKU_APPLICATION_NAME` with the name of the Heroku app.
---
# Send data from Kubernetes Cluster to Axiom
Source: https://axiom.co/docs/send-data/kubernetes
Axiom makes it easy to collect, analyze, and monitor logs from your Kubernetes clusters. Integrate popular tools like Filebeat, Vector, or Fluent Bit with Axiom to send your cluster logs.
## Send Kubernetes Cluster logs to Axiom using Filebeat [#send-kubernetes-cluster-logs-to-axiom-using-filebeat]
Ingest logs from your Kubernetes cluster into Axiom using Filebeat.
The following is an example of a DaemonSet configuration to ingest your data logs into Axiom.
### Configuration [#configuration]
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: filebeat
namespace: kube-system
labels:
k8s-app: filebeat
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: filebeat
labels:
k8s-app: filebeat
rules:
- apiGroups: [''] # "" indicates the core API group
resources:
- namespaces
- pods
- nodes
verbs:
- get
- watch
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: filebeat
subjects:
- kind: ServiceAccount
name: filebeat
namespace: kube-system
roleRef:
kind: ClusterRole
name: filebeat
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
data:
filebeat.yml: |-
filebeat.autodiscover:
providers:
- type: kubernetes
node: ${NODE_NAME}
hints.enabled: true
hints.default_config:
type: container
paths:
- /var/log/containers/*${data.kubernetes.container.id}.log
allow_older_versions: true
processors:
- add_cloud_metadata:
output.elasticsearch:
hosts: ['${AXIOM_HOST}/v1/datasets/${AXIOM_DATASET}/elastic']
api_key: 'axiom:${AXIOM_TOKEN}'
setup.ilm.enabled: false
kind: ConfigMap
metadata:
annotations: {}
labels:
k8s-app: filebeat
name: filebeat-config
namespace: kube-system
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
k8s-app: filebeat
name: filebeat
namespace: kube-system
spec:
selector:
matchLabels:
k8s-app: filebeat
template:
metadata:
annotations: {}
labels:
k8s-app: filebeat
spec:
containers:
- args:
- -c
- /etc/filebeat.yml
- -e
env:
- name: AXIOM_HOST
value: AXIOM_DOMAIN
- name: AXIOM_DATASET
value: DATASET_NAME
- name: AXIOM_TOKEN
value: API_TOKEN
- name: NODE_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: spec.nodeName
image: docker.elastic.co/beats/filebeat-oss:8.11.1
imagePullPolicy: IfNotPresent
name: filebeat
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 100Mi
securityContext:
runAsUser: 0
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /etc/filebeat.yml
name: config
readOnly: true
subPath: filebeat.yml
- mountPath: /usr/share/filebeat/data
name: data
- mountPath: /var/lib/docker/containers
name: varlibdockercontainers
readOnly: true
- mountPath: /var/log
name: varlog
readOnly: true
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: filebeat
serviceAccountName: filebeat
terminationGracePeriodSeconds: 30
volumes:
- configMap:
defaultMode: 416
name: filebeat-config
name: config
- hostPath:
path: /var/lib/docker/containers
type: ''
name: varlibdockercontainers
- hostPath:
path: /var/log
type: ''
name: varlog
- hostPath:
path: /var/lib/filebeat-data
type: ''
name: data
updateStrategy:
rollingUpdate:
maxUnavailable: 1
type: RollingUpdate
```
After editing your values, apply the changes to your cluster using `kubectl apply -f daemonset.yaml`
## Send Kubernetes Cluster logs to Axiom using Vector [#send-kubernetes-cluster-logs-to-axiom-using-vector]
Collect logs from your Kubernetes cluster and send them directly to Axiom using the Vector daemonset.
### Configuration [#configuration-1]
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: vector
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: vector
rules:
- apiGroups: [""]
resources:
- pods
- nodes
- namespaces
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vector
subjects:
- kind: ServiceAccount
name: vector
namespace: kube-system
roleRef:
kind: ClusterRole
name: vector
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ConfigMap
metadata:
name: vector-config
namespace: kube-system
data:
vector.yml: |-
sources:
kubernetes_logs:
type: kubernetes_logs
self_node_name: ${VECTOR_SELF_NODE_NAME}
sinks:
axiom:
type: axiom
inputs:
- kubernetes_logs
compression: zstd
dataset: ${AXIOM_DATASET}
token: ${AXIOM_TOKEN}
healthcheck:
enabled: true
log_level: debug
logging:
level: debug
log_level: debug
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: vector
namespace: kube-system
spec:
selector:
matchLabels:
name: vector
template:
metadata:
labels:
name: vector
spec:
serviceAccountName: vector
containers:
- name: vector
image: timberio/vector:0.37.0-debian
args:
- --config-dir
- /etc/vector/
env:
- name: AXIOM_HOST
value: AXIOM_DOMAIN
- name: AXIOM_DATASET
value: DATASET_NAME
- name: AXIOM_TOKEN
value: API_TOKEN
- name: VECTOR_SELF_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
volumeMounts:
- name: config
mountPath: /etc/vector/vector.yml
subPath: vector-config.yml
- name: data-dir
mountPath: /var/lib/vector
- name: var-log
mountPath: /var/log
readOnly: true
- name: var-lib
mountPath: /var/lib
readOnly: true
resources:
limits:
memory: 500Mi
requests:
cpu: 200m
memory: 100Mi
securityContext:
runAsUser: 0
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumes:
- name: config
configMap:
name: vector-config
items:
- key: vector.yml
path: vector-config.yml
- name: data-dir
hostPath:
path: /var/lib/vector
type: DirectoryOrCreate
- name: var-log
hostPath:
path: /var/log
- name: var-lib
hostPath:
path: /var/lib
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
updateStrategy:
rollingUpdate:
maxUnavailable: 1
type: RollingUpdate
```
After editing your values, apply the changes to your cluster using `kubectl apply -f daemonset.yaml`
## Send Kubernetes Cluster logs to Axiom using Fluent Bit [#send-kubernetes-cluster-logs-to-axiom-using-fluent-bit]
Collect logs from your Kubernetes cluster and send them directly to Axiom using Fluent Bit.
### Configuration [#configuration-2]
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: fluent-bit
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: fluent-bit
rules:
- apiGroups: [""]
resources:
- pods
- nodes
- namespaces
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: fluent-bit
subjects:
- kind: ServiceAccount
name: fluent-bit
namespace: kube-system
roleRef:
kind: ClusterRole
name: fluent-bit
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ConfigMap
metadata:
name: fluent-bit-config
namespace: kube-system
data:
fluent-bit.conf: |-
[SERVICE]
Flush 1
Log_Level debug
Daemon off
Parsers_File parsers.conf
HTTP_Server On
HTTP_Listen 0.0.0.0
HTTP_Port 2020
[INPUT]
Name tail
Tag kube.*
Path /var/log/containers/*.log
Parser docker
DB /var/log/flb_kube.db
Mem_Buf_Limit 7MB
Skip_Long_Lines On
Refresh_Interval 10
[FILTER]
Name kubernetes
Match kube.*
Kube_URL https://kubernetes.default.svc:443
Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token
Kube_Tag_Prefix kube.var.log.containers.
Merge_Log On
Merge_Log_Key log_processed
K8S-Logging.Parser On
K8S-Logging.Exclude Off
[OUTPUT]
Name http
Match *
Host ${AXIOM_HOST}
Port 443
URI /v1/ingest/${AXIOM_DATASET}
Header Authorization Bearer ${AXIOM_TOKEN}
Format json
Json_date_key time
Json_date_format iso8601
Retry_Limit False
Compress gzip
tls On
tls.verify Off
parsers.conf: |-
[PARSER]
Name docker
Format json
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L
Time_Keep On
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: kube-system
spec:
selector:
matchLabels:
name: fluent-bit
template:
metadata:
labels:
name: fluent-bit
spec:
serviceAccountName: fluent-bit
containers:
- name: fluent-bit
image: fluent/fluent-bit:1.9.9
env:
- name: AXIOM_HOST
value: AXIOM_DOMAIN
- name: AXIOM_DATASET
value: DATASET_NAME
- name: AXIOM_TOKEN
value: API_TOKEN
volumeMounts:
- name: config
mountPath: /fluent-bit/etc/fluent-bit.conf
subPath: fluent-bit.conf
- name: config
mountPath: /fluent-bit/etc/parsers.conf
subPath: parsers.conf
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
volumes:
- name: config
configMap:
name: fluent-bit-config
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
terminationGracePeriodSeconds: 10
```
After editing your values, apply the changes to your cluster using `kubectl apply -f daemonset.yaml`
## Extract top-level fields from log body [#extract-top-level-fields-from-log-body]
To extract top-level fields from the log body, configure the `filelog` receiver with operators to parse container log formats and JSON-structured log bodies.
Configure the following settings in your OpenTelemetry Collector configuration:
| Setting | Description |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| **Receiver** | Use `filelog` receiver targeting `/var/log/pods/*/*/*.log` |
| **Container operator** | Add the `container` operator to parse container log formats (Docker, CRI-O, Containerd) and extract the inner log message as the body |
| **JSON parser operator** | Add the `json_parser` operator to parse JSON-structured log bodies into structured fields |
| **Transform processor** | Optional: Use the `transform` processor to promote parsed JSON fields to top-level attributes instead of keeping them in the body |
Example configuration:
```yaml
receivers:
filelog/k8s-logs:
include: [/var/log/pods/*/*/*.log]
start_at: end
operators:
- type: container
- type: json_parser
if: body matches '^{.*}$'
```
For complete configuration details, including optional timestamp and severity extraction, see the [OpenTelemetry Collector documentation](https://opentelemetry.io/docs/collector/).
---
# Send data from Logstash to Axiom
Source: https://axiom.co/docs/send-data/logstash
Logstash is an open-source log aggregation, transformation tool, and server-side data processing pipeline that simultaneously ingests data from many sources. With Logstash, you can collect, parse, send, and store logs for future use on Axiom.
Logstash works as a data pipeline tool with Axiom, where, from one end, the data is input from your servers and system and, from the other end, Axiom takes out the data and converts it into useful information.
It can read data from various `input` sources, filter data for the specified configuration, and eventually store it.
Logstash sits between your data and where you want to keep it.
* Visit the [Logstash download page](https://www.elastic.co/downloads/logstash) to install Logstash on your system.
## Configuration [#configuration]
To configure the `logstash.conf` file, define the source, set the rules to format your data, and set Axiom as the destination where the data is sent.
The Logstash configuration works with OpenSearch, so you can use the OpenSearch syntax to define the source and destination.
The Logstash Pipeline has three stages:
* [Input stage](https://www.elastic.co/guide/en/logstash/8.0/pipeline.html#_inputs) generates the event & Ingest Data of all volumes, Sizes, forms, and Sources
* [Filter stage](https://www.elastic.co/guide/en/logstash/8.0/pipeline.html#_filters) modifies the event as you specify in the filter component
* [Output stage](https://www.elastic.co/guide/en/logstash/8.0/pipeline.html#_outputs) shifts and sends the event into Axiom.
## OpenSearch output [#opensearch-output]
For installation instructions for the plugin, check out the [OpenSearch documentation](https://opensearch.org/docs/latest/tools/logstash/index/#install-logstash)
In `logstash.conf`, configure your Logstash pipeline to collect and send data logs to Axiom.
The example below shows Logstash configuration that sends data to Axiom:
```js
input{
exec{
command => "date"
interval => "1"
}
}
output{
opensearch{
hosts => ["https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic"]
user => "axiom"
password => "API_TOKEN"
}
}
```
## Combining filters with conditionals on Logstash events [#combining-filters-with-conditionals-on-logstash-events]
Logstash provides an extensive array of filters that allow you to enhance, manipulate, and transform your data. These filters can be used to perform tasks such as extracting, removing, and adding new fields and changing the content of fields.
Some valuable filters include the following.
## Grok filter plugin [#grok-filter-plugin]
The Grok filter plugin allows you to parse the unstructured log data into something structured and queryable, and eventually send the structured logs to Axiom. It matches the unstructured data to patterns and maps the data to specified fields.
Here’s an example of how to use the Grok plugin:
```js
input{
exec{
command => "axiom"
interval => "1"
}
}
filter {
grok {
match => { "message" => "%{COMBINEDAPACHELOG}" }
}
date {
match => [ "timestamp" , "dd/MMM/yyyy:HH:mm:ss Z" ]
}
mutate {
add_field => { "foo" => "Hello Axiom, from Logstash" }
remove_field => [ "axiom", "logging" ]
}
}
output{
opensearch{
hosts => ["https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic"]
user => "axiom"
password => "API_TOKEN"
}
}
```
This configuration parses Apache log data by matching the pattern of `COMBINEDAPACHELOG`.
## Mutate filter plugin [#mutate-filter-plugin]
The Mutate filter plugin allows you to perform general transformations on fields. For example, rename, convert, strip, and modify fields in event data.
Here’s an example of using the Mutate plugin:
```js
input{
exec{
command => "axiom"
interval => "1"
}
}
filter {
mutate {
rename => { "hostname" => "host" }
convert => { "response" => "integer" }
uppercase => [ "method" ]
remove_field => [ "request", "httpversion" ]
}
}
output{
opensearch{
hosts => ["https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic"]
user => "axiom"
password => "API_TOKEN"
}
}
```
This configuration renames the field `hostname` to `host`, converts the `response` field value to an integer, changes the `method` field to uppercase, and removes the `request` and `httpversion` fields.
## Drop filter plugin [#drop-filter-plugin]
The Drop filter plugin allows you to drop certain events based on specified conditions. This helps you to filter out unnecessary data.
Here’s an example of using the Drop plugin:
```js
input {
syslog {
port => 5140
type => syslog
}
}
filter {
if [type] == "syslog" and [severity] == "debug" {
drop { }
}
}
output{
opensearch{
hosts => ["https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic"]
user => "axiom"
password => "API_TOKEN"
}
}
```
This configuration drops all events of type `syslog` with severity `debug`.
## Clone filter plugin [#clone-filter-plugin]
The Clone filter plugin creates a copy of an event and stores it in a new event. The event continues along the pipeline until it ends or is dropped.
Here’s an example of using the Clone plugin:
```js
input {
syslog {
port => 5140
type => syslog
}
}
filter {
clone {
clones => ["cloned_event"]
}
}
output{
opensearch{
hosts => ["https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic"]
user => "axiom"
password => "API_TOKEN"
}
}
```
This configuration creates a new event named `cloned_event` that’s a clone of the original event.
## GeoIP filter plugin [#geoip-filter-plugin]
The GeoIP filter plugin adds information about the geographical location of IP addresses. This data includes the latitude, longitude, continent, country, and so on.
Here’s an example of using the GeoIP plugin:
```js
input{
exec{
command => "axiom"
interval => "6"
}
}
filter {
geoip {
source => "ip"
}
}
output{
opensearch{
hosts => ["https://AXIOM_DOMAIN:443/v1/datasets/DATASET_NAME/elastic"]
user => "axiom"
password => "API_TOKEN"
}
}
```
This configuration adds geographical location data for the IP address in the `ip` field. Note that you may need to specify the path to the GeoIP database file in the plugin configuration, depending on your setup.
---
# Send data from Loki Multiplexer to Axiom
Source: https://axiom.co/docs/send-data/loki-multiplexer
Loki by Prometheus is a multi-tenant log aggregation system that’s highly scalable and capable of indexing metadata about your logs.
Loki exposes an HTTP API for pushing, querying, and tailing Axiom log data.
Axiom Loki Proxy provides a gateway for you to connect a direct link interface to Axiom via Loki endpoint.
Using the Axiom Loki Proxy, you can ship logs to Axiom via the [Loki HTTP API](https://grafana.com/docs/loki/latest/reference/loki-http-api/#ingest-logs).
## Installation [#installation]
### Install and update using Homebrew [#install-and-update-using-homebrew]
```bash
brew tap axiomhq/tap
brew install axiom-loki-proxy
brew update
brew upgrade axiom-loki-proxy
```
### Install using `go get` [#install-using-go-get]
```bash
go get -u github.com/axiomhq/axiom-loki-proxy/cmd/axiom-loki-proxy
```
### Install from source [#install-from-source]
```bash
git clone https://github.com/axiomhq/axiom-loki-proxy.git
cd axiom-loki-proxy
make build
```
### Run the Loki-Proxy Docker [#run-the-loki-proxy-docker]
```bash
docker pull axiomhq/axiom-loki-proxy:latest
```
## Configuration [#configuration]
Specify the environmental variables for your Axiom deployment:
```bash
AXIOM_URL = AXIOM_DOMAIN
AXIOM_TOKEN = API_TOKEN
```
## Run and test [#run-and-test]
```bash
./axiom-loki-proxy
```
### Using Docker [#using-docker]
```bash
docker run -p8080:8080/tcp \
-e=AXIOM_TOKEN= \
axiomhq/axiom-loki-proxy
```
For more information on Axiom Loki Proxy and how you can propose bug fix, report issues and submit PRs, see the [GitHub repository](https://github.com/axiomhq/axiom-loki-proxy).
---
# Methods for sending data
Source: https://axiom.co/docs/send-data/methods
The easiest way to send your first event data to Axiom is with a direct HTTP request using a tool like `cURL`.
```shell wrap
curl -X 'POST' 'https://AXIOM_DOMAIN/v1/ingest/DATASET_NAME' \
-H 'Authorization: Bearer API_TOKEN' \
-H 'Content-Type: application/x-ndjson' \
-d '{ "http": { "request": { "method": "GET", "duration_ms": 231 }, "response": { "body": { "size": 3012 } } }, "url": { "path": "/download" } }'
```
To send events continuously, Axiom supports a wide range of standard tools, libraries, and platform integrations.
## Popular methods [#popular-methods]
| Method | Description |
| :---------------------------------------- | :----------------------------------------------- |
| [Rest API](/restapi/introduction) | Direct HTTP API for sending logs and events |
| [OpenTelemetry](/send-data/opentelemetry) | Industry-standard observability framework |
| [Vector](/send-data/vector) | High-performance observability data pipeline |
| [Cribl](/send-data/cribl) | Route and transform data with Cribl Stream |
| [Fluent Bit](/send-data/fluent-bit) | Fast and lightweight log processor |
| [Fluentd](/send-data/fluentd) | Open source data collector with plugin ecosystem |
| [JavaScript](/guides/javascript) | Browser and Node.js logging |
## Other methods [#other-methods]
| Method | Description |
| :------------------------------------------------------------- | :------------------------------------------------------------------------ |
| [.NET](/guides/send-logs-from-dotnet) | Send logs from .NET applications |
| [Apache Log4j](/guides/send-logs-from-apache-log4j) | Java logging with Log4j integration |
| [Apex](/guides/apex) | Structured logging for Go |
| [Cloudflare Workers](/guides/opentelemetry-cloudflare-workers) | Edge computing with Workers and OpenTelemetry |
| [Convex](/send-data/convex) | Stream data from Convex applications |
| [Elastic Beats](/send-data/elastic-beats) | Lightweight data shippers from Elastic |
| [Elasticsearch Bulk API](/send-data/elasticsearch-bulk-api) | Compatible endpoint for Elasticsearch clients |
| [Flutter](/guides/flutter) | Send structured logs from Flutter apps |
| [Go](/guides/go) | Native Go logging integration |
| [Heroku Log Drains](/send-data/heroku-log-drains) | Stream logs directly from Heroku apps |
| [Honeycomb](/endpoints/honeycomb) | Compatible endpoint for Honeycomb clients |
| [Kubernetes](/send-data/kubernetes) | Collect logs and metrics from K8s clusters |
| [Laravel](/guides/send-logs-from-laravel) | PHP Laravel framework integration |
| [Logrus](/guides/logrus) | Structured logging for Go with Logrus |
| [Logstash](/send-data/logstash) | Server-side data processing pipeline |
| [Loki](/endpoints/loki) | Compatible endpoint for Grafana Loki clients |
| [Loki Multiplexer](/send-data/loki-multiplexer) | Forward Loki data to multiple destinations |
| [Next.js](/send-data/nextjs) | Full-stack React framework logging |
| [PgBouncer](/send-data/pgbouncer) | PgBouncer metrics via pgbouncer\_exporter and the OpenTelemetry Collector |
| [Pino](/guides/pino) | Fast Node.js logger integration |
| [Python](/guides/python) | Python logging with standard library |
| [React](/send-data/react) | Client-side React app logging |
| [Render](/send-data/render) | Stream logs from Render.com services |
| [Ruby on Rails](/guides/send-logs-from-ruby-on-rails) | Rails app logging |
| [Rust](/guides/rust) | High-performance Rust logging |
| [Secure Syslog](/send-data/secure-syslog) | TLS-encrypted syslog forwarding |
| [Serverless](/send-data/serverless) | Best practices for serverless environments |
| [Splunk](/endpoints/splunk) | Compatible endpoint for Splunk forwarders |
| [StatsD](/send-data/statsd) | Send StatsD metrics through the OpenTelemetry Collector |
| [Syslog Proxy](/send-data/syslog-proxy) | Forward syslog data with transformation |
| [Traefik](/send-data/traefik) | Scrape Traefik metrics with the OpenTelemetry Collector |
| [Tremor](/send-data/tremor) | Event processing system for complex workflows |
| [Winston](/guides/winston) | Popular Node.js logging library |
| [Zap](/guides/zap) | Uber’s fast, structured Go logger |
## Amazon Web Services (AWS) [#amazon-web-services-aws]
Axiom offers deep integration with the AWS ecosystem.
| Method | Description |
| :------------------------------------------------------ | :------------------------------------ |
| [Amazon CloudFront](/send-data/cloudfront) | CDN access logs and real-time logs |
| [Amazon CloudWatch](/send-data/cloudwatch) | Stream logs from CloudWatch Logs |
| [Amazon Kinesis Data Firehose](/send-data/aws-firehose) | Real-time streaming with Firehose |
| [Amazon S3](/send-data/aws-s3) | Process logs stored in S3 buckets |
| [AWS FireLens](/send-data/aws-firelens) | Container log routing for ECS/Fargate |
| [AWS IoT Rules](/send-data/aws-iot-rules) | Route IoT device data and telemetry |
| [AWS Lambda](/send-data/aws-lambda) | Serverless function logs and traces |
| [AWS Lambda .NET](/send-data/aws-lambda-dot) | .NET-specific Lambda integration |
## Example configurations [#example-configurations]
The following examples show how to send data using OpenTelemetry from various languages and frameworks.
| Application | Description |
| :------------------------------------------------------------- | :---------------------------------------------- |
| [OpenTelemetry .NET](/guides/opentelemetry-dotnet) | Complete .NET app example |
| [OpenTelemetry Claude Code](/guides/opentelemetry-claude-code) | Monitor Claude Code usage with metrics and logs |
| [OpenTelemetry Django](/guides/opentelemetry-django) | Python Django with OpenTelemetry |
| [OpenTelemetry Go](/guides/opentelemetry-go) | Go app with full observability |
| [OpenTelemetry Java](/guides/opentelemetry-java) | Java Spring Boot example |
| [OpenTelemetry Next.js](/guides/opentelemetry-nextjs) | Full-stack Next.js with tracing |
| [OpenTelemetry Node.js](/guides/opentelemetry-nodejs) | Node.js Express example |
| [OpenTelemetry Nuxt.js](/guides/opentelemetry-nuxtjs) | Nuxt.js with server-side tracing |
| [OpenTelemetry Python](/guides/opentelemetry-python) | Python Flask/FastAPI example |
| [OpenTelemetry Ruby](/guides/opentelemetry-ruby) | Ruby on Rails with OpenTelemetry |
If you need an ingestion method that isn’t in the list above, [contact Axiom](https://www.axiom.co/contact).
## Limits on ingested data [#limits-on-ingested-data]
For more information on limits and requirements imposed by Axiom, see [Limits](/reference/limits).
---
# Send data from Next.js app to Axiom
Source: https://axiom.co/docs/send-data/nextjs
Next.js is a popular open-source JavaScript framework built on top of React, developed by Vercel. It’s used by a wide range of companies and organizations, from startups to large enterprises, due to its performance benefits and developer-friendly features.
To send data from your Next.js app to Axiom, choose one of the following options:
* [Axiom Vercel app](/apps/vercel)
* [@axiomhq/nextjs library](#use-axiomhq-nextjs-library)
* [next-axiom library](#use-next-axiom-library)
The choice between these options depends on your individual requirements:
* The two options can collect different event types.
| Event type | Axiom Vercel app | @axiomhq/nextjs library | next-axiom library |
| ---------------- | :--------------: | :---------------------: | :----------------: |
| Application logs | Yes | Yes | Yes |
| Web Vitals | No | Yes | Yes |
| HTTP logs | Yes | Yes | No |
| Build logs | Yes | No | No |
| Tracing | Yes | Yes | No |
* If you already use Vercel for deployments, the Axiom Vercel app can be easier to integrate into your existing experience.
* The cost of these options can differ widely depending on the volume of data you transfer. The Axiom Vercel app depends on Vercel Drains, a feature that’s only available on paid plans. For more information, see [the blog post on the changes to Vercel Drains](https://axiom.co/blog/changes-to-vercel-log-drains).
* The @axiomhq/nextjs library is the recommended choice if you want to send data from your Next.js app to Axiom without using Vercel Drains.
* The next-axiom library is no longer under active development. It’s supported with bug fixes, but it won’t receive new features.
For information on the Axiom Vercel app and migrating from the Vercel app to the next-axiom library, see [Axiom Vercel app](/apps/vercel).
The rest of this page explains how to send data from your Next.js app to Axiom using the next-axiom or the @axiomhq/nextjs library.
* [A new or existing Next.js app](https://nextjs.org/).
## Use @axiomhq/nextjs library [#use-axiomhqnextjs-library]
The @axiomhq/nextjs library is part of the Axiom JavaScript SDK, an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/axiom-js).
### Install @axiomhq/nextjs [#install-axiomhqnextjs]
1. In your terminal, go to the root folder of your Next.js app and run the following command:
```sh
npm install --save @axiomhq/js @axiomhq/logging @axiomhq/nextjs @axiomhq/react
```
2. Create the folder `lib/axiom` to store configurations for Axiom.
3. Create a `axiom.ts` file in the `lib/axiom` folder with the following content:
```ts lib/axiom/axiom.ts [expandable]
import { Axiom } from '@axiomhq/js';
const axiomClient = new Axiom({
token: process.env.NEXT_PUBLIC_AXIOM_TOKEN!,
});
export default axiomClient;
```
4. In the `lib/axiom` folder, create a `server.ts` file with the following content:
```ts lib/axiom/server.ts [expandable]
import axiomClient from '@/lib/axiom/axiom';
import { Logger, AxiomJSTransport } from '@axiomhq/logging';
import { createAxiomRouteHandler, nextJsFormatters } from '@axiomhq/nextjs';
export const logger = new Logger({
transports: [
new AxiomJSTransport({ axiom: axiomClient, dataset: process.env.NEXT_PUBLIC_AXIOM_DATASET! }),
],
formatters: nextJsFormatters,
});
export const withAxiom = createAxiomRouteHandler(logger);
```
The `createAxiomRouteHandler` is a builder function that returns a wrapper for your route handlers. The wrapper handles successful responses and errors thrown within the route handler. For more information on the logger, see [the @axiomhq/logging library](/guides/javascript#use-axiomhqlogging).
5. In the `lib/axiom` folder, create a `client.ts` file with the following content:
Ensure the API token you use on the client side has the appropriate permissions. Axiom recommends you create a client-side token with the only permission to ingest data into a specific dataset.
If you don’t want to expose the token to the client, use the [proxy transport](#proxy-for-client-side-usage) to send logs to Axiom.
```ts lib/axiom/client.ts [expandable]
'use client';
import axiomClient from '@/lib/axiom/axiom';
import { Logger, AxiomJSTransport } from '@axiomhq/logging';
import { createUseLogger, createWebVitalsComponent } from '@axiomhq/react';
import { nextJsFormatters } from '@axiomhq/nextjs/client';
export const logger = new Logger({
transports: [
new AxiomJSTransport({ axiom: axiomClient, dataset: process.env.NEXT_PUBLIC_AXIOM_DATASET! }),
],
formatters: nextJsFormatters,
});
const useLogger = createUseLogger(logger);
const WebVitals = createWebVitalsComponent(logger);
export { useLogger, WebVitals };
```
For more information on React client side helpers, see [React](/send-data/react).
### Capture traffic requests [#capture-traffic-requests]
To capture traffic requests, create a `proxy.ts` file in the root folder of your Next.js app with the following content:
```ts proxy.ts [expandable]
import { logger } from "@/lib/axiom/server";
import { transformMiddlewareRequest } from "@axiomhq/nextjs";
import { NextResponse } from "next/server";
import type { NextFetchEvent, NextRequest } from "next/server";
export async function proxy(request: NextRequest, event: NextFetchEvent) {
logger.info(...transformMiddlewareRequest(request));
event.waitUntil(logger.flush());
return NextResponse.next();
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
*/
"/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)",
],
};
```
Next.js 16 renamed middleware to proxy. If your app uses Next.js 15 or earlier, name the file `middleware.ts` and the exported function `middleware` instead. The file contents are otherwise identical. `middleware.ts` still works in Next.js 16, but it's deprecated and will be removed in a future version.
### Web Vitals [#web-vitals]
To capture Web Vitals, add the `WebVitals` component to the `app/layout.tsx` file:
```tsx /app/layout.tsx [expandable]
import { WebVitals } from "@/lib/axiom/client";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
{children}
);
}
```
### Logs [#logs]
Send logs to Axiom from different parts of your app. Each log function call takes a message and an optional `fields` object.
```ts [expandable]
import { logger } from "@/lib/axiom/server";
logger.debug("Login attempt", { user: "j_doe", status: "success" }); // Results in {"message": "Login attempt", "fields": {"user": "j_doe", "status": "success"}}
logger.info("Payment completed", { userID: "123", amount: "25USD" });
logger.warn("API rate limit exceeded", {
endpoint: "/users/1",
rateLimitRemaining: 0,
});
logger.error("System Error", { code: "500", message: "Internal server error" });
```
#### Route handlers [#route-handlers]
You can use the `withAxiom` function exported from the setup file in `lib/axiom/server.ts` to wrap your route handlers.
```ts
import { logger } from "@/lib/axiom/server";
import { withAxiom } from "@/lib/axiom/server";
export const GET = withAxiom(async () => {
return new Response("Hello World!");
});
```
For more information on customizing the data sent to Axiom, see [Advanced route handlers](#advanced-route-handlers).
#### Client components [#client-components]
To send logs from client components, add `useLogger` to your component:
```tsx [expandable]
"use client";
import { useLogger } from "@/lib/axiom/client";
export default function ClientComponent() {
const log = useLogger();
log.debug("User logged in", { userId: 42 });
const handleClick = () => log.info("User logged out");
return (
Logged in
Log out
);
}
```
#### Server components [#server-components]
To send logs from server components, use the following:
```tsx [expandable]
import { logger } from "@/lib/axiom/server";
import { after } from "next/server";
export default async function ServerComponent() {
log.info("User logged in", { userId: 42 });
after(() => {
logger.flush();
});
return Logged in ;
}
```
### Capture errors [#capture-errors]
#### Capture errors on Next 15 or later [#capture-errors-on-next-15-or-later]
To capture errors on Next 15 or later, use the `onRequestError` option. Create an `instrumentation.ts` file in the `src` or root folder of your Next.js app (depending on your configuration) with the following content:
```ts instrumentation.ts [expandable]
import { logger } from "@/lib/axiom/server";
import { createOnRequestError } from "@axiomhq/nextjs";
export const onRequestError = createOnRequestError(logger);
```
Alternatively, customize the error logging by creating a custom `onRequestError` function:
```ts [expandable]
import { logger } from "@/lib/axiom/server";
import { transformOnRequestError } from "@axiomhq/nextjs";
import { Instrumentation } from "next";
export const onRequestError: Instrumentation.onRequestError = async (
error,
request,
ctx
) => {
logger.error(...transformOnRequestError(error, request, ctx));
await logger.flush();
};
```
#### Capture errors on Next 14 or earlier [#capture-errors-on-next-14-or-earlier]
To capture routing errors on Next 14 or earlier, use the [error handling mechanism of Next.js](https://nextjs.org/docs/app/building-your-application/routing/error-handling):
1. Create an `error.tsx` file in the `app` folder.
2. Inside your component function, add `useLogger` to send the error to Axiom. For example:
```tsx [expandable]
"use client";
import NavTable from "@/components/NavTable";
import { LogLevel } from "@axiomhq/logging";
import { useLogger } from "@/lib/axiom/client";
import { usePathname } from "next/navigation";
export default function ErrorPage({
error,
}: {
error: Error & { digest?: string };
}) {
const pathname = usePathname();
const log = useLogger({ source: "error.tsx" });
let status = error.message == "Invalid URL" ? 404 : 500;
log.log(LogLevel.error, error.message, {
error: error.name,
cause: error.cause,
stack: error.stack,
digest: error.digest,
request: {
host: window.location.href,
path: pathname,
statusCode: status,
},
});
return (
Ops! An Error has occurred:{" "}
`{error.message}`
);
}
```
### Advanced customizations [#advanced-customizations]
This section describes some advanced customizations.
#### Proxy for client-side usage [#proxy-for-client-side-usage]
Instead of sending logs directly to Axiom, you can send them to a proxy endpoint in your Next.js app. This is useful if you don’t want to expose the Axiom API token to the client or if you want to send the logs from the client to transports on your server.
1. Create a `client.ts` file in the `lib/axiom` folder with the following content:
```ts lib/axiom/client.ts [expandable]
'use client';
import { Logger, ProxyTransport } from '@axiomhq/logging';
import { createUseLogger, createWebVitalsComponent } from '@axiomhq/react';
export const logger = new Logger({
transports: [
new ProxyTransport({ url: '/api/axiom', autoFlush: true }),
],
});
const useLogger = createUseLogger(logger);
const WebVitals = createWebVitalsComponent(logger);
export { useLogger, WebVitals };
```
2. In the `/app/api/axiom` folder, create a `route.ts` file with the following content. This example uses `/api/axiom` as the Axiom proxy path.
```ts /app/api/axiom/route.ts
import { logger } from "@/lib/axiom/server";
import { createProxyRouteHandler } from "@axiomhq/nextjs";
export const POST = createProxyRouteHandler(logger);
```
For more information on React client side helpers, see [React](/send-data/react).
#### Customize data reports sent to Axiom [#customize-data-reports-sent-to-axiom]
To customize the reports sent to Axiom, use the `onError` and `onSuccess` functions that the `createAxiomRouteHandler` function accepts in the configuration object.
In the `lib/axiom/server.ts` file, use the `transformRouteHandlerErrorResult` and `transformRouteHandlerSuccessResult` functions to customize the data sent to Axiom by adding fields to the report object:
```ts [expandable]
import { Logger, AxiomJSTransport } from '@axiomhq/logging';
import {
createAxiomRouteHandler,
getLogLevelFromStatusCode,
nextJsFormatters,
transformRouteHandlerErrorResult,
transformRouteHandlerSuccessResult
} from '@axiomhq/nextjs';
/* ... your logger setup ... */
export const withAxiom = createAxiomRouteHandler(logger, {
onError: (error) => {
if (error.error instanceof Error) {
logger.error(error.error.message, error.error);
}
const [message, report] = transformRouteHandlerErrorResult(error);
report.customField = "customValue";
report.request.searchParams = error.req.nextUrl.searchParams;
logger.log(getLogLevelFromStatusCode(report.statusCode), message, report);
logger.flush();
},
onSuccess: (data) => {
const [message, report] = transformRouteHandlerSuccessResult(data);
report.customField = "customValue";
report.request.searchParams = data.req.nextUrl.searchParams;
logger.info(message, report);
logger.flush();
},
});
```
Changing the `transformSuccessResult()` or `transformErrorResult()` functions can change the shape of your data. This can affect dashboards (especially auto-generated dashboards) and other integrations.
Axiom recommends you add fields on top of the ones returned by the default `transformSuccessResult()` or `transformErrorResult()` functions, without replacing the default fields.
Alternatively, create your own `transformSuccessResult()` or `transformErrorResult()` functions:
```ts [expandable]
import { Logger, AxiomJSTransport } from '@axiomhq/logging';
import {
createAxiomRouteHandler,
getLogLevelFromStatusCode,
nextJsFormatters,
transformRouteHandlerErrorResult,
transformRouteHandlerSuccessResult
} from '@axiomhq/nextjs';
/* ... your logger setup ... */
export const transformSuccessResult = (
data: SuccessData
): [message: string, report: Record] => {
const report = {
request: {
type: "request",
method: data.req.method,
url: data.req.url,
statusCode: data.res.status,
durationMs: data.end - data.start,
path: new URL(data.req.url).pathname,
endTime: data.end,
startTime: data.start,
},
};
return [
`${data.req.method} ${report.request.path} ${
report.request.statusCode
} in ${report.request.endTime - report.request.startTime}ms`,
report,
];
};
export const transformRouteHandlerErrorResult = (data: ErrorData): [message: string, report: Record] => {
const statusCode = data.error instanceof Error ? getNextErrorStatusCode(data.error) : 500;
const report = {
request: {
startTime: new Date().getTime(),
endTime: new Date().getTime(),
path: data.req.nextUrl.pathname ?? new URL(data.req.url).pathname,
method: data.req.method,
host: data.req.headers.get('host'),
userAgent: data.req.headers.get('user-agent'),
scheme: data.req.url.split('://')[0],
ip: data.req.headers.get('x-forwarded-for'),
region: getRegion(data.req),
statusCode: statusCode,
},
};
return [
`${data.req.method} ${report.request.path} ${report.request.statusCode} in ${report.request.endTime - report.request.startTime}ms`,
report,
];
};
export const withAxiom = createAxiomRouteHandler(logger, {
onError: (error) => {
if (error.error instanceof Error) {
logger.error(error.error.message, error.error);
}
const [message, report] = transformRouteHandlerErrorResult(error);
report.customField = "customValue";
report.request.searchParams = error.req.nextUrl.searchParams;
logger.log(getLogLevelFromStatusCode(report.statusCode), message, report);
logger.flush();
},
onSuccess: (data) => {
const [message, report] = transformRouteHandlerSuccessResult(data);
report.customField = "customValue";
report.request.searchParams = data.req.nextUrl.searchParams;
logger.info(message, report);
logger.flush();
},
});
```
#### Change the log level from Next.js built-in function errors [#change-the-log-level-from-nextjs-built-in-function-errors]
By default, Axiom uses the following log levels:
* Errors thrown by the `redirect()` function are logged as `info`.
* Errors thrown by the `forbidden()`, `notFound()` and `unauthorized()` functions are logged as `warn`.
To customize this behavior, provide a custom `logLevelByStatusCode()` function when logging errors from your route handler:
```ts [expandable]
import { Logger, AxiomJSTransport, LogLevel } from '@axiomhq/logging';
import {
createAxiomRouteHandler,
nextJsFormatters,
transformRouteHandlerErrorResult,
} from '@axiomhq/nextjs';
/* ... your logger setup ... */
const getLogLevelFromStatusCode = (statusCode: number) => {
if (statusCode >= 300 && statusCode < 400) {
return LogLevel.info;
} else if (statusCode >= 400 && statusCode < 500) {
return LogLevel.warn;
}
return LogLevel.error;
};
export const withAxiom = createAxiomRouteHandler(logger, {
onError: (error) => {
if (error.error instanceof Error) {
logger.error(error.error.message, error.error);
}
const [message, report] = transformRouteHandlerErrorResult(error);
report.customField = 'customValue';
report.request.searchParams = error.req.nextUrl.searchParams;
logger.log(getLogLevelFromStatusCode(report.statusCode), message, report);
logger.flush();
}
});
```
Internally, the status code gets captured in the `transformErrorResult()` function using a `getNextErrorStatusCode()` function. To compose these functions yourself, create your own `getNextErrorStatusCode()` function and inject the result into the `transformErrorResult()` report.
```ts [expandable]
import { Logger, AxiomJSTransport, LogLevel } from '@axiomhq/logging';
import {
createAxiomRouteHandler,
nextJsFormatters,
transformRouteHandlerErrorResult,
} from '@axiomhq/nextjs';
import { isRedirectError } from 'next/dist/client/components/redirect-error';
import { isHTTPAccessFallbackError } from 'next/dist/client/components/http-access-fallback/http-access-fallback';
import axiomClient from '@/lib/axiom/axiom';
export const logger = new Logger({
transports: [
new AxiomJSTransport({ axiom: axiomClient, dataset: process.env.NEXT_PUBLIC_AXIOM_DATASET! }),
],
formatters: nextJsFormatters,
});
export const getNextErrorStatusCode = (error: Error & { digest?: string }) => {
if (!error.digest) {
return 500;
}
if (isRedirectError(error)) {
return parseInt(error.digest.split(';')[3]);
} else if (isHTTPAccessFallbackError(error)) {
return parseInt(error.digest.split(';')[1]);
}
};
const getLogLevelFromStatusCode = (statusCode: number) => {
if (statusCode >= 300 && statusCode < 400) {
return LogLevel.info;
} else if (statusCode >= 400 && statusCode < 500) {
return LogLevel.warn;
}
return LogLevel.error;
};
export const withAxiom = createAxiomRouteHandler(logger, {
onError: (error) => {
if (error.error instanceof Error) {
logger.error(error.error.message, error.error);
}
const [message, report] = transformRouteHandlerErrorResult(error);
const statusCode = error.error instanceof Error ? getNextErrorStatusCode(error.error) : 500;
report.request.statusCode = statusCode;
report.customField = 'customValue';
report.request.searchParams = error.req.nextUrl.searchParams;
logger.log(getLogLevelFromStatusCode(report.statusCode), message, report);
logger.flush();
},
});
```
### Server execution context [#server-execution-context]
The `serverContextFieldsFormatter` function included in the `nextJsFormatters` adds the server execution context to the logs, this is useful to have information about the scope where the logs were generated.
By default, the `createAxiomRouteHandler` function adds a `request_id` field to the logs using this server context and the server context fields formatter.
#### Route handlers server context [#route-handlers-server-context]
The `createAxiomRouteHandler` accepts a `store` field in the configuration object. The store can be a map, an object, or a function that accepts a request and context. It returns a map or an object.
The fields in the store are added to the `fields` object of the log report. For example, you can use this to add a `trace_id` field to every log report within the same function execution in the route handler.
```ts [expandable]
import { Logger, AxiomJSTransport } from '@axiomhq/logging';
import { createAxiomRouteHandler, nextJsFormatters } from '@axiomhq/nextjs';
import { NextRequest } from 'next/server';
import axiomClient from '@/lib/axiom/axiom';
export const logger = new Logger({
transports: [
new AxiomJSTransport({ axiom: axiomClient, dataset: process.env.NEXT_PUBLIC_AXIOM_DATASET! }),
],
formatters: nextJsFormatters,
});
export const withAxiom = createAxiomRouteHandler(logger, {
store: (req: NextRequest) => {
return {
request_id: crypto.randomUUID(),
trace_id: req.headers.get('x-trace-id'),
};
},
});
```
#### Server context on arbitrary functions [#server-context-on-arbitrary-functions]
You can also add the server context to any function that runs in the server. For example, server actions, proxy (formerly middleware), and server components.
```ts [expandable]
"use server";
import { runWithServerContext } from "@axiomhq/nextjs";
export const serverAction = () =>
runWithServerContext(() => {
return "Hello World";
}, { request_id: crypto.randomUUID() });
```
```ts proxy.ts [expandable]
import { logger } from "@/lib/axiom/server";
import { runWithServerContext, transformMiddlewareRequest } from "@axiomhq/nextjs";
import { NextResponse } from "next/server";
import type { NextFetchEvent, NextRequest } from "next/server";
export const proxy = (request: NextRequest, event: NextFetchEvent) =>
runWithServerContext(() => {
// trace_id will be added to the log fields
logger.info(...transformMiddlewareRequest(request));
// trace_id will also be added to the log fields
logger.info("Hello from proxy");
event.waitUntil(logger.flush());
return NextResponse.next();
}, { trace_id: request.headers.get("x-trace-id") });
```
## Use next-axiom library [#use-next-axiom-library]
The next-axiom library is an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/next-axiom).
### Install next-axiom [#install-next-axiom]
1. In your terminal, go to the root folder of your Next.js app and run the following command:
```sh
npm install --save next-axiom
```
2. Add the following environment variables to your Next.js app:
* `NEXT_PUBLIC_AXIOM_DATASET` is the name of the Axiom dataset where you want to send data.
* `NEXT_PUBLIC_AXIOM_TOKEN` is the Axiom API token you have generated.
3. In the `next.config.ts` file, wrap your Next.js configuration in `withAxiom`:
```js
const { withAxiom } = require("next-axiom");
module.exports = withAxiom({
// Your existing configuration.
});
```
### Capture traffic requests [#capture-traffic-requests-1]
To capture traffic requests, create a `middleware.ts` file in the root folder of your Next.js app:
```ts [expandable]
import { Logger } from 'next-axiom'
import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'
export async function middleware(request: NextRequest, event: NextFetchEvent) {
const logger = new Logger({ source: 'middleware' }); // traffic, request
logger.middleware(request)
event.waitUntil(logger.flush())
return NextResponse.next()
}
// For more information, see Matching Paths below
export const config = {
}
```
Next.js 16 renamed middleware to proxy. If your app uses Next.js 16 or later, name the file `proxy.ts` and the exported function `proxy` instead. `middleware.ts` still works in Next.js 16, but it's deprecated.
### Web Vitals [#web-vitals-1]
To send Web Vitals to Axiom, add the `AxiomWebVitals` component from next-axiom to the `app/layout.tsx` file:
```ts [expandable]
import { AxiomWebVitals } from "next-axiom";
export default function RootLayout() {
return (
...
...
);
}
```
Web Vitals are only sent from production deployments.
### Logs [#logs-1]
Send logs to Axiom from different parts of your app. Each log function call takes a message and an optional `fields` object.
```ts [expandable]
log.debug("Login attempt", { user: "j_doe", status: "success" }); // Results in {"message": "Login attempt", "fields": {"user": "j_doe", "status": "success"}}
log.info("Payment completed", { userID: "123", amount: "25USD" });
log.warn("API rate limit exceeded", {
endpoint: "/users/1",
rateLimitRemaining: 0,
});
log.error("System Error", { code: "500", message: "Internal server error" });
```
#### Route handlers [#route-handlers-1]
Wrap your route handlers in `withAxiom` to add a logger to your request and log exceptions automatically:
```ts [expandable]
import { withAxiom, AxiomRequest } from "next-axiom";
export const GET = withAxiom((req: AxiomRequest) => {
req.log.info("Login function called");
// You can create intermediate loggers
const log = req.log.with({ scope: "user" });
log.info("User logged in", { userId: 42 });
return NextResponse.json({ hello: "world" });
});
```
#### Client components [#client-components-1]
To send logs from client components, add `useLogger` from next-axiom to your component:
```ts [expandable]
"use client";
import { useLogger } from "next-axiom";
export default function ClientComponent() {
const log = useLogger();
log.debug("User logged in", { userId: 42 });
return Logged in ;
}
```
#### Server components [#server-components-1]
To send logs from server components, add `Logger` from next-axiom to your component, and call flush before returning:
```ts [expandable]
import { Logger } from "next-axiom";
export default async function ServerComponent() {
const log = new Logger();
log.info("User logged in", { userId: 42 });
// ...
await log.flush();
return Logged in ;
}
```
#### Log levels [#log-levels]
The log level defines the lowest level of logs sent to Axiom. Choose one of the following levels (from lowest to highest):
* `debug` is the default setting. It means that you send all logs to Axiom.
* `info`
* `warn`
* `error` means that you only send the highest-level logs to Axiom.
* `off` means that you don’t send any logs to Axiom.
For example, to send all logs except for debug logs to Axiom:
```sh
export NEXT_PUBLIC_AXIOM_LOG_LEVEL=info
```
### Capture errors [#capture-errors-1]
To capture routing errors, use the [error handling mechanism of Next.js](https://nextjs.org/docs/app/building-your-application/routing/error-handling):
1. Go to the `app` folder.
2. Create an `error.tsx` file.
3. Inside your component function, add `useLogger` from next-axiom to send the error to Axiom. For example:
```ts [expandable]
"use client";
import NavTable from "@/components/NavTable";
import { LogLevel } from "@/next-axiom/logger";
import { useLogger } from "next-axiom";
import { usePathname } from "next/navigation";
export default function ErrorPage({
error,
}: {
error: Error & { digest?: string };
}) {
const pathname = usePathname();
const log = useLogger({ source: "error.tsx" });
let status = error.message == "Invalid URL" ? 404 : 500;
log.logHttpRequest(
LogLevel.error,
error.message,
{
host: window.location.href,
path: pathname,
statusCode: status,
},
{
error: error.name,
cause: error.cause,
stack: error.stack,
digest: error.digest,
}
);
return (
Ops! An Error has occurred:{" "}
`{error.message}`
);
}
```
### Extend logger [#extend-logger]
To extend the logger, use `log.with` to create an intermediate logger. For example:
```ts [expandable]
const logger = useLogger().with({ userId: 42 });
logger.info("Hi"); // will ingest { ..., "message": "Hi", "fields" { "userId": 42 }}
```
---
# Send OpenTelemetry data to Axiom
Source: https://axiom.co/docs/send-data/opentelemetry
OpenTelemetry (OTel) is a set of APIs, libraries, and agents to capture distributed traces and metrics from your app. It’s a Cloud Native Computing Foundation (CNCF) project that was started to create a unified solution for service and app performance monitoring.
The OpenTelemetry project has published strong specifications for the three main pillars of observability: logs, traces, and metrics. These schemas are supported by all tools and services that support interacting with OpenTelemetry. Axiom supports OpenTelemetry natively on an API level, allowing you to connect any existing OpenTelemetry shipper, library, or tool to Axiom for sending data.
OpenTelemetry-compatible events flow into Axiom, where they’re organized into datasets for easy segmentation. Users can create a dataset to receive OpenTelemetry data and obtain an API token for ingestion. Axiom provides comprehensive observability through browsing, querying, dashboards, and alerting of OpenTelemetry data.
| OpenTelemetry component | Support |
| ------------------------------------------------------------------ | ------- |
| [Logs](https://opentelemetry.io/docs/concepts/signals/logs/) | ✓ |
| [Traces](https://opentelemetry.io/docs/concepts/signals/traces/) | ✓ |
| [Metrics](https://opentelemetry.io/docs/concepts/signals/metrics/) | ✓ |
You must use a different, dedicated dataset for each OTel component. When you create a dataset, select **Events** for logs or traces data, and select **Metrics** for metrics data. For more information, see [Create dataset](/reference/datasets#create-dataset).
## OpenTelemetry Collector [#opentelemetry-collector]
Configuring the OpenTelemetry collector is as simple as creating an HTTP exporter that sends data to the Axiom API together with headers to set the dataset and API token.
```yaml
exporters:
otlphttp:
compression: zstd
endpoint: https://AXIOM_DOMAIN
headers:
authorization: Bearer API_TOKEN
x-axiom-dataset: DATASET_NAME
service:
pipelines:
logs:
receivers:
- otlp
processors:
- memory_limiter
- batch
exporters:
- otlphttp
```
```yaml
exporters:
otlphttp:
compression: zstd
endpoint: https://AXIOM_DOMAIN
headers:
authorization: Bearer API_TOKEN
x-axiom-dataset: DATASET_NAME
service:
pipelines:
traces:
receivers:
- otlp
processors:
- memory_limiter
- batch
exporters:
- otlphttp
```
```yaml
processors:
batch:
send_batch_max_size: 8192
exporters:
otlphttp:
compression: zstd
endpoint: https://AXIOM_DOMAIN
headers:
authorization: Bearer API_TOKEN
x-axiom-metrics-dataset: DATASET_NAME
service:
pipelines:
metrics:
receivers:
- otlp
processors:
- memory_limiter
- batch
exporters:
- otlphttp
```
When configuring metrics, use the `x-axiom-metrics-dataset` header instead of `x-axiom-dataset`.
When using the OTLP/HTTP endpoint, the following endpoint URLs should be used in your SDK exporter OTel configuration.
* Traces: `https://AXIOM_DOMAIN/v1/traces`
* Logs: `https://AXIOM_DOMAIN/v1/logs`
* Metrics: `https://AXIOM_DOMAIN/v1/metrics`
The `/v1/metrics` endpoint only supports the `application/x-protobuf` content type. JSON format isn’t supported for metrics ingestion.
## OpenTelemetry for Go [#opentelemetry-for-go]
The example below configures a Go app using the [OpenTelemetry SDK for Go](https://github.com/open-telemetry/opentelemetry-go) to send OpenTelemetry data to Axiom.
```go
package main
import (
"context" // For managing request-scoped values, cancellation signals, and deadlines.
"crypto/tls" // For configuring TLS options, like certificates.
// OpenTelemetry imports for setting up tracing and exporting telemetry data.
"go.opentelemetry.io/otel" // Core OpenTelemetry APIs for managing tracers.
"go.opentelemetry.io/otel/attribute" // For creating and managing trace attributes.
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" // HTTP trace exporter for OpenTelemetry Protocol (OTLP).
"go.opentelemetry.io/otel/propagation" // For managing context propagation formats.
"go.opentelemetry.io/otel/sdk/resource" // For defining resources that describe an entity producing telemetry.
"go.opentelemetry.io/otel/sdk/trace" // For configuring tracing, like sampling and processors.
semconv "go.opentelemetry.io/otel/semconv/v1.24.0" // Semantic conventions for resource attributes.
)
const (
serviceName = "axiom-go-otel" // Name of the service for tracing.
serviceVersion = "0.1.0" // Version of the service.
otlpEndpoint = "AXIOM_DOMAIN" // OTLP collector endpoint.
bearerToken = "Bearer API_TOKEN" // Authorization token.
deploymentEnvironment = "production" // Deployment environment.
)
func SetupTracer() (func(context.Context) error, error) {
ctx := context.Background()
return InstallExportPipeline(ctx) // Setup and return the export pipeline for telemetry data.
}
func Resource() *resource.Resource {
// Defines resource with service name, version, and environment.
return resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String(serviceName),
semconv.ServiceVersionKey.String(serviceVersion),
attribute.String("environment", deploymentEnvironment),
)
}
func InstallExportPipeline(ctx context.Context) (func(context.Context) error, error) {
// Sets up OTLP HTTP exporter with endpoint, headers, and TLS config.
exporter, err := otlptracehttp.New(ctx,
otlptracehttp.WithEndpoint(otlpEndpoint),
otlptracehttp.WithHeaders(map[string]string{
"Authorization": bearerToken,
"X-AXIOM-DATASET": "DATASET_NAME",
}),
otlptracehttp.WithTLSClientConfig(&tls.Config{}),
)
if err != nil {
return nil, err
}
// Configures the tracer provider with the exporter and resource.
tracerProvider := trace.NewTracerProvider(
trace.WithBatcher(exporter),
trace.WithResource(Resource()),
)
otel.SetTracerProvider(tracerProvider)
// Sets global propagator to W3C Trace Context and Baggage.
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))
return tracerProvider.Shutdown, nil // Returns a function to shut down the tracer provider.
}
```
## OpenTelemetry for Ruby [#opentelemetry-for-ruby]
To send traces to an OpenTelemetry Collector using the [OTLP over HTTP in Ruby](https://github.com/open-telemetry/opentelemetry-ruby), use the `opentelemetry-exporter-otlp-http` gem provided by the OpenTelemetry project.
```bash
require 'opentelemetry/sdk'
require 'opentelemetry/exporter/otlp'
require 'opentelemetry/instrumentation/all'
OpenTelemetry::SDK.configure do |c|
c.service_name = 'ruby-traces' # Set your service name
c.use_all # or specify individual instrumentation you need
c.add_span_processor(
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
OpenTelemetry::Exporter::OTLP::Exporter.new(
endpoint: 'https://AXIOM_DOMAIN/v1/traces',
headers: {
'Authorization' => 'Bearer API_TOKEN',
'X-AXIOM-DATASET' => 'DATASET_NAME'
}
)
)
)
end
```
## OpenTelemetry for Java [#opentelemetry-for-java]
Here is a basic configuration for a Java app that sends traces to an OpenTelemetry Collector using OTLP over HTTP using the [OpenTelemetry Java SDK](https://github.com/open-telemetry/opentelemetry-java):
```java
package com.example;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import java.util.concurrent.TimeUnit;
public class OtelConfiguration {
// OpenTelemetry configuration
private static final String SERVICE_NAME = "SERVICE_NAME";
private static final String SERVICE_VERSION = "SERVICE_VERSION";
private static final String OTLP_ENDPOINT = "https://AXIOM_DOMAIN/v1/traces";
private static final String BEARER_TOKEN = "Bearer API_TOKEN";
private static final String AXIOM_DATASET = "DATASET_NAME";
public static OpenTelemetry initializeOpenTelemetry() {
// Create a Resource with service name and version
Resource resource = Resource.getDefault()
.merge(Resource.create(Attributes.of(
AttributeKey.stringKey("service.name"), SERVICE_NAME,
AttributeKey.stringKey("service.version"), SERVICE_VERSION
)));
// Create an OTLP/HTTP span exporter
OtlpHttpSpanExporter spanExporter = OtlpHttpSpanExporter.builder()
.setEndpoint(OTLP_ENDPOINT)
.addHeader("Authorization", BEARER_TOKEN)
.addHeader("X-Axiom-Dataset", AXIOM_DATASET)
.build();
// Create a BatchSpanProcessor with the OTLP/HTTP exporter
SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter)
.setScheduleDelay(100, TimeUnit.MILLISECONDS)
.build())
.setResource(resource)
.build();
// Build and register the OpenTelemetry SDK
OpenTelemetrySdk openTelemetry = OpenTelemetrySdk.builder()
.setTracerProvider(sdkTracerProvider)
.buildAndRegisterGlobal();
// Add a shutdown hook to properly close the SDK
Runtime.getRuntime().addShutdownHook(new Thread(sdkTracerProvider::close));
return openTelemetry;
}
}
```
## OpenTelemetry for .NET [#opentelemetry-for-net]
You can send traces to Axiom using the [OpenTelemetry .NET SDK](https://github.com/open-telemetry/opentelemetry-dotnet) by configuring an OTLP HTTP exporter in your .NET app. Here is a simple example:
```csharp
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
using System.Diagnostics;
using System.Reflection;
// Class to configure OpenTelemetry tracing
public static class TracingConfiguration
{
// Declares an ActivitySource for creating tracing activities
private static readonly ActivitySource ActivitySource = new("MyCustomActivitySource");
// Configures OpenTelemetry with custom settings and instrumentation
public static void ConfigureOpenTelemetry()
{
// Retrieve the service name and version from the executing assembly metadata
var serviceName = Assembly.GetExecutingAssembly().GetName().Name ?? "UnknownService";
var serviceVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "UnknownVersion";
// Setting up the tracer provider with various configurations
Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(
// Set resource attributes including service name and version
ResourceBuilder.CreateDefault().AddService(serviceName, serviceVersion: serviceVersion)
.AddAttributes(new[] { new KeyValuePair("environment", "development") }) // Additional attributes
.AddTelemetrySdk() // Add telemetry SDK information to the traces
.AddEnvironmentVariableDetector()) // Detect resource attributes from environment variables
.AddSource(ActivitySource.Name) // Add the ActivitySource defined above
.AddAspNetCoreInstrumentation() // Add automatic instrumentation for ASP.NET Core
.AddHttpClientInstrumentation() // Add automatic instrumentation for HttpClient requests
.AddOtlpExporter(options => // Configure the OTLP exporter
{
options.Endpoint = new Uri("https://AXIOM_DOMAIN/v1/traces"); // Set the endpoint for the exporter
options.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf; // Set the protocol
options.Headers = "Authorization=Bearer API_TOKEN, X-Axiom-Dataset=DATASET_NAME"; // Update API token and dataset
})
.Build(); // Build the tracer provider
}
// Method to start a new tracing activity with an optional activity kind
public static Activity? StartActivity(string activityName, ActivityKind kind = ActivityKind.Internal)
{
// Starts and returns a new activity if sampling allows it, otherwise returns null
return ActivitySource.StartActivity(activityName, kind);
}
}
```
## OpenTelemetry for Python [#opentelemetry-for-python]
You can send traces to Axiom using the [OpenTelemetry Python SDK](https://github.com/open-telemetry/opentelemetry-python) by configuring an OTLP HTTP exporter in your Python app. Here is a simple example:
```python
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.resources import Resource, SERVICE_NAME
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
# Define the service name resource for the tracer.
resource = Resource(attributes={
SERVICE_NAME: "NAME_OF_SERVICE" # Replace `NAME_OF_SERVICE` with the name of the service you want to trace.
})
# Create a TracerProvider with the defined resource for creating tracers.
provider = TracerProvider(resource=resource)
# Configure the OTLP/HTTP Span Exporter with Axiom headers and endpoint. Replace `API_TOKEN` with your Axiom API key, and replace `DATASET_NAME` with the name of the Axiom dataset where you want to send data.
otlp_exporter = OTLPSpanExporter(
endpoint="https://AXIOM_DOMAIN/v1/traces",
headers={
"Authorization": "Bearer API_TOKEN",
"X-Axiom-Dataset": "DATASET_NAME"
}
)
# Create a BatchSpanProcessor with the OTLP exporter to batch and send trace spans.
processor = BatchSpanProcessor(otlp_exporter)
provider.add_span_processor(processor)
# Set the TracerProvider as the global tracer provider.
trace.set_tracer_provider(provider)
# Define a tracer for external use in different parts of the app.
service1_tracer = trace.get_tracer("service1")
```
## OpenTelemetry for Node [#opentelemetry-for-node]
You can send traces to Axiom using the [OpenTelemetry Node SDK](https://github.com/open-telemetry/opentelemetry-js) by configuring an OTLP HTTP exporter in your Node app. Here is a simple example:
```js
const opentelemetry = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-proto');
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
// Initialize OTLP trace exporter with the URL and headers for the Axiom API
const traceExporter = new OTLPTraceExporter({
url: 'https://AXIOM_DOMAIN/v1/traces', // Axiom API endpoint for trace data
headers: {
'Authorization': 'Bearer API_TOKEN', // Replace API_TOKEN with your actual API token
'X-Axiom-Dataset': 'DATASET_NAME' // Replace DATASET_NAME with your dataset
},
});
// Define the resource attributes, in this case, setting the service name for the traces
const resource = new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'node traces', // Name for the tracing service
});
// Create a NodeSDK instance with the configured span processor, resource, and auto-instrumentations
const sdk = new opentelemetry.NodeSDK({
spanProcessor: new BatchSpanProcessor(traceExporter), // Use BatchSpanProcessor for batching and sending traces
resource: resource, // Attach the defined resource to provide additional context
instrumentations: [getNodeAutoInstrumentations()], // Automatically instrument common Node.js modules
});
// Start the OpenTelemetry SDK
sdk.start();
```
## OpenTelemetry for Cloudflare Workers [#opentelemetry-for-cloudflare-workers]
Configure OpenTelemetry in Cloudflare Workers to send telemetry data to Axiom using the [OTel CF Worker package](https://github.com/evanderkoogh/otel-cf-workers). Here is an example exporter configuration:
```js
// index.ts
import { trace } from '@opentelemetry/api';
import { instrument, ResolveConfigFn } from '@microlabs/otel-cf-workers';
export interface Env {
AXIOM_TOKEN: string,
AXIOM_DATASET: string,
AXIOM_DOMAIN: string
}
const handler = {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise {
await fetch('https://cloudflare.com');
const greeting = "Welcome to Axiom Cloudflare instrumentation";
trace.getActiveSpan()?.setAttribute('greeting', greeting);
ctx.waitUntil(fetch('https://workers.dev'));
return new Response(`${greeting}!`);
},
};
const config: ResolveConfigFn = (env: Env, _trigger) => {
return {
exporter: {
url: `https://${env.AXIOM_DOMAIN}/v1/traces`,
headers: {
'Authorization': `Bearer ${env.AXIOM_TOKEN}`,
'X-Axiom-Dataset': `${env.AXIOM_DATASET}`
},
},
service: { name: 'axiom-cloudflare-workers' },
};
};
export default instrument(handler, config);
```
### Requirements for log level fields [#requirements-for-log-level-fields]
The Stream and Query tabs allow you to easily detect warnings and errors in your logs by highlighting the severity of log entries in different colors. As a prerequisite, specify the log level in the data you send to Axiom. For Open Telemetry logs, specify the log level in the following fields:
* `severity`
* `severityNumber`
* `severityText`
## Additional resources [#additional-resources]
For further guidance on integrating OpenTelemetry with Axiom, explore the following guides:
* [Node.js OpenTelemetry guide](/guides/opentelemetry-nodejs)
* [Python OpenTelemetry guide](/guides/opentelemetry-python)
* [Golang OpenTelemetry guide](/guides/opentelemetry-go)
* [Cloudflare Workers guide](/guides/opentelemetry-cloudflare-workers)
* [Ruby on Rails OpenTelemetry guide](/guides/opentelemetry-ruby)
* [.NET OpenTelemetry guide](/guides/opentelemetry-dotnet)
---
# Send PgBouncer metrics to Axiom
Source: https://axiom.co/docs/send-data/pgbouncer
PgBouncer tracks detailed pool and query statistics, but it only exposes them through its admin console (`SHOW STATS`, `SHOW POOLS`) over the PostgreSQL wire protocol — there’s no native Prometheus or OTLP endpoint. To send these metrics to Axiom, run the Prometheus community’s [pgbouncer\_exporter](https://github.com/prometheus-community/pgbouncer_exporter) next to PgBouncer, and an OpenTelemetry Collector that scrapes the exporter and forwards the metrics to Axiom.
The Prometheus receiver is part of the [contrib distribution](https://github.com/open-telemetry/opentelemetry-collector-releases) of the OpenTelemetry Collector (`otelcol-contrib`). It isn’t included in the core distribution.
When you create the dataset, select **Metrics** as the dataset type. Metrics require their own dedicated dataset. For more information, see [Create dataset](/reference/datasets#create-dataset).
## Expose PgBouncer stats with pgbouncer\_exporter [#expose-pgbouncer-stats-with-pgbouncer_exporter]
In `pgbouncer.ini`, allow a user to read stats and let the exporter connect:
```ini
stats_users = stats
ignore_startup_parameters = extra_float_digits
```
Then run the exporter, pointing it at PgBouncer’s special `pgbouncer` admin database:
```shell
docker run -d -p 9127:9127 prometheuscommunity/pgbouncer-exporter \
--pgBouncer.connectionString="postgres://stats:PASSWORD@pgbouncer:6432/pgbouncer?sslmode=disable"
```
Replace the connection string with your PgBouncer host, port, and stats user. You can also set it with the `PGBOUNCER_EXPORTER_CONNECTION_STRING` environment variable. The exporter then serves metrics at `http://localhost:9127/metrics`.
## Configure the OpenTelemetry Collector [#configure-the-opentelemetry-collector]
Add a Prometheus receiver that scrapes the exporter and an OTLP/HTTP exporter that sends data to Axiom:
```yaml
receivers:
prometheus:
config:
scrape_configs:
- job_name: pgbouncer
scrape_interval: 15s
static_configs:
- targets: ["pgbouncer-exporter:9127"] # host:port of pgbouncer_exporter
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 80
spike_limit_percentage: 25
batch:
send_batch_max_size: 8192
exporters:
otlphttp:
compression: zstd
endpoint: https://AXIOM_DOMAIN
headers:
authorization: Bearer API_TOKEN
x-axiom-metrics-dataset: DATASET_NAME
service:
pipelines:
metrics:
receivers:
- prometheus
processors:
- memory_limiter
- batch
exporters:
- otlphttp
```
For metrics, use the `x-axiom-metrics-dataset` header instead of `x-axiom-dataset`.
## Query your metrics [#query-your-metrics]
Once data is flowing, query your PgBouncer metrics like any other OTel metrics in Axiom. Pool metrics carry `database` and `user` tags. For example, to chart active client connections per database:
```kusto
`DATASET_NAME`:`pgbouncer_pools_client_active_connections`
| align to $__interval using last
| group by `database` using sum
```
For more information, see [Metrics](/query-data/metrics).
---
# Send data from client-side React apps to Axiom
Source: https://axiom.co/docs/send-data/react
React is a popular open-source JavaScript library developed by Meta for building user interfaces. Known for its component-based architecture and efficient rendering with a virtual DOM, React is widely used by companies of all sizes to create fast, scalable, and dynamic web applications.
This page explains how to use the @axiomhq/react library to send data from your client-side React apps to Axiom.
The @axiomhq/react library is part of the Axiom JavaScript SDK, an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/axiom-js).
* A new or existing React app.
## Install @axiomhq/react library [#install-axiomhqreact-library]
1. In your terminal, go to the root folder of your React app and run the following command:
```sh
npm install --save @axiomhq/logging @axiomhq/react
```
2. Create a `Logger` instance and export the utils. The example below uses the `useLogger` and `WebVitals` components.
```tsx [expandable]
'use client';
import { Logger, AxiomJSTransport } from '@axiomhq/logging';
import { Axiom } from '@axiomhq/js';
import { createUseLogger, createWebVitalsComponent } from '@axiomhq/react';
const axiomClient = new Axiom({
token: process.env.AXIOM_TOKEN!,
});
export const logger = new Logger({
transports: [
new AxiomJSTransport({
axiom: axiomClient,
dataset: process.env.AXIOM_DATASET!,
}),
],
});
const useLogger = createUseLogger(logger);
const WebVitals = createWebVitalsComponent(logger);
export { useLogger, WebVitals };
```
## Send logs from components [#send-logs-from-components]
To send logs from components, use the `useLogger` hook that returns your logger instance.
```tsx
import { useLogger } from "@/lib/axiom/client";
import { useEffect } from "react";
export default function ClientComponent() {
const log = useLogger();
const handleClick = () => log.info("User logged out");
useEffect(() => {
log.info("User logged in", { userId: 42 });
}, []);
return (
Logged in
Log out
);
}
```
## Send Web Vitals [#send-web-vitals]
To send Web Vitals, mount the `WebVitals` component in the root of your React app.
```tsx
import { WebVitals } from "@/lib/axiom/client";
export default function App({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
---
# Reference architectures
Source: https://axiom.co/docs/send-data/reference-architectures
Successfully adopting a new platform at scale requires a robust and reliable data collection strategy. In complex distributed systems, how you collect and forward telemetry is as important as the platform that analyzes it.
While Axiom is flexible and supports dozens of [methods](/send-data/methods), there is a confident, battle-tested recommendation for large-scale environments. This guide outlines Axiom’s recommended architectural patterns using two popular tools: the [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) and [Vector](https://vector.dev/).
These patterns are designed to provide:
* **Resilience:** Ensure no data is lost during network partitions or downstream issues.
* **Enrichment:** Add valuable, consistent metadata to all your telemetry at the source.
* **Performance:** Offload processing and batching from your applications.
* **Governance:** Centrally manage data routing, filtering, and sampling to control costs.
While these patterns are recommended for large-scale environments, Axiom has many other simpler [methods](/send-data/methods) available to send data.
***
## The core patterns [#the-core-patterns]
There are two primary deployment patterns for a collector: the Agent model and the Aggregator model. Most large organizations use a hybrid of both.
Most organizations find it easy to send data to Axiom. If you are having trouble, please [contact](https://axiom.co/contact) the Axiom team for help. We can provide ad-hoc guidance or design tailored implementation services for larger projects.
### Agent pattern [#agent-pattern]
In this model, a lightweight collector runs as an agent on every host or as a sidecar in every pod. It’s responsible for collecting telemetry from applications on that specific node and forwarding it directly to Axiom.
**Best for:** Capturing rich, host-specific metadata (for example, `k8s.pod.name`, `host.id`) and providing a resilient, decentralized collection point with local buffering.
```mermaid
graph TD
subgraph "Node/VM 1"
App1[Application] --> Agent1[Collector Agent];
end
subgraph "Node/VM 2"
App2[Application] --> Agent2[Collector Agent];
end
subgraph "Node/VM 3"
App3[Application] --> Agent3[Collector Agent];
end
Agent1 --> Axiom[(Axiom)];
Agent2 --> Axiom;
Agent3 --> Axiom;
```
### Aggregator pattern [#aggregator-pattern]
In this model, a separate, horizontally scalable pool of collectors acts as a centralized aggregation layer. Applications and agents send their data to this layer, which then handles final processing, enrichment, and forwarding to Axiom.
**Best for:** Centralized control over data routing and filtering, managing data from sources that can't run an agent (for example, third-party APIs, cloud provider log streams), and simplifying management by maintaining a small fleet of aggregators instead of thousands of agents.
```mermaid
flowchart LR
subgraph Sources["Data Sources"]
direction TB
App[Application]
Agent[Collector Agent]
CloudLogs[Cloud Provider Logs]
end
subgraph AggLayer["Aggregation Layer"]
Pool[Collector Aggregator Pool]
end
App --> Pool
Agent --> Pool
CloudLogs --> Pool
Pool --> Axiom[(Axiom)]
```
***
## Tool recommendations [#tool-recommendations]
Both the OpenTelemetry Collector and Vector are excellent choices that can be deployed in either an Agent or Aggregator pattern. The best choice depends on your team’s existing ecosystem and primary use case.
### OpenTelemetry Collector [#opentelemetry-collector]
The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) is the CNCF-backed, vendor-neutral standard for collecting and processing telemetry. It’s the ideal choice when your organization is standardizing on the OpenTelemetry framework for all signals (logs, metrics, and traces).
**Use the OTel Collector when:**
* You are instrumenting your applications with OpenTelemetry SDKs.
* You need to process traces, metrics, and logs in a single, unified pipeline.
* You want to align with the fastest-growing standard in the observability community.
Axiom natively supports the OpenTelemetry Line Protocol (OTLP). Configuring the collector to send data to Axiom is simple.
**Example Collector Configuration (`otel-collector-config.yaml`):**
```yaml
exporters:
# Exporter for logs and traces
otlphttp:
compression: zstd
endpoint: https://AXIOM_DOMAIN
headers:
authorization: Bearer API_TOKEN
x-axiom-dataset: DATASET_NAME
# Exporter for metrics
otlphttp/metrics:
compression: zstd
endpoint: https://AXIOM_DOMAIN
headers:
authorization: Bearer API_TOKEN
x-axiom-metrics-dataset: DATASET_NAME
service:
pipelines:
traces:
receivers:
- otlp
processors:
- memory_limiter
- batch
exporters:
- otlphttp
logs:
receivers:
- otlp
processors:
- memory_limiter
- batch
exporters:
- otlphttp
metrics:
receivers:
- otlp
processors:
memory_limiter:
batch:
send_batch_max_size: 8192
exporters:
- otlphttp/metrics
```
When configuring metrics, use the `x-axiom-metrics-dataset` header instead of `x-axiom-dataset`.
### Vector [#vector]
[Vector](https://vector.dev/) is a high-performance, Rust-based observability data pipeline. It excels at log collection and transformation, offering a powerful domain-specific language (VRL) for complex data manipulation.
**Use Vector when:**
* Your primary focus is on logs from a wide variety of sources (files, syslog, APIs).
* You need to perform complex parsing, filtering, enrichment, or redaction on your event data.
* You require an extremely lightweight and memory-efficient agent for edge deployments.
Vector has a [native sink](https://vector.dev/docs/reference/configuration/sinks/axiom/) for Axiom, making configuration straightforward.
**Example Vector Configuration (`vector.toml`):**
```toml
[sources.VECTOR_SOURCE_ID]
type = "file"
include = ["PATH_TO_LOGS"]
[sinks.SINK_ID]
type = "axiom"
inputs = ["VECTOR_SOURCE_ID"]
token = "API_TOKEN"
dataset = "DATASET_NAME"
```
Replace `VECTOR_SOURCE_ID` with the Vector source ID.
Replace `PATH_TO_LOGS` with the path to the log files. For example, `/var/log/**/*.log`.
Replace `SINK_ID` with the sink ID.
***
## Summary [#summary]
| Aspect | OpenTelemetry Collector | Vector |
| :------------------- | :--------------------------------------------- | :------------------------------------------------------ |
| **Primary Use Case** | Unified pipeline for logs, metrics, and traces | High-performance log collection and transformation |
| **Ecosystem** | CNCF and OpenTelemetry standard | Standalone, broad source/sink compatibility |
| **Transformation** | Processors (limited for complex logic) | Vector Remap Language (VRL) for advanced logic |
| **Performance** | Excellent | Excellent, often with lower resource footprint for logs |
Both are first-class choices for sending data to Axiom. Your decision should be based on whether you need a unified OTel pipeline or a specialized, high-performance log processing tool.
### What’s next? [#whats-next]
* Explore the complete range of options for sending data in the [Methods](/send-data/methods) page.
* For direct ingestion, see the [Axiom REST API](/restapi/introduction).
---
# Send logs from Render to Axiom
Source: https://axiom.co/docs/send-data/render
Render is a unified cloud to build and run all your apps and websites. Axiom provides complete visibility into your Render projects, allowing you to monitor the behavior of your websites and apps.
* [Create an account on Render](https://dashboard.render.com/login).
## Setup [#setup]
### Create endpoint in Axiom [#create-endpoint-in-axiom]
### Create log stream in Render [#create-log-stream-in-render]
In Render, create a log stream. For more information, see the [Render documentation](https://docs.render.com/log-streams). As the log endpoint, use the target URL generated in Axiom in the procedure above.
Back in your Axiom dataset, you see logs coming from Render.
---
# Send data from syslog to Axiom over a secure connection
Source: https://axiom.co/docs/send-data/secure-syslog
The Secure Syslog endpoint allows you to send syslog data to Axiom over a secure connection. With the Secure Syslog endpoint, the logs you send to Axiom are encrypted using SSL/TLS.
## Syslog limitations and recommended alternatives [#syslog-limitations-and-recommended-alternatives]
Syslog is an outdated protocol. Some of the limitations are the following:
* Lack of error reporting and feedback mechanisms when issues occur.
* Inability to gracefully end the connection. This can result in missing data.
For a more reliable and modern logging experience, consider using tools like [Vector](https://vector.dev/) to receive syslog messages and [forward them to Axiom](/send-data/vector). This approach bypasses many of syslog’s limitations.
## Configure syslog client [#configure-syslog-client]
1. Ensure the syslog client meets the following requirements:
* **Message size limit:** Axiom currently enforces a 64KB per-message size limit. This is in line with RFC5425 guidelines. Any message exceeding the limit causes the connection to close because Axiom doesn’t support ingesting truncated messages.
* **TLS requirement:** Axiom only supports syslog over TLS, specifically following RFC5425. Ensure you have certificate authority certificates installed in your environment to validate Axiom’s SSL certificate. For example, on Ubuntu/Debian systems, install the `ca-certificates` package. For more information, see the [RFC Series documentation](https://www.rfc-editor.org/rfc/rfc5425).
* **Port requirements:** TCP log messages are sent on TCP port `6514`.
2. Configure your syslog client to connect to Axiom. Use the target URL for the endpoint you have generated in Axiom by following the procedure above. For example, `https://opbizplsf8klnw.ingress.axiom.co`. Consider this URL as secret information because syslog doesn’t support additional authentication such as API tokens.
## Troubleshooting [#troubleshooting]
Ensure your messages conform to the size limit and TLS requirements. If the connection is frequently re-established and messages are rejected, the issue can be the size of the messages or other formatting issues.
---
# Send data from Serverless to Axiom
Source: https://axiom.co/docs/send-data/serverless
Serverless is an open-source web framework for building apps on AWS Lambda. Sending event data from your Serverless apps to Axiom allows you to gain deep insights into your apps’ performance and behavior without complex setup or configuration.
To send data from Serverless to Axiom:
1. [Create an Axiom account](https://app.axiom.co/register).
2. [Create an API token in Axiom](/reference/tokens) with **Ingest**, **Query**, **Datasets**, **Dashboards**, and **Monitors** permissions.
3. [Create a Serverless account](https://app.serverless.com/).
4. Set up your app with Serverless using the [Serverless documentation](https://www.serverless.com/framework/docs/getting-started).
5. Configure Axiom in your Serverless Framework Service using the [Serverless documentation](https://www.serverless.com/framework/docs/guides/observability/axiom).
---
# Send StatsD metrics to Axiom
Source: https://axiom.co/docs/send-data/statsd
StatsD is a simple push-based protocol for app metrics like counters, gauges, and timers. Axiom doesn’t provide a native StatsD listener. Instead, you send StatsD metrics to Axiom with the [StatsD receiver](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/statsdreceiver) of the OpenTelemetry Collector. The receiver listens for StatsD packets, aggregates them, and forwards the results to Axiom as OpenTelemetry metrics.
The StatsD receiver is part of the [contrib distribution](https://github.com/open-telemetry/opentelemetry-collector-releases) of the OpenTelemetry Collector (`otelcol-contrib`). It isn’t included in the core distribution.
When you create the dataset, select **Metrics** as the dataset type. StatsD data arrives in Axiom as OTel metrics, and metrics require their own dedicated dataset. For more information, see [Create dataset](/reference/datasets#create-dataset).
## Configure the OpenTelemetry Collector [#configure-the-opentelemetry-collector]
Add the StatsD receiver and an OTLP/HTTP exporter that sends data to Axiom:
```yaml
receivers:
statsd:
endpoint: 0.0.0.0:8125 # UDP by default. Defaults to localhost:8125 if omitted.
aggregation_interval: 60s # How often aggregated metrics are flushed to the exporter.
processors:
batch:
send_batch_max_size: 8192
exporters:
otlphttp:
compression: zstd
endpoint: https://AXIOM_DOMAIN
headers:
authorization: Bearer API_TOKEN
x-axiom-metrics-dataset: DATASET_NAME
service:
pipelines:
metrics:
receivers:
- statsd
processors:
- batch
exporters:
- otlphttp
```
For metrics, use the `x-axiom-metrics-dataset` header instead of `x-axiom-dataset`.
Point your StatsD clients at the collector’s address and port (UDP 8125 in the example above) instead of your previous StatsD server.
## Send a test metric [#send-a-test-metric]
With the collector running, send a test counter using netcat:
```shell
echo -n "test.requests:1|c" | nc -u -w1 localhost 8125
```
After the next aggregation interval, the `test.requests` metric appears in your Metrics dataset in Axiom.
## Metric types [#metric-types]
The StatsD receiver maps StatsD metric types to OpenTelemetry metric types:
| StatsD type | OpenTelemetry type |
| :------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Counter (`c`) | Sum |
| Gauge (`g`) | Gauge |
| Timer (`ms`), histogram (`h`), distribution (`d`) | Configurable with the receiver’s [`timer_histogram_mapping`](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/statsdreceiver) option: `histogram`, `summary`, or `gauge` |
Axiom supports all of these resulting types. For the full list of supported OTel metric types and current limitations, see [Supported data types](/query-data/metrics#supported-data-types).
## Query your metrics [#query-your-metrics]
Once data is flowing, query your StatsD metrics like any other OTel metrics in Axiom. For more information, see [Metrics](/query-data/metrics).
---
# Send data from syslog to Axiom
Source: https://axiom.co/docs/send-data/syslog-proxy
The Axiom Syslog Proxy acts as a syslog server to send data to Axiom.
The Axiom Syslog Proxy is an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/axiom-syslog-proxy).
## Syslog limitations and recommended alternatives [#syslog-limitations-and-recommended-alternatives]
Syslog is an outdated protocol. Some of the limitations are the following:
* Lack of error reporting and feedback mechanisms when issues occur.
* Inability to gracefully end the connection. This can result in missing data.
For a more reliable and modern logging experience, consider using tools like [Vector](https://vector.dev/) to receive syslog messages and [forward them to Axiom](/send-data/vector). This approach bypasses many of syslog’s limitations.
Other requirements:
* **Message size limit:** Axiom currently enforces a 64KB per-message size limit. This is in line with RFC5425 guidelines. Any message exceeding the limit causes the connection to close because Axiom doesn’t support ingesting truncated messages.
* **TLS requirement:** Axiom only supports syslog over TLS, specifically following RFC5425. Configure your syslog client accordingly.
* **Port requirements:** UDP log messages are sent on UDP port `514` to the Syslog server. TCP log messages are sent on TCP port `601` to the Syslog server.
Ensure your messages conform to the size limit and TLS requirements. If the connection is frequently re-established and messages are rejected, the issue can be the size of the messages or other formatting issues.
## Install Axiom Syslog Proxy [#install-axiom-syslog-proxy]
To install the Axiom Syslog Proxy, choose one of the following options:
* [Install using a pre-compiled binary file](#install-using-pre-compiled-binary-file)
* [Install using Homebrew](#install-using-homebrew)
* [Install using Go command](#install-using-go-command)
* [Install from the GitHub source](#install-from-github-source)
* [Install using a Docker image](#install-using-docker-image)
### Install using pre-compiled binary file [#install-using-pre-compiled-binary-file]
To install the Axiom Syslog Proxy using a pre-compiled binary file, download one of the [releases in GitHub](https://github.com/axiomhq/axiom-syslog-proxy/releases/latest).
### Install using Homebrew [#install-using-homebrew]
Run the following to install the Axiom Syslog Proxy using Homebrew:
```shell
brew tap axiomhq/tap
brew install axiom-syslog-proxy
```
### Install using Go command [#install-using-go-command]
Run the following to install the Axiom Syslog Proxy using `go get`:
```shell
go install github.com/axiomhq/axiom-syslog-proxy/cmd/axiom-syslog-proxy@latest
```
### Install from GitHub source [#install-from-github-source]
Run the following to install the Axiom Syslog Proxy from the GitHub source:
```shell
git clone https://github.com/axiomhq/axiom-syslog-proxy.git
cd axiom-syslog-proxy
make install
```
### Install using Docker image [#install-using-docker-image]
To install the Axiom Syslog Proxy using a Docker image, use a [Docker image from DockerHub](https://hub.docker.com/r/axiomhq/axiom-syslog-proxy/tags)
## Configure Axiom Syslog Proxy [#configure-axiom-syslog-proxy]
Set the following environment variables to connect to Axiom:
* `AXIOM_TOKEN` is the Axiom API token you have generated.
* `AXIOM_DATASET` is the name of the Axiom dataset where you want to send data.
* Optional: `AXIOM_URL` is the URL of the Axiom API. By default, it uses the `US East 1 (AWS)` edge deployment. Change the default value if your organization uses another edge deployment. For more information, see [Edge deployments](/reference/edge-deployments).
## Run Axiom Syslog Proxy [#run-axiom-syslog-proxy]
To run Axiom Syslog Proxy, run the following in your terminal.
```shell
./axiom-syslog-proxy
```
If you use Docker, run the following:
```shell
docker run -p601:601/tcp -p514:514/udp \
-e=AXIOM_TOKEN=API_TOKEN \
-e=AXIOM_DATASET=DATASET_NAME \
axiomhq/axiom-syslog-proxy
```
## Test configuration [#test-configuration]
To test that the Axiom Syslog Proxy configuration:
1. Run the following in your terminal to send two messages:
```shell
echo -n "tcp message" | nc -w1 localhost 601
echo -n "udp message" | nc -u -w1 localhost 514
```
2. In Axiom, click the **Stream** tab.
3. Click your dataset.
4. Check whether Axiom displays the messages you have sent.
---
# Send Traefik metrics to Axiom
Source: https://axiom.co/docs/send-data/traefik
Traefik exposes detailed metrics about its entrypoints, routers, and services in Prometheus format. Axiom ingests metrics over OTLP and doesn’t accept Prometheus remote-write, so the recommended setup is an OpenTelemetry Collector that scrapes Traefik’s Prometheus endpoint and forwards the metrics to Axiom. The same pattern works for any app that exposes Prometheus metrics.
The Prometheus receiver is part of the [contrib distribution](https://github.com/open-telemetry/opentelemetry-collector-releases) of the OpenTelemetry Collector (`otelcol-contrib`). It isn’t included in the core distribution.
When you create the dataset, select **Metrics** as the dataset type. Metrics require their own dedicated dataset. For more information, see [Create dataset](/reference/datasets#create-dataset).
## Enable Prometheus metrics in Traefik [#enable-prometheus-metrics-in-traefik]
Enable the Prometheus metrics provider in Traefik’s static configuration:
```yaml
metrics:
prometheus:
addEntryPointsLabels: true
addServicesLabels: true
```
Traefik then serves metrics at `/metrics` on the internal `traefik` entrypoint (port 8080 by default). To add per-router metrics, set `addRoutersLabels: true` as well. For all options, see the [Traefik documentation](https://doc.traefik.io/traefik/reference/install-configuration/observability/metrics/).
## Configure the OpenTelemetry Collector [#configure-the-opentelemetry-collector]
Add a Prometheus receiver that scrapes Traefik and an OTLP/HTTP exporter that sends data to Axiom:
```yaml
receivers:
prometheus:
config:
scrape_configs:
- job_name: traefik
scrape_interval: 15s
static_configs:
- targets: ["traefik:8080"] # host:port of Traefik's metrics entrypoint
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 80
spike_limit_percentage: 25
batch:
send_batch_max_size: 8192
exporters:
otlphttp:
compression: zstd
endpoint: https://AXIOM_DOMAIN
headers:
authorization: Bearer API_TOKEN
x-axiom-metrics-dataset: DATASET_NAME
service:
pipelines:
metrics:
receivers:
- prometheus
processors:
- memory_limiter
- batch
exporters:
- otlphttp
```
For metrics, use the `x-axiom-metrics-dataset` header instead of `x-axiom-dataset`.
Traefik v3 can also push OTLP metrics directly (`metrics.otlp`) — for example, to the OTLP receiver of an OpenTelemetry Collector that forwards to Axiom as shown above. For more information, see the [Traefik documentation](https://doc.traefik.io/traefik/reference/install-configuration/observability/metrics/).
## Query your metrics [#query-your-metrics]
Once data is flowing, query your Traefik metrics like any other OTel metrics in Axiom. For example, to chart the request rate per service:
```kusto
`DATASET_NAME`:`traefik_service_requests_total`
| align to $__interval using prom::rate
| group by `service` using sum
```
For more information, see [Metrics](/query-data/metrics).
---
# Send data from Tremor to Axiom
Source: https://axiom.co/docs/send-data/tremor
Axiom provides a unique way of ingesting [Tremor logs](https://www.tremor.rs/) into Axiom. With your connector definitions, you can configure Tremor connectors and events components to interact with your external systems, such as databases, message queues, or APIs, and eventually ingest data from these sources into Axiom.
## Installation [#installation]
Install the latest package from the runtime [releases tag](https://github.com/tremor-rs/tremor-runtime/releases) on your local machine.
## Configuration using HTTP [#configuration-using-http]
To send logs via Tremor to Axiom, you need to create a configuration file. For example, create `axiom-http.troy` with the following content (using a file as example data source):
```text
define flow client_sink_only
flow
use std::time::nanos;
use tremor::pipelines;
define connector input from file
args
file = "in.json" # Default input file is 'in.json' in current working directory
with
codec = "json", # Data is JSON encoded
preprocessors = ["separate"], # Data is newline separated
config = {
"path": args.file,
"mode": "read"
},
end;
create connector input;
define connector http_client from http_client
args
dataset,
token
with
config = {
"url": "https://AXIOM_DOMAIN/v1/ingest/#{args.dataset}",
"tls": true,
"method": "POST",
"headers": {
"Authorization": "Bearer #{args.token}"
},
"timeout": nanos::from_seconds(10),
"mime_mapping": {
"*/*": {"name": "json"},
}
}
end;
create connector http_client
with
dataset = "DATASET_NAME",
token = "API_TOKEN"
end;
create pipeline passthrough from pipelines::passthrough;
connect /connector/input to /pipeline/passthrough;
connect /pipeline/passthrough to /connector/http_client;
end;
deploy flow client_sink_only;
```
This assumes you have set `TREMOR_PATH` in your environment pointing to `tremor-runtime/tremor-script/lib` if you are using a `src` clone then you can execute it as follows `tremor server run axiom-http.troy`
## Configuration using Syslog [#configuration-using-syslog]
You can also send logs via Tremor to the Syslog endpoint using a file as an example data source.
In the code below, replace `url` with the URL of your Syslog endpoint.
```text
define flow client_sink_only
flow
use std::time::nanos;
use tremor::pipelines;
define connector input from file
args
file = "in.json" # Default input file is 'in.json' in current working directory
with
codec = "json", # Data is JSON encoded
preprocessors = ["separate"], # Data is newline separated
config = {
"path": args.file,
"mode": "read"
},
end;
create connector input;
define connector syslog_forwarder from tcp_client
args
endpoint_hostport,
with
tls = true,
codec = "syslog",
config = {
"url": "#{args.endpoint_hostport}",
"no_delay": false,
"buf_size": 1024,
},
reconnect = {
"retry": {
"interval_ms": 100,
"growth_rate": 2,
"max_retries": 3,
}
}
end;
create connector syslog_forwarder
with
endpoint_hostport = "tcp+tls://testsyslog.syslog.axiom.co:6514"
end;
create pipeline passthrough from pipelines::passthrough;
connect /connector/input to /pipeline/passthrough;
connect /pipeline/passthrough to /connector/syslog_forwarder;
end;
deploy flow client_sink_only;
```
---
# Send data from Vector to Axiom
Source: https://axiom.co/docs/send-data/vector
Vector is a lightweight and ultra-fast tool for building observability pipelines. It has a built-in support for shipping logs to Axiom through the [`axiom` sink](https://vector.dev/docs/reference/configuration/sinks/axiom/).
## Installation [#installation]
Follow the [quickstart guide in the Vector documentation](https://vector.dev/docs/setup/quickstart/) to install Vector, and to configure sources and sinks.
If you use Vector version v0.41.1 (released on September 11, 2024) or earlier, use the `@timestamp` field instead of `_time` to specify the timestamp of the events. For more information, see [Timestamp in legacy Vector versions](#timestamp-in-legacy-vector-versions).
If you upgrade from Vector version v0.41.1 or earlier to a newer version, update your configuration. For more information, see [Upgrade from legacy Vector version](#upgrade-from-legacy-vector-version).
## Configuration [#configuration]
Send data to Axiom with Vector using the [`file` method](https://vector.dev/docs/reference/configuration/sources/file/) and the [`axiom` sink](https://vector.dev/docs/reference/configuration/sinks/axiom/).
The example below configures Vector to read and collect logs from files and send them to Axiom.
1. Create a vector configuration file `vector.toml` with the following content:
```toml
[sources.VECTOR_SOURCE_ID]
type = "file"
include = ["PATH_TO_LOGS"]
[sinks.SINK_ID]
type = "axiom"
inputs = ["VECTOR_SOURCE_ID"]
token = "API_TOKEN"
dataset = "DATASET_NAME"
region = "AXIOM_DOMAIN"
```
Replace `VECTOR_SOURCE_ID` with the Vector source ID.
Replace `PATH_TO_LOGS` with the path to the log files. For example, `/var/log/**/*.log`.
Replace `SINK_ID` with the sink ID.
2. Run Vector to send logs to Axiom.
### Example with data transformation [#example-with-data-transformation]
The example below deletes a field before sending the data to Axiom:
```toml
[sources.VECTOR_SOURCE_ID]
type = "file"
include = ["PATH_TO_LOGS"]
[transforms.filter_json_fields]
type = "remap"
inputs = ["VECTOR_SOURCE_ID"]
source = '''
. = del(.FIELD_TO_REMOVE)
'''
[sinks.SINK_ID]
type = "axiom"
inputs = ["filter_json_fields"]
token = "API_TOKEN"
dataset = "DATASET_NAME"
region = "AXIOM_DOMAIN"
```
Replace `VECTOR_SOURCE_ID` with the Vector source ID.
Replace `PATH_TO_LOGS` with the path to the log files. For example, `/var/log/**/*.log`.
Replace `FIELD_TO_REMOVE` with the field you want to remove.
Replace `SINK_ID` with the sink ID.
Any changes to Vector’s `file` method can make the code example above outdated. If this happens, please refer to the [official Vector documentation on the `file` method](https://vector.dev/docs/reference/configuration/sources/file/), and we kindly ask you to inform us of the issue using the feedback tool at the bottom of this page.
## Send Kubernetes logs to Axiom [#send-kubernetes-logs-to-axiom]
Send Kubernetes logs to Axiom using the Kubernetes source.
```toml
[sources.my_source_id]
type = "kubernetes_logs"
auto_partial_merge = true
ignore_older_secs = 600
read_from = "beginning"
self_node_name = "${VECTOR_SELF_NODE_NAME}"
exclude_paths_glob_patterns = [ "**/exclude/**" ]
extra_field_selector = "metadata.name!=pod-name-to-exclude"
extra_label_selector = "my_custom_label!=my_value"
extra_namespace_label_selector = "my_custom_label!=my_value"
max_read_bytes = 2_048
max_line_bytes = 32_768
fingerprint_lines = 1
glob_minimum_cooldown_ms = 60_000
delay_deletion_ms = 60_000
data_dir = "/var/lib/vector"
timezone = "local"
[sinks.axiom]
type = "axiom"
inputs = ["my_source_id"]
token = "API_TOKEN"
dataset = "DATASET_NAME"
region = "AXIOM_DOMAIN"
```
## Send Docker logs to Axiom [#send-docker-logs-to-axiom]
To send Docker logs using the Axiom sink, you need to create a configuration file, for example, `vector.toml`, with the following content:
```toml
# Define the Docker logs source
[sources.docker_logs]
type = "docker_logs"
docker_host = "unix:///var/run/docker.sock"
# Define the Axiom sink
[sinks.axiom]
type = "axiom"
inputs = ["docker_logs"]
dataset = "DATASET_NAME"
token = "API_TOKEN"
region = "AXIOM_DOMAIN"
```
Run Vector: Start Vector with the configuration file you just created:
```bash
vector --config /path/to/vector.toml
```
Vector collects logs from Docker and forward them to Axiom using the Axiom sink. You can view and analyze your logs in your dataset.
## Send AWS S3 logs to Axiom [#send-aws-s3-logs-to-axiom]
To send AWS S3 logs using the Axiom sink, create a configuration file, for example, `vector.toml`, with the following content:
```toml
[sources.my_s3_source]
type = "aws_s3"
bucket = "my-bucket" # replace with your bucket name
region = "us-west-2" # replace with the AWS region of your bucket
[sinks.axiom]
type = "axiom"
inputs = ["my_s3_source"]
dataset = "DATASET_NAME"
token = "API_TOKEN"
region = "AXIOM_DOMAIN"
```
Finally, run Vector with the configuration file using `vector --config ./vector.toml`. This starts Vector and begins reading logs from the specified S3 bucket and sending them to the specified Axiom dataset.
## Send Kafka logs to Axiom [#send-kafka-logs-to-axiom]
To send Kafka logs using the Axiom sink, you need to create a configuration file, for example, `vector.toml`, with the following code:
```toml
[sources.my_kafka_source]
type = "kafka" # must be: kafka
bootstrap_servers = "10.14.22.123:9092" # your Kafka bootstrap servers
group_id = "my_group_id" # your Kafka consumer group ID
topics = ["my_topic"] # the Kafka topics to consume from
auto_offset_reset = "earliest" # start reading from the beginning
[sinks.axiom]
type = "axiom"
inputs = ["my_kafka_source"] # connect the Axiom sink to your Kafka source
dataset = "DATASET_NAME" # replace with the name of your Axiom dataset
token = "API_TOKEN" # replace with your Axiom API token
region = "AXIOM_DOMAIN"
```
Finally, you can start Vector with your configuration file: `vector --config /path/to/your/vector.toml`
## Send NGINX metrics to Axiom [#send-nginx-metrics-to-axiom]
To send NGINX metrics using Vector to the Axiom sink, first enable NGINX to emit metrics, then use Vector to capture and forward those metrics. Here is a step-by-step guide:
### Step 1: Enable NGINX Metrics [#step-1-enable-nginx-metrics]
Configure NGINX to expose metrics. This typically involves enabling the `ngx_http_stub_status_module` module in your NGINX configuration.
1. Open your NGINX configuration file (often located at `/etc/nginx/nginx.conf`) and in your `server` block, add:
```bash
location /metrics {
stub_status;
allow 127.0.0.1; # only allow requests from localhost
deny all; # deny all other hosts
}
```
2. Restart or reload NGINX to apply the changes:
```bash
sudo systemctl restart nginx
```
This exposes basic NGINX metrics at the `/metrics` endpoint on your server.
### Step 2: Configure Vector [#step-2-configure-vector]
Configure Vector to scrape the NGINX metrics and send them to Axiom. Create a new configuration file (`vector.toml`), and add the following:
```toml
[sources.nginx_metrics]
type = "nginx_metrics" # must be: nginx_metrics
endpoints = ["http://localhost/metrics"] # the endpoint where NGINX metrics are exposed
[sinks.axiom]
type = "axiom" # must be: axiom
inputs = ["nginx_metrics"] # use the metrics from the NGINX source
dataset = "DATASET_NAME" # replace with the name of your Axiom dataset
token = "API_TOKEN" # replace with your Axiom API token
region = "AXIOM_DOMAIN"
```
Finally, you can start Vector with your configuration file: `vector --config /path/to/your/vector.toml`
## Send Syslog logs to Axiom [#send-syslog-logs-to-axiom]
To send Syslog logs using the Axiom sink, you need to create a configuration file, for example, `vector.toml`, with the following code:
```toml
[sources.my_source_id]
type="syslog"
address="0.0.0.0:6514"
max_length=102_400
mode="tcp"
[sinks.axiom]
type="axiom"
inputs = [ "my_source_id" ] # required
dataset="DATASET_NAME" # replace with the name of your Axiom dataset
token="API_TOKEN" # replace with your Axiom API token
region = "AXIOM_DOMAIN"
```
## Send Prometheus metrics to Axiom [#send-prometheus-metrics-to-axiom]
To send Prometheus scrape metrics using the Axiom sink, you need to create a configuration file, for example, `vector.toml`, with the following code:
```toml
# Define the Prometheus source that scrapes metrics
[sources.my_prometheus_source]
type = "prometheus_scrape" # scrape metrics from a Prometheus endpoint
endpoints = ["http://localhost:9090/metrics"] # replace with your Prometheus endpoint
# Define Axiom sink where logs will be sent
[sinks.axiom]
type = "axiom" # Axiom type
inputs = ["my_prometheus_source"] # connect the Axiom sink to your Prometheus source
dataset = "DATASET_NAME" # replace with the name of your Axiom dataset
token = "API_TOKEN" # replace with your Axiom API token
region = "AXIOM_DOMAIN"
```
Check out the [advanced configuration on Batch, Buffer configuration, and Encoding on Vector Documentation](https://vector.dev/docs/reference/configuration/sinks/axiom/)
## Timestamp in legacy Vector versions [#timestamp-in-legacy-vector-versions]
If you use Vector version v0.41.1 (released on September 11, 2024) or earlier, use the `@timestamp` field instead of `_time` to specify the timestamp in the event data you send to Axiom. For example: `{"@timestamp":"2022-04-14T21:30:30.658Z..."}`. For more information, see [Requirements of the timestamp field](/reference/limits#requirements-of-the-timestamp-field). In the case of Vector version v0.41.1 or earlier, the requirements explained on the page apply to the `@timestamp` field, not to `_time`.
If you use Vector version v0.42.0 (released on October 21, 2024) or newer, use the `_time` field as usual for other collectors.
### Upgrade from legacy Vector version [#upgrade-from-legacy-vector-version]
If you upgrade from Vector version v0.41.1 or earlier to a newer version, change all references from the `timestamp` field to the `_time` field and remap the logic.
Example `vrl` file:
```text example.vrl
# Set time explicitly rather than allowing Axiom to default to the current time
. = set!(value: ., path: ["_time"], data: .timestamp)
# Remove the original value as it’s effectively a duplicate
del(.timestamp)
```
Example Vector configuration file:
```toml
# ...
[transforms.migrate]
type = "remap"
inputs = [ "k8s"]
file= 'example.vrl' # See above
[sinks.debug]
type = "axiom"
inputs = [ "migrate" ]
dataset = "DATASET_NAME" # No change
token = "API_TOKEN" # No change
region = "AXIOM_DOMAIN"
[sinks.debug.encoding]
codec = "json"
```
### Set compression algorithm [#set-compression-algorithm]
Upgrading to Vector version v0.42.0 or newer automatically enables the `zstd` compression algorithm by default.
To set another compression algorithm, use the example below:
```toml
# ...
[transforms.migrate]
type = "remap"
inputs = [ "k8s"]
file= 'example.vrl' # See above
[sinks.debug]
type = "axiom"
compression = "gzip" # Set the compression algorithm
inputs = [ "migrate" ]
dataset = "DATASET_NAME" # No change
token = "API_TOKEN" # No change
region = "AXIOM_DOMAIN"
[sinks.debug.encoding]
codec = "json"
```
---
# All features of Axiom Processing Language (APL)
Source: https://axiom.co/docs/apl/apl-features
| Category | Feature | Description |
| :---------------------- | :-------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Aggregation function | [arg\_max](/apl/aggregation-function/arg-max) | Returns the row where an expression evaluates to the maximum value. |
| Aggregation function | [arg\_min](/apl/aggregation-function/arg-min) | Returns the row where an expression evaluates to the minimum value. |
| Aggregation function | [avg](/apl/aggregation-function/avg) | Returns an average value across the group. |
| Aggregation function | [avgif](/apl/aggregation-function/avgif) | Calculates the average value of an expression in records for which the predicate evaluates to true. |
| Aggregation function | [count](/apl/aggregation-function/count) | Returns a count of the group without/with a predicate. |
| Aggregation function | [countif](/apl/aggregation-function/countif) | Returns a count of rows for which the predicate evaluates to true. |
| Aggregation function | [dcount](/apl/aggregation-function/dcount) | Returns an estimate for the number of distinct values that are taken by a scalar expression in the summary group. |
| Aggregation function | [dcountif](/apl/aggregation-function/dcountif) | Returns an estimate of the number of distinct values of an expression of rows for which the predicate evaluates to true. |
| Aggregation function | [histogram](/apl/aggregation-function/histogram) | Returns a timeseries heatmap chart across the group. |
| Aggregation function | [histogramif](/apl/aggregation-function/histogramif) | Creates a histogram for rows where a condition evaluates to true. |
| Aggregation function | [make\_list\_if](/apl/aggregation-function/make-list-if) | Creates a dynamic JSON object (array) of an expression values in the group for which the predicate evaluates to true. |
| Aggregation function | [make\_list](/apl/aggregation-function/make-list) | Creates a dynamic JSON object (array) of all the values of an expression in the group. |
| Aggregation function | [make\_set\_if](/apl/aggregation-function/make-set-if) | Creates a dynamic JSON object (array) of the set of distinct values that an expression takes in records for which the predicate evaluates to true. |
| Aggregation function | [make\_set](/apl/aggregation-function/make-set) | Creates a dynamic JSON array of the set of distinct values that an expression takes in the group. |
| Aggregation function | [max](/apl/aggregation-function/max) | Returns the maximum value across the group. |
| Aggregation function | [maxif](/apl/aggregation-function/maxif) | Calculates the maximum value of an expression in records for which the predicate evaluates to true. |
| Aggregation function | [min](/apl/aggregation-function/min) | Returns the minimum value across the group. |
| Aggregation function | [minif](/apl/aggregation-function/minif) | Returns the minimum of an expression in records for which the predicate evaluates to true. |
| Aggregation function | [percentile](/apl/aggregation-function/percentile) | Calculates the requested percentiles of the group and produces a timeseries chart. |
| Aggregation function | [percentileif](/apl/aggregation-function/percentileif) | Calculates the requested percentiles of the field for the rows where the predicate evaluates to true. |
| Aggregation function | [percentiles\_array](/apl/aggregation-function/percentiles-array) | Returns an array of numbers where each element is the value at the corresponding percentile. |
| Aggregation function | [percentiles\_arrayif](/apl/aggregation-function/percentiles-arrayif) | Returns an array of percentile values for the records that satisfy the condition. |
| Aggregation function | [rate](/apl/aggregation-function/rate) | Calculates the rate of values in a group per second. |
| Aggregation function | [spotlight](/apl/aggregation-function/spotlight) | Compares a selected set of events against a baseline and surface the most significant differences. |
| Aggregation function | [stdev](/apl/aggregation-function/stdev) | Calculates the standard deviation of an expression across the group. |
| Aggregation function | [stdevif](/apl/aggregation-function/stdevif) | Calculates the standard deviation of an expression in records for which the predicate evaluates to true. |
| Aggregation function | [sum](/apl/aggregation-function/sum) | Calculates the sum of an expression across the group. |
| Aggregation function | [sumif](/apl/aggregation-function/sumif) | Calculates the sum of an expression in records for which the predicate evaluates to true. |
| Aggregation function | [topk](/apl/aggregation-function/topk) | Calculates the top values of an expression across the group in a dataset. |
| Aggregation function | [topkif](/apl/aggregation-function/topkif) | Calculates the top values of an expression in records for which the predicate evaluates to true. |
| Aggregation function | [variance](/apl/aggregation-function/variance) | Calculates the variance of an expression across the group. |
| Aggregation function | [varianceif](/apl/aggregation-function/varianceif) | Calculates the variance of an expression in records for which the predicate evaluates to true. |
| Aggregation function | [phrases](/apl/aggregation-function/phrases) | Extracts and counts common phrases or word sequences from text fields. |
| Array function | [array\_concat](/apl/scalar-functions/array-functions/array-concat) | Concatenates arrays into one. |
| Array function | [array\_extract](/apl/scalar-functions/array-functions/array-extract) | Extracts values from a nested array. |
| Array function | [array\_iff](/apl/scalar-functions/array-functions/array-iff) | Filters array by condition. |
| Array function | [array\_index\_of](/apl/scalar-functions/array-functions/array-index-of) | Returns index of item in array. |
| Array function | [array\_length](/apl/scalar-functions/array-functions/array-length) | Returns length of array. |
| Array function | [array\_reverse](/apl/scalar-functions/array-functions/array-reverse) | Reverses array elements. |
| Array function | [array\_rotate\_left](/apl/scalar-functions/array-functions/array-rotate-left) | Rotates array values to the left. |
| Array function | [array\_rotate\_right](/apl/scalar-functions/array-functions/array-rotate-right) | Rotates array values to the right. |
| Array function | [array\_select\_dict](/apl/scalar-functions/array-functions/array-select-dict) | Selects dictionary from array of dictionaries. |
| Array function | [array\_shift\_left](/apl/scalar-functions/array-functions/array-shift-left) | Shifts array values to the left. |
| Array function | [array\_shift\_right](/apl/scalar-functions/array-functions/array-shift-right) | Shifts array values to the right. |
| Array function | [array\_slice](/apl/scalar-functions/array-functions/array-slice) | Returns slice of an array. |
| Array function | [array\_sort\_asc](/apl/scalar-functions/array-functions/array-sort-asc) | Sorts an array in ascending order. |
| Array function | [array\_sort\_desc](/apl/scalar-functions/array-functions/array-sort-desc) | Sorts an array in descending order. |
| Array function | [array\_split](/apl/scalar-functions/array-functions/array-split) | Splits array by indices. |
| Array function | [array\_sum](/apl/scalar-functions/array-functions/array-sum) | Sums array elements. |
| Array function | [bag\_has\_key](/apl/scalar-functions/array-functions/bag-has-key) | Checks if dynamic object has a specific key. |
| Array function | [bag\_keys](/apl/scalar-functions/array-functions/bag-keys) | Returns keys of a dynamic property bag. |
| Array function | [bag\_pack](/apl/scalar-functions/array-functions/bag-pack) | Creates a dynamic property bag from key-value pairs. |
| Array function | [bag\_zip](/apl/scalar-functions/array-functions/bag-zip) | Combines two arrays of keys and values into a dynamic property bag. |
| Array function | [isarray](/apl/scalar-functions/array-functions/isarray) | Checks if value is an array. |
| Array function | [len](/apl/scalar-functions/array-functions/len) | Returns array or string length. |
| Array function | [pack\_array](/apl/scalar-functions/array-functions/pack-array) | Packs input into a dynamic array. |
| Array function | [pack\_dictionary](/apl/scalar-functions/array-functions/pack-dictionary) | Returns a dictionary from key-value mappings. |
| Array function | [strcat\_array](/apl/scalar-functions/array-functions/strcat-array) | Joins array elements into a string using a delimiter. |
| Conditional function | [case](/apl/scalar-functions/conditional-function/case) | Evaluates conditions and returns the first matched result. |
| Conditional function | [iff](/apl/scalar-functions/conditional-function/iff) | Returns one of two values based on predicate. |
| Conversion function | [dynamic\_to\_json](/apl/scalar-functions/conversion-functions/dynamic-to-json) | Converts dynamic value to JSON string. |
| Conversion function | [ensure\_field](/apl/scalar-functions/conversion-functions/ensure-field) | Returns value of field or typed null. |
| Conversion function | [isbool](/apl/scalar-functions/conversion-functions/isbool) | Checks if expression evaluates to boolean. |
| Conversion function | [toarray](/apl/scalar-functions/conversion-functions/toarray) | Converts to array. |
| Conversion function | [tobool](/apl/scalar-functions/conversion-functions/tobool) | Converts to boolean. |
| Conversion function | [todatetime](/apl/scalar-functions/conversion-functions/todatetime) | Converts to datetime. |
| Conversion function | [todouble](/apl/scalar-functions/conversion-functions/todouble) | Converts to real. `todouble` and `toreal` are synonyms. |
| Conversion function | [todynamic](/apl/scalar-functions/conversion-functions/todynamic) | Converts to dynamic. |
| Conversion function | [tohex](/apl/scalar-functions/conversion-functions/tohex) | Converts to hexadecimal string. |
| Conversion function | [toint](/apl/scalar-functions/conversion-functions/toint) | Converts to integer. `toint` and `tolong` are synonyms. |
| Conversion function | [tolong](/apl/scalar-functions/conversion-functions/toint) | Converts to signed 64-bit long. `toint` and `tolong` are synonyms. |
| Conversion function | [toreal](/apl/scalar-functions/conversion-functions/todouble) | Converts to real. `todouble` and `toreal` are synonyms. |
| Conversion function | [tostring](/apl/scalar-functions/conversion-functions/tostring) | Converts to string. |
| Conversion function | [totimespan](/apl/scalar-functions/conversion-functions/totimespan) | Converts to timespan. |
| Datetime function | [ago](/apl/scalar-functions/datetime-functions/ago) | Subtracts timespan from current time. |
| Datetime function | [datetime\_add](/apl/scalar-functions/datetime-functions/datetime-add) | Adds amount to datetime. |
| Datetime function | [datetime\_diff](/apl/scalar-functions/datetime-functions/datetime-diff) | Difference between two datetimes. |
| Datetime function | [datetime\_part](/apl/scalar-functions/datetime-functions/datetime-part) | Extracts part of a datetime. |
| Datetime function | [dayofmonth](/apl/scalar-functions/datetime-functions/dayofmonth) | Day number in month. |
| Datetime function | [dayofweek](/apl/scalar-functions/datetime-functions/dayofweek) | Days since previous Sunday. |
| Datetime function | [dayofyear](/apl/scalar-functions/datetime-functions/dayofyear) | Day number in year. |
| Datetime function | [endofday](/apl/scalar-functions/datetime-functions/endofday) | Returns end of day. |
| Datetime function | [endofmonth](/apl/scalar-functions/datetime-functions/endofmonth) | Returns end of month. |
| Datetime function | [endofweek](/apl/scalar-functions/datetime-functions/endofweek) | Returns end of week. |
| Datetime function | [endofyear](/apl/scalar-functions/datetime-functions/endofyear) | Returns end of year. |
| Datetime function | [getmonth](/apl/scalar-functions/datetime-functions/getmonth) | Month of a datetime. |
| Datetime function | [getyear](/apl/scalar-functions/datetime-functions/getyear) | Year of a datetime. |
| Datetime function | [hourofday](/apl/scalar-functions/datetime-functions/hourofday) | Hour number of the day. |
| Datetime function | [monthofyear](/apl/scalar-functions/datetime-functions/monthofyear) | Month number of year. |
| Datetime function | [now](/apl/scalar-functions/datetime-functions/now) | Returns current UTC time. |
| Datetime function | [startofday](/apl/scalar-functions/datetime-functions/startofday) | Returns start of day. |
| Datetime function | [startofmonth](/apl/scalar-functions/datetime-functions/startofmonth) | Returns start of month. |
| Datetime function | [startofweek](/apl/scalar-functions/datetime-functions/startofweek) | Returns start of week. |
| Datetime function | [startofyear](/apl/scalar-functions/datetime-functions/startofyear) | Returns start of year. |
| Datetime function | [unixtime\_microseconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-microseconds-todatetime) | Converts microsecond Unix timestamp to datetime. |
| Datetime function | [unixtime\_milliseconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-milliseconds-todatetime) | Converts millisecond Unix timestamp to datetime. |
| Datetime function | [unixtime\_nanoseconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-nanoseconds-todatetime) | Converts nanosecond Unix timestamp to datetime. |
| Datetime function | [unixtime\_seconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-seconds-todatetime) | Converts second Unix timestamp to datetime. |
| Datetime function | [week\_of\_year](/apl/scalar-functions/datetime-functions/week-of-year) | Returns the ISO 8601 week number from a datetime expression. |
| GenAI function | [genai\_concat\_contents](/apl/scalar-functions/genai-functions/genai-concat-contents) | Concatenates message contents from a GenAI conversation array. |
| GenAI function | [genai\_conversation\_turns](/apl/scalar-functions/genai-functions/genai-conversation-turns) | Counts the number of conversation turns in GenAI messages. |
| GenAI function | [genai\_cost](/apl/scalar-functions/genai-functions/genai-cost) | Calculates the total cost for input and output tokens. |
| GenAI function | [genai\_estimate\_tokens](/apl/scalar-functions/genai-functions/genai-estimate-tokens) | Estimates the number of tokens in a text string. |
| GenAI function | [genai\_extract\_assistant\_response](/apl/scalar-functions/genai-functions/genai-extract-assistant-response) | Extracts the assistant’s response from a GenAI conversation. |
| GenAI function | [genai\_extract\_function\_results](/apl/scalar-functions/genai-functions/genai-extract-function-results) | Extracts function call results from GenAI messages. |
| GenAI function | [genai\_extract\_system\_prompt](/apl/scalar-functions/genai-functions/genai-extract-system-prompt) | Extracts the system prompt from a GenAI conversation. |
| GenAI function | [genai\_extract\_tool\_calls](/apl/scalar-functions/genai-functions/genai-extract-tool-calls) | Extracts tool calls from GenAI messages. |
| GenAI function | [genai\_extract\_user\_prompt](/apl/scalar-functions/genai-functions/genai-extract-user-prompt) | Extracts the user prompt from a GenAI conversation. |
| GenAI function | [genai\_get\_content\_by\_index](/apl/scalar-functions/genai-functions/genai-get-content-by-index) | Gets message content by index position. |
| GenAI function | [genai\_get\_content\_by\_role](/apl/scalar-functions/genai-functions/genai-get-content-by-role) | Gets message content by role. |
| GenAI function | [genai\_get\_pricing](/apl/scalar-functions/genai-functions/genai-get-pricing) | Gets pricing information for a specific AI model. |
| GenAI function | [genai\_get\_role](/apl/scalar-functions/genai-functions/genai-get-role) | Gets the role of a message at a specific index. |
| GenAI function | [genai\_has\_tool\_calls](/apl/scalar-functions/genai-functions/genai-has-tool-calls) | Checks if GenAI messages contain tool calls. |
| GenAI function | [genai\_input\_cost](/apl/scalar-functions/genai-functions/genai-input-cost) | Calculates the cost for input tokens. |
| GenAI function | [genai\_is\_truncated](/apl/scalar-functions/genai-functions/genai-is-truncated) | Checks if a GenAI response was truncated. |
| GenAI function | [genai\_message\_roles](/apl/scalar-functions/genai-functions/genai-message-roles) | Extracts all message roles from a GenAI conversation. |
| GenAI function | [genai\_output\_cost](/apl/scalar-functions/genai-functions/genai-output-cost) | Calculates the cost for output tokens. |
| Hash function | [hash\_md5](/apl/scalar-functions/hash-functions/hash-md5) | Returns MD5 hash. |
| Hash function | [hash\_sha1](/apl/scalar-functions/hash-functions/hash-sha1) | Returns SHA-1 hash. |
| Hash function | [hash\_sha256](/apl/scalar-functions/hash-functions/hash-sha256) | Returns SHA256 hash. |
| Hash function | [hash\_sha512](/apl/scalar-functions/hash-functions/hash-sha512) | Returns SHA512 hash. |
| Hash function | [hash](/apl/scalar-functions/hash-functions/hash) | Returns integer hash of input. |
| IP function | [format\_ipv4\_mask](/apl/scalar-functions/ip-functions/format-ipv4-mask) | Formats IPv4 and mask to CIDR. |
| IP function | [format\_ipv4](/apl/scalar-functions/ip-functions/format-ipv4) | Formats netmask into IPv4 string. |
| IP function | [geo\_info\_from\_ip\_address](/apl/scalar-functions/ip-functions/geo-info-from-ip-address) | Extracts geolocation from IP address. |
| IP function | [has\_any\_ipv4\_prefix](/apl/scalar-functions/ip-functions/has-any-ipv4-prefix) | Checks if IPv4 starts with any prefix. |
| IP function | [has\_any\_ipv4](/apl/scalar-functions/ip-functions/has-any-ipv4) | Checks if any of given IPv4s exist in column. |
| IP function | [has\_ipv4\_prefix](/apl/scalar-functions/ip-functions/has-ipv4-prefix) | Checks if IPv4 starts with specified prefix. |
| IP function | [has\_ipv4](/apl/scalar-functions/ip-functions/has-ipv4) | Checks if IPv4 is valid and in source text. |
| IP function | [ipv4\_compare](/apl/scalar-functions/ip-functions/ipv4-compare) | Compares two IPv4 addresses. |
| IP function | [ipv4\_is\_in\_any\_range](/apl/scalar-functions/ip-functions/ipv4-is-in-any-range) | Checks if IPv4 is in any specified range. |
| IP function | [ipv4\_is\_in\_range](/apl/scalar-functions/ip-functions/ipv4-is-in-range) | Checks if IPv4 is in a given range. |
| IP function | [ipv4\_is\_match](/apl/scalar-functions/ip-functions/ipv4-is-match) | Matches IPv4 against a pattern. |
| IP function | [ipv4\_is\_private](/apl/scalar-functions/ip-functions/ipv4-is-private) | Checks if IPv4 is private. |
| IP function | [ipv4\_netmask\_suffix](/apl/scalar-functions/ip-functions/ipv4-netmask-suffix) | Extracts netmask suffix. |
| IP function | [ipv6\_compare](/apl/scalar-functions/ip-functions/ipv6-compare) | Compares two IPv6 addresses. |
| IP function | [ipv6\_is\_in\_any\_range](/apl/scalar-functions/ip-functions/ipv6-is-in-any-range) | Checks if IPv6 is in any range. |
| IP function | [ipv6\_is\_in\_range](/apl/scalar-functions/ip-functions/ipv6-is-in-range) | Checks if IPv6 is in range. |
| IP function | [ipv6\_is\_match](/apl/scalar-functions/ip-functions/ipv6-is-match) | Checks if IPv6 matches pattern. |
| IP function | [parse\_ipv4\_mask](/apl/scalar-functions/ip-functions/parse-ipv4-mask) | Converts IPv4 and mask to long integer. |
| IP function | [parse\_ipv4](/apl/scalar-functions/ip-functions/parse-ipv4) | Converts IPv4 to long integer. |
| Logical operator | [!=](/apl/scalar-operators/logical-operators) | Returns `true` if either one (or both) of the operands are null, or they aren’t equal to each other. Otherwise, `false`. |
| Logical operator | [==](/apl/scalar-operators/logical-operators) | Returns `true` if both operands are non-null and equal to each other. Otherwise, `false`. |
| Logical operator | [and](/apl/scalar-operators/logical-operators) | Returns `true` if both operands are `true`. |
| Logical operator | [or](/apl/scalar-operators/logical-operators) | Returns `true` if one of the operands is `true`, regardless of the other operand. |
| Mathematical function | [abs](/apl/scalar-functions/mathematical-functions/abs) | Returns absolute value. |
| Mathematical function | [acos](/apl/scalar-functions/mathematical-functions/acos) | Returns arccosine of a number. |
| Mathematical function | [asin](/apl/scalar-functions/mathematical-functions/asin) | Returns arcsine of a number. |
| Mathematical function | [atan](/apl/scalar-functions/mathematical-functions/atan) | Returns arctangent of a number. |
| Mathematical function | [atan2](/apl/scalar-functions/mathematical-functions/atan2) | Returns angle between x-axis and point (y, x). |
| Mathematical function | [cos](/apl/scalar-functions/mathematical-functions/cos) | Returns cosine of a number. |
| Mathematical function | [cot](/apl/scalar-functions/mathematical-functions/cot) | Returns cotangent of a number. |
| Mathematical function | [degrees](/apl/scalar-functions/mathematical-functions/degrees) | Converts radians to degrees. |
| Mathematical function | [exp](/apl/scalar-functions/mathematical-functions/exp) | Returns e^x. |
| Mathematical function | [exp10](/apl/scalar-functions/mathematical-functions/exp10) | Returns 10^x. |
| Mathematical function | [exp2](/apl/scalar-functions/mathematical-functions/exp2) | Returns 2^x. |
| Mathematical function | [gamma](/apl/scalar-functions/mathematical-functions/gamma) | Returns gamma function of x. |
| Mathematical function | [isfinite](/apl/scalar-functions/mathematical-functions/isfinite) | Returns `true` if x is finite. |
| Mathematical function | [isinf](/apl/scalar-functions/mathematical-functions/isinf) | Returns `true` if x is infinite. |
| Mathematical function | [isint](/apl/scalar-functions/mathematical-functions/isint) | Returns `true` if x is an integer. |
| Mathematical function | [isnan](/apl/scalar-functions/mathematical-functions/isnan) | Returns `true` if x is NaN. |
| Mathematical function | [log](/apl/scalar-functions/mathematical-functions/log) | Returns natural logarithm of x. |
| Mathematical function | [log10](/apl/scalar-functions/mathematical-functions/log10) | Returns base-10 logarithm. |
| Mathematical function | [log2](/apl/scalar-functions/mathematical-functions/log2) | Returns base-2 logarithm. |
| Mathematical function | [loggamma](/apl/scalar-functions/mathematical-functions/loggamma) | Returns log of absolute gamma function. |
| Mathematical function | [max\_of](/apl/scalar-functions/mathematical-functions/max-of) | Returns largest value among arguments. |
| Mathematical function | [min\_of](/apl/scalar-functions/mathematical-functions/min-of) | Returns smallest value among arguments. |
| Mathematical function | [not](/apl/scalar-functions/mathematical-functions/not) | Reverses boolean value. |
| Mathematical function | [pi](/apl/scalar-functions/mathematical-functions/pi) | Returns value of Pi. |
| Mathematical function | [pow](/apl/scalar-functions/mathematical-functions/pow) | Returns value raised to a power. |
| Mathematical function | [radians](/apl/scalar-functions/mathematical-functions/radians) | Converts degrees to radians. |
| Mathematical function | [rand](/apl/scalar-functions/mathematical-functions/rand) | Returns pseudo-random numbers between 0 (inclusive) and 1 (exclusive). |
| Mathematical function | [range](/apl/scalar-functions/mathematical-functions/range) | Returns a dynamic array of evenly spaced values. |
| Mathematical function | [round](/apl/scalar-functions/mathematical-functions/round) | Rounds value to given precision. |
| Mathematical function | [set\_difference](/apl/scalar-functions/mathematical-functions/set-difference) | Returns array difference. |
| Mathematical function | [set\_has\_element](/apl/scalar-functions/mathematical-functions/set-has-element) | Returns `true` if set contains an element. |
| Mathematical function | [set\_intersect](/apl/scalar-functions/mathematical-functions/set-intersect) | Returns array intersection. |
| Mathematical function | [set\_union](/apl/scalar-functions/mathematical-functions/set-union) | Returns array union. |
| Mathematical function | [sign](/apl/scalar-functions/mathematical-functions/sign) | Returns sign of number. |
| Mathematical function | [sin](/apl/scalar-functions/mathematical-functions/sin) | Returns sine of a number. |
| Mathematical function | [sqrt](/apl/scalar-functions/mathematical-functions/sqrt) | Returns square root of a number. |
| Mathematical function | [tan](/apl/scalar-functions/mathematical-functions/tan) | Returns tangent of a number. |
| Metadata function | [column\_ifexists](/apl/scalar-functions/metadata-functions/column-ifexists) | Checks if a field with a given name exists in the dataset. |
| Metadata function | [cursor\_current](/apl/scalar-functions/metadata-functions/cursor-current) | Retrieves a cursor string representing the current query execution point. |
| Metadata function | [ingestion\_time](/apl/scalar-functions/metadata-functions/ingestion-time) | Retrieves the timestamp of when each record was ingested into Axiom. |
| Numerical operator | [-](/apl/scalar-operators/numerical-operators) | Subtract. Example: `0.26 - 0.23` |
| Numerical operator | [!=](/apl/scalar-operators/numerical-operators) | Not equals. Example: `2 != 1` |
| Numerical operator | [\*](/apl/scalar-operators/numerical-operators) | Multiply. Example: `1s * 5`, `5 * 5` |
| Numerical operator | [/](/apl/scalar-operators/numerical-operators) | Divide. Example: `10m / 1s`, `4 / 2` |
| Numerical operator | [\<](/apl/scalar-operators/numerical-operators) | Less. Example: `1 < 2`, `1 <= 1` |
| Numerical operator | [\<=](/apl/scalar-operators/numerical-operators) | Less or Equal. Example: `5 <= 6` |
| Numerical operator | [%](/apl/scalar-operators/numerical-operators) | Modulo. Example: `10 % 3`, `5 % 2` |
| Numerical operator | [+](/apl/scalar-operators/numerical-operators) | Add. Example: `3.19 + 3.19`, `ago(10m) + 10m` |
| Numerical operator | [==](/apl/scalar-operators/numerical-operators) | Equals. Example: `3 == 3` |
| Numerical operator | [>](/apl/scalar-operators/numerical-operators) | Greater. Example: `0.23 > 0.22`, `now() > ago(1d)` |
| Numerical operator | [>=](/apl/scalar-operators/numerical-operators) | Greater or Equal. Example: `7 >= 6` |
| Pair function | [find\_pair](/apl/scalar-functions/pair-functions/find-pair) | Searches an array of key-value pairs for the first pair matching specified patterns. |
| Pair function | [pair](/apl/scalar-functions/pair-functions/pair) | Creates a dynamic object representing a key-value pair. |
| Pair function | [parse\_pair](/apl/scalar-functions/pair-functions/parse-pair) | Parses a pair string into its key and value components. |
| Rounding function | [bin](/apl/scalar-functions/rounding-functions/bin) | Rounds values down to an integer multiple of a specified bin size. |
| Rounding function | [bin\_auto](/apl/scalar-functions/rounding-functions/bin-auto) | Rounds datetime values down to a fixed-size bin with automatic size selection. |
| Rounding function | [ceiling](/apl/scalar-functions/rounding-functions/ceiling) | Rounds a number up to the smallest integer greater than or equal to the input. |
| Rounding function | [floor](/apl/scalar-functions/rounding-functions/floor) | Rounds a number down to the largest integer less than or equal to the input. |
| Set membership operator | [in](/apl/scalar-operators/in-operators/in-operator) | Equals to one of the elements (case-sensitive). Example: `"abc" in ("123", "345", "abc")` |
| Set membership operator | [in\~](/apl/scalar-operators/in-operators/in-tilde-operator) | Equals to one of the elements (case-insensitive). Example: `"abc" in~ ("123", "345", "ABC")` |
| Set membership operator | [!in](/apl/scalar-operators/in-operators/not-in-operator) | Not equals to any of the elements (case-sensitive). Example: `"bca" !in ("123", "345", "abc")` |
| Set membership operator | [!in\~](/apl/scalar-operators/in-operators/not-in-tilde-operator) | Not equals to any of the elements (case-insensitive). Example: `"bca" !in~ ("123", "345", "ABC")` |
| SQL function | [format\_sql](/apl/scalar-functions/sql-functions/format-sql) | Converts parsed SQL data model back into SQL statement. |
| SQL function | [parse\_sql](/apl/scalar-functions/sql-functions/parse-sql) | Parses and analyzes SQL queries. |
| String function | [base64\_decode\_toarray](/apl/scalar-functions/string-functions/base64-decode-toarray) | Decodes a Base64-encoded string into an array of bytes. |
| String function | [base64\_decode\_tostring](/apl/scalar-functions/string-functions/base64-decode-tostring) | Decodes a base64 string to a UTF-8 string. |
| String function | [base64\_encode\_fromarray](/apl/scalar-functions/string-functions/base64-encode-fromarray) | Converts a sequence of bytes into a Base64-encoded string. |
| String function | [base64\_encode\_tostring](/apl/scalar-functions/string-functions/base64-encode-tostring) | Encodes a string as base64 string. |
| String function | [coalesce](/apl/scalar-functions/string-functions/coalesce) | Returns the first non-null/non-empty value from a list. |
| String function | [countof\_regex](/apl/scalar-functions/string-functions/countof-regex) | Counts occurrences of a regex in a string. |
| String function | [countof](/apl/scalar-functions/string-functions/countof) | Counts occurrences of a substring in a string. |
| String function | [extract\_all](/apl/scalar-functions/string-functions/extract-all) | Gets all matches for a regular expression from a text string. |
| String function | [extract](/apl/scalar-functions/string-functions/extract) | Gets a match for a regular expression from a text string. |
| String function | [format\_bytes](/apl/scalar-functions/string-functions/format-bytes) | Formats a number of bytes as a string including units. |
| String function | [format\_url](/apl/scalar-functions/string-functions/format-url) | Formats a string into a valid URL. |
| String function | [gettype](/apl/scalar-functions/string-functions/gettype) | Returns the runtime type of an argument. |
| String function | [indexof](/apl/scalar-functions/string-functions/indexof) | Returns index of the first occurrence of a substring. |
| String function | [isascii](/apl/scalar-functions/string-functions/isascii) | Returns `true` if all characters in an input string are ASCII characters. |
| String function | [isempty](/apl/scalar-functions/string-functions/isempty) | Returns `true` if the argument is empty or null. |
| String function | [isnotempty](/apl/scalar-functions/string-functions/isnotempty) | Returns `true` if the argument isn’t empty or null. |
| String function | [isnotnull](/apl/scalar-functions/string-functions/isnotnull) | Returns `true` if the argument isn’t null. |
| String function | [isnull](/apl/scalar-functions/string-functions/isnull) | Returns `true` if the argument is null. |
| String function | [parse\_bytes](/apl/scalar-functions/string-functions/parse-bytes) | Parses byte-size string to number of bytes. |
| String function | [parse\_csv](/apl/scalar-functions/string-functions/parse-csv) | Splits a CSV-formatted string into an array. |
| String function | [parse\_json](/apl/scalar-functions/string-functions/parse-json) | Parses a string as a JSON value. |
| String function | [parse\_url](/apl/scalar-functions/string-functions/parse-url) | Parses a URL string and returns parts in a dynamic object. |
| String function | [parse\_urlquery](/apl/scalar-functions/string-functions/parse-urlquery) | Parses a URL query string into key-value pairs. |
| String function | [quote](/apl/scalar-functions/string-functions/quote) | Returns a string representing the input enclosed in double quotes, with internal quotes and escape sequences handled appropriately. |
| String function | [replace\_regex](/apl/scalar-functions/string-functions/replace-regex) | Replaces regex matches with another string. |
| String function | [replace\_string](/apl/scalar-functions/string-functions/replace-string) | Replaces string matches with another string. |
| String function | [replace](/apl/scalar-functions/string-functions/replace) | Replaces all regex matches with another string. |
| String function | [reverse](/apl/scalar-functions/string-functions/reverse) | Reverses a string. |
| String function | [split](/apl/scalar-functions/string-functions/split) | Splits a string into an array using a delimiter. |
| String function | [strcat\_delim](/apl/scalar-functions/string-functions/strcat-delim) | Concatenates 2–64 arguments with a delimiter. |
| String function | [strcat](/apl/scalar-functions/string-functions/strcat) | Concatenates 1–64 arguments. |
| String function | [strcmp](/apl/scalar-functions/string-functions/strcmp) | Compares two strings. |
| String function | [string-size](/apl/scalar-functions/string-functions/string-size) | Returns the length, in characters, of the input string. |
| String function | [strip\_ansi\_escapes](/apl/scalar-functions/string-functions/strip-ansi-escapes) | Removes ANSI escape sequences from strings. |
| String function | [strlen](/apl/scalar-functions/string-functions/strlen) | Returns the length of a string. |
| String function | [strrep](/apl/scalar-functions/string-functions/strrep) | Repeats a string a given number of times. |
| String function | [substring](/apl/scalar-functions/string-functions/substring) | Extracts a substring. |
| String function | [tolower](/apl/scalar-functions/string-functions/tolower) | Converts string to lowercase. |
| String function | [totitle](/apl/scalar-functions/string-functions/totitle) | Converts string to title case. |
| String function | [toupper](/apl/scalar-functions/string-functions/toupper) | Converts string to uppercase. |
| String function | [translate](/apl/scalar-functions/string-functions/translate) | Substitutes characters in a string, one by one, based on their position in two input lists. |
| String function | [trim\_end\_regex](/apl/scalar-functions/string-functions/trim-end-regex) | Trims trailing characters using regex. |
| String function | [trim\_end](/apl/scalar-functions/string-functions/trim-end) | Trims trailing characters. |
| String function | [trim\_regex](/apl/scalar-functions/string-functions/trim-regex) | Trims characters matching a regex. |
| String function | [trim\_space](/apl/scalar-functions/string-functions/trim-space) | Removes all leading and trailing whitespace from a string. |
| String function | [trim\_start\_regex](/apl/scalar-functions/string-functions/trim-start-regex) | Trims leading characters using regex. |
| String function | [trim\_start](/apl/scalar-functions/string-functions/trim-start) | Trims leading characters. |
| String function | [trim](/apl/scalar-functions/string-functions/trim) | Trims leading/trailing characters. |
| String function | [unicode\_codepoints\_from\_string](/apl/scalar-functions/string-functions/unicode-codepoints-from-string) | Converts a UTF-8 string into an array of Unicode code points. |
| String function | [unicode\_codepoints\_to\_string](/apl/scalar-functions/string-functions/unicode-codepoints-to-string) | Converts an array of Unicode code points into a UTF-8 encoded string. |
| String function | [url\_decode](/apl/scalar-functions/string-functions/url-decode) | Decodes a URL-encoded string. |
| String function | [url\_encode](/apl/scalar-functions/string-functions/url-encode) | Encodes characters into a URL-friendly format. |
| String operator | [!=](/apl/scalar-operators/string-operators) | Not equals (case-sensitive). Example: `"abc" != "ABC"` |
| String operator | [!\~](/apl/scalar-operators/string-operators) | Not equals (case-insensitive). Example: `"aBc" !~ "xyz"` |
| String operator | [!contains\_cs](/apl/scalar-operators/string-operators) | RHS doesn’t occur in LHS (case-sensitive). Example: `"parentSpanId" !contains_cs "Id"` |
| String operator | [!contains](/apl/scalar-operators/string-operators) | RHS doesn’t occur in LHS (case-insensitive). Example: `"parentSpanId" !contains "abc"` |
| String operator | [!endswith\_cs](/apl/scalar-operators/string-operators) | RHS isn’t a closing subsequence of LHS (case-sensitive). Example: `"parentSpanId" !endswith_cs "Span"` |
| String operator | [!endswith](/apl/scalar-operators/string-operators) | RHS isn’t a closing subsequence of LHS (case-insensitive). Example: `"parentSpanId" !endswith "Span"` |
| String operator | [!has\_cs](/apl/scalar-operators/string-operators) | RHS isn’t a whole term in LHS (case-sensitive). Example: `"North America" !has_cs "America"` |
| String operator | [!has](/apl/scalar-operators/string-operators) | RHS isn’t a whole term in LHS (case-insensitive). Example: `"North America" !has "america"` |
| String operator | [!hasprefix\_cs](/apl/scalar-operators/string-operators) | LHS string doesn’t start with the RHS string (case-sensitive). Example: `"DOCS_file" !hasprefix_cs "DOCS"` |
| String operator | [!hasprefix](/apl/scalar-operators/string-operators) | LHS string doesn’t start with the RHS string (case-insensitive). Example: `"Admin_User" !hasprefix "Admin"` |
| String operator | [!hassuffix\_cs](/apl/scalar-operators/string-operators) | LHS string doesn’t end with the RHS string (case-sensitive). Example: `"Document.HTML" !hassuffix_cs ".HTML"` |
| String operator | [!hassuffix](/apl/scalar-operators/string-operators) | LHS string doesn’t end with the RHS string (case-insensitive). Example: `"documentation.docx" !hassuffix ".docx"` |
| String operator | [!matches regex](/apl/scalar-operators/string-operators) | LHS doesn’t contain a match for RHS. Example: `"parentSpanId" !matches regex "g.*r"` |
| String operator | [!startswith\_cs](/apl/scalar-operators/string-operators) | RHS isn’t an initial subsequence of LHS (case-sensitive). Example: `"parentSpanId" !startswith_cs "parent"` |
| String operator | [!startswith](/apl/scalar-operators/string-operators) | RHS isn’t an initial subsequence of LHS (case-insensitive). Example: `"parentSpanId" !startswith "Id"` |
| String operator | [==](/apl/scalar-operators/string-operators) | Equals (case-sensitive). Example: `"aBc" == "aBc"` |
| String operator | [=\~](/apl/scalar-operators/string-operators) | Equals (case-insensitive). Example: `"abc" =~ "ABC"` |
| String operator | [contains\_cs](/apl/scalar-operators/string-operators) | RHS occurs as a subsequence of LHS (case-sensitive). Example: `"parentSpanId" contains_cs "Id"` |
| String operator | [contains](/apl/scalar-operators/string-operators) | RHS occurs as a subsequence of LHS (case-insensitive). Example: `"parentSpanId" contains "Span"` |
| String operator | [endswith\_cs](/apl/scalar-operators/string-operators) | RHS is a closing subsequence of LHS (case-sensitive). Example: `"parentSpanId" endswith_cs "Id"` |
| String operator | [endswith](/apl/scalar-operators/string-operators) | RHS is a closing subsequence of LHS (case-insensitive). Example: `"parentSpanId" endswith "Id"` |
| String operator | [has\_cs](/apl/scalar-operators/string-operators) | RHS is a whole term in LHS (case-sensitive). Example: `"North America" has_cs "America"` |
| String operator | [has](/apl/scalar-operators/string-operators) | RHS is a whole term in LHS (case-insensitive). Example: `"North America" has "america"` |
| String operator | [has\_any](/apl/scalar-operators/string-operators) | RHS has any whole term in LHS (case-insensitive). Example: `"North America" has_any ("america", "europe")` |
| String operator | [has\_any\_cs](/apl/scalar-operators/string-operators) | RHS has any whole term in LHS (case-sensitive). Example: `"North America" has_any_cs ("America", "Europe")` |
| String operator | [hasprefix\_cs](/apl/scalar-operators/string-operators) | LHS string starts with the RHS string (case-sensitive). Example: `"DOCS_file" hasprefix_cs "DOCS"` |
| String operator | [hasprefix](/apl/scalar-operators/string-operators) | LHS string starts with the RHS string (case-insensitive). Example: `"Admin_User" hasprefix "Admin"` |
| String operator | [hassuffix\_cs](/apl/scalar-operators/string-operators) | LHS string ends with the RHS string (case-sensitive). Example: `"Document.HTML" hassuffix_cs ".HTML"` |
| String operator | [hassuffix](/apl/scalar-operators/string-operators) | LHS string ends with the RHS string (case-insensitive). Example: `"documentation.docx" hassuffix ".docx"` |
| String operator | [matches regex](/apl/scalar-operators/string-operators) | LHS contains a match for RHS. Example: `"parentSpanId" matches regex "g.*r"` |
| String operator | [startswith\_cs](/apl/scalar-operators/string-operators) | RHS is an initial subsequence of LHS (case-sensitive). Example: `"parentSpanId" startswith_cs "parent"` |
| String operator | [startswith](/apl/scalar-operators/string-operators) | RHS is an initial subsequence of LHS (case-insensitive). Example: `"parentSpanId" startswith "parent"` |
| Tabular operator | [count](/apl/tabular-operators/count-operator) | Returns an integer representing the total number of records in the dataset. |
| Tabular operator | [distinct](/apl/tabular-operators/distinct-operator) | Returns a dataset with unique values from the specified fields, removing any duplicate entries. |
| Tabular operator | [extend-valid](/apl/tabular-operators/extend-valid-operator) | Returns a table where the specified fields are extended with new values based on the given expression for valid rows. |
| Tabular operator | [extend](/apl/tabular-operators/extend-operator) | Returns the original dataset with one or more new fields appended, based on the defined expressions. |
| Tabular operator | [externaldata](/apl/tabular-operators/externaldata-operator) | Returns a table with the specified schema, containing data retrieved from an external source. |
| Tabular operator | [getschema](/apl/tabular-operators/getschema-operator) | Returns the schema of the input, including field names and their data types. |
| Tabular operator | [join](/apl/tabular-operators/join-operator) | Returns a dataset containing rows from two different tables based on conditions. |
| Tabular operator | [limit](/apl/tabular-operators/limit-operator) | Returns the top N rows from the input dataset. |
| Tabular operator | [lookup](/apl/tabular-operators/lookup-operator) | Returns a dataset where rows from one dataset are enriched with matching columns from a lookup table based on conditions. |
| Tabular operator | [make-series](/apl/tabular-operators/make-series) | Returns a dataset where the specified field is aggregated into a time series. |
| Tabular operator | [mv-expand](/apl/tabular-operators/mv-expand) | Returns a dataset where the specified field is expanded into multiple rows. |
| Tabular operator | [order](/apl/tabular-operators/order-operator) | Returns the input dataset, sorted according to the specified fields and order. |
| Tabular operator | [parse](/apl/tabular-operators/parse-operator) | Returns the input dataset with new fields added based on the specified parsing pattern. |
| Tabular operator | [parse-kv](/apl/tabular-operators/parse-kv) | Returns a dataset where key-value pairs are extracted from a string field into individual columns. |
| Tabular operator | [parse-where](/apl/tabular-operators/parse-where) | Returns a dataset where values from a string are extracted based on a pattern. |
| Tabular operator | [project-away](/apl/tabular-operators/project-away-operator) | Returns the input dataset excluding the specified fields. |
| Tabular operator | [project-keep](/apl/tabular-operators/project-keep-operator) | Returns a dataset with only the specified fields. |
| Tabular operator | [project-rename](/apl/tabular-operators/project-rename) | Returns a dataset where the specified field is renamed according to the specified pattern. |
| Tabular operator | [project-reorder](/apl/tabular-operators/project-reorder-operator) | Returns a table with the specified fields reordered as requested followed by any unspecified fields in their original order. |
| Tabular operator | [project](/apl/tabular-operators/project-operator) | Returns a dataset containing only the specified fields. |
| Tabular operator | [redact](/apl/tabular-operators/redact-operator) | Returns the input dataset with sensitive data replaced or hashed. |
| Tabular operator | [sample](/apl/tabular-operators/sample-operator) | Returns a table containing the specified number of rows, selected randomly from the input dataset. |
| Tabular operator | [search](/apl/tabular-operators/search-operator) | Returns all rows where the specified keyword appears in any field. |
| Tabular operator | [sort](/apl/tabular-operators/sort-operator) | Returns a table with rows ordered based on the specified fields. |
| Tabular operator | [summarize](/apl/tabular-operators/summarize-operator) | Returns a table where each row represents a unique combination of values from the by fields, with the aggregated results calculated for the other fields. |
| Tabular operator | [take](/apl/tabular-operators/take-operator) | Returns the specified number of rows from the dataset. |
| Tabular operator | [top](/apl/tabular-operators/top-operator) | Returns the top N rows from the dataset based on the specified sorting criteria. |
| Tabular operator | [union](/apl/tabular-operators/union-operator) | Returns all rows from the specified tables or queries. |
| Tabular operator | [where](/apl/tabular-operators/where-operator) | Returns a filtered dataset containing only the rows where the condition evaluates to true. |
| Time series function | [series\_abs](/apl/scalar-functions/time-series/series-abs) | Returns the absolute value of a series. |
| Time series function | [series\_acos](/apl/scalar-functions/time-series/series-acos) | Returns the inverse cosine (arccos) of a series. |
| Time series function | [series\_add](/apl/scalar-functions/time-series/series-add) | Performs element-wise addition between two series. |
| Time series function | [series\_asin](/apl/scalar-functions/time-series/series-asin) | Returns the inverse sine (arcsin) of a series. |
| Time series function | [series\_atan](/apl/scalar-functions/time-series/series-atan) | Returns the inverse tangent (arctan) of a series. |
| Time series function | [series\_ceiling](/apl/scalar-functions/time-series/series-ceiling) | Rounds each element up to the nearest integer. |
| Time series function | [series\_cos](/apl/scalar-functions/time-series/series-cos) | Returns the cosine of a series. |
| Time series function | [series\_cosine\_similarity](/apl/scalar-functions/time-series/series-cosine-similarity) | Calculates the cosine similarity between two series. |
| Time series function | [series\_divide](/apl/scalar-functions/time-series/series-divide) | Performs element-wise division between two series. |
| Time series function | [series\_dot\_product](/apl/scalar-functions/time-series/series-dot-product) | Calculates the dot product between two series. |
| Time series function | [series\_equals](/apl/scalar-functions/time-series/series-equals) | Compares each element in a series to a specified value and returns a boolean array. |
| Time series function | [series\_exp](/apl/scalar-functions/time-series/series-exp) | Calculates the exponential (e^x) of each element in a series. |
| Time series function | [series\_fft](/apl/scalar-functions/time-series/series-fft) | Performs a Fast Fourier Transform on a series, converting time-domain data into frequency-domain representation. |
| Time series function | [series\_fill\_backward](/apl/scalar-functions/time-series/series-fill-backward) | Fills missing values by propagating the last known value backward through the array. |
| Time series function | [series\_fill\_const](/apl/scalar-functions/time-series/series-fill-const) | Fills missing values with a specified constant value. |
| Time series function | [series\_fill\_forward](/apl/scalar-functions/time-series/series-fill-forward) | Fills missing values by propagating the first known value forward through the array. |
| Time series function | [series\_fill\_linear](/apl/scalar-functions/time-series/series-fill-linear) | Fills missing values using linear interpolation between known values. |
| Time series function | [series\_fir](/apl/scalar-functions/time-series/series-fir) | Applies a Finite Impulse Response filter to a series using a specified filter kernel. |
| Time series function | [series\_floor](/apl/scalar-functions/time-series/series-floor) | Rounds down each element in a series to the nearest integer. |
| Time series function | [series\_greater](/apl/scalar-functions/time-series/series-greater) | Returns the elements of a series that are greater than a specified value. |
| Time series function | [series\_greater\_equals](/apl/scalar-functions/time-series/series-greater-equals) | Returns the elements of a series that are greater than or equal to a specified value. |
| Time series function | [series\_ifft](/apl/scalar-functions/time-series/series-ifft) | Performs an Inverse Fast Fourier Transform on a series, converting frequency-domain data back into time-domain representation. |
| Time series function | [series\_iir](/apl/scalar-functions/time-series/series-iir) | Applies an Infinite Impulse Response filter to a series. |
| Time series function | [series\_less](/apl/scalar-functions/time-series/series-less) | Returns the elements of a series that are less than a specified value. |
| Time series function | [series\_less\_equals](/apl/scalar-functions/time-series/series-less-equals) | Returns the elements of a series that are less than or equal to a specified value. |
| Time series function | [series\_log](/apl/scalar-functions/time-series/series-log) | Returns the natural logarithm of each element in a series. |
| Time series function | [series\_magnitude](/apl/scalar-functions/time-series/series-magnitude) | Calculates the Euclidean norm (magnitude) of a series. |
| Time series function | [series\_max](/apl/scalar-functions/time-series/series-max) | Returns the maximum value from a series. |
| Time series function | [series\_min](/apl/scalar-functions/time-series/series-min) | Returns the minimum value from a series. |
| Time series function | [series\_multiply](/apl/scalar-functions/time-series/series-multiply) | Performs element-wise multiplication of two series. |
| Time series function | [series\_not\_equals](/apl/scalar-functions/time-series/series-not-equals) | Returns the elements of a series that aren’t equal to a specified value. |
| Time series function | [series\_pearson\_correlation](/apl/scalar-functions/time-series/series-pearson-correlation) | Calculates the Pearson correlation coefficient between two series. |
| Time series function | [series\_pow](/apl/scalar-functions/time-series/series-pow) | Raises each element in a series to a specified power. |
| Time series function | [series\_sign](/apl/scalar-functions/time-series/series-sign) | Returns the sign of each element in a series. |
| Time series function | [series\_sin](/apl/scalar-functions/time-series/series-sin) | Returns the sine of a series. |
| Time series function | [series\_stats](/apl/scalar-functions/time-series/series-stats) | Computes comprehensive statistical measures for a series. |
| Time series function | [series\_stats\_dynamic](/apl/scalar-functions/time-series/series-stats-dynamic) | Computes statistical measures and returns them in a dynamic object format. |
| Time series function | [series\_subtract](/apl/scalar-functions/time-series/series-subtract) | Performs element-wise subtraction between two series. |
| Time series function | [series\_sum](/apl/scalar-functions/time-series/series-sum) | Returns the sum of a series. |
| Time series function | [series\_tan](/apl/scalar-functions/time-series/series-tan) | Returns the tangent of a series. |
| Type function | [iscc](/apl/scalar-functions/type-functions/iscc) | Checks whether a value is a valid credit card (CC) number. |
| Type function | [isimei](/apl/scalar-functions/type-functions/isimei) | Checks whether a value is a valid International Mobile Equipment Identity (IMEI) number. |
| Type function | [ismap](/apl/scalar-functions/type-functions/ismap) | Checks whether a value is of the `dynamic` type and represents a mapping. |
| Type function | [isreal](/apl/scalar-functions/type-functions/isreal) | Checks whether a value is a real number. |
| Type function | [isstring](/apl/scalar-functions/type-functions/isstring) | Checks whether a value is a string. |
| Type function | [isutf8](/apl/scalar-functions/type-functions/isutf8) | Checks whether a value is a valid UTF-8 encoded sequence. |
---
# Axiom Processing Language (APL)
Source: https://axiom.co/docs/apl/introduction
Axiom Processing Language (APL) is a text-based query language for logs, traces, and events stored in [EventDB](/platform-overview/architecture). It provides the flexibility to filter, manipulate, and summarize your data exactly the way you need it.
You can't use APL to query metrics. To query metrics, use [MPL](/mpl/introduction).
## Build an APL query [#build-an-apl-query]
APL queries consist of the following:
* **Data source:** The most common data source is one of your Axiom datasets.
* **Operators:** Operators filter, manipulate, and summarize your data.
Delimit operators with the pipe character (`|`).
A typical APL query has the following structure:
```kusto
DatasetName
| Operator ...
| Operator ...
```
* `DatasetName` is the name of the dataset you want to query.
* `Operator` is an operation you apply to the data.
Apart from Axiom datasets, you can use other data sources:
* External data sources using the [externaldata](/apl/tabular-operators/externaldata-operator) operator.
* Specify a data table in the APL query itself using the `let` statement.
## Example query [#example-query]
```kusto
['github-issue-comment-event']
| extend isBot = actor contains '-bot' or actor contains '[bot]'
| where isBot == true
| summarize count() by bin_auto(_time), actor
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'github-issue-comment-event'%5D%20%7C%20extend%20isBot%20%3D%20actor%20contains%20'-bot'%20or%20actor%20contains%20'%5Bbot%5D'%20%7C%20where%20isBot%20%3D%3D%20true%20%7C%20summarize%20count\(\)%20by%20bin_auto\(_time\)%2C%20actor%22%7D)
The query above uses a dataset called `github-issue-comment-event` as its data source. It uses the following operators:
* [extend](/apl/tabular-operators/extend-operator) adds a new field `isBot` to the query results. It sets the values of the new field to true if the values of the `actor` field in the original dataset contain `-bot` or `[bot]`.
* [where](/apl/tabular-operators/where-operator) filters for the values of the `isBot` field. It only returns rows where the value is true.
* [summarize](/apl/tabular-operators/summarize-operator) aggregates the data and produces a chart.
Each operator is separated using the pipe character (`|`).
## Example result [#example-result]
As a result, the query returns a chart and a table. The table counts the different values of the `actor` field where `isBot` is true, and the chart displays the distribution of these counts over time.
| actor | count\_ |
| -------------------- | ------- |
| github-actions\[bot] | 487 |
| sonarqubecloud\[bot] | 208 |
| dependabot\[bot] | 148 |
| vercel\[bot] | 91 |
| codecov\[bot] | 63 |
| openshift-ci\[bot] | 52 |
| coderabbitai\[bot] | 43 |
| netlify\[bot] | 37 |
The query results are a representation of your data based on your request. The query doesn’t change the original dataset.
## Quote dataset and field names [#quote-dataset-and-field-names]
If the name of a dataset or field contains at least one of the following special characters, quote the name in your APL query:
* Space (` `)
* Dot (`.`)
* Dash (`-`)
To quote the dataset or field in your APL query, enclose its name with quotation marks (`'` or `"`) and square brackets (`[]`). For example, `['my-field']`.
For more information on rules about naming and quoting entities, see [Entity names](/apl/entities/entity-names).
## Common patterns [#common-patterns]
### Handle nested JSON [#handle-nested-json]
A common scenario is dealing with fields that contain JSON objects. Use `parse_json` to access nested data.
```kusto
['sample-http-logs']
| extend parsed_headers = parse_json(req_duration_ms)
| where isnotempty(method)
| project _time, method, status, geo.city
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20extend%20parsed_headers%20%3D%20parse_json\(req_duration_ms\)%5Cn%7C%20where%20isnotempty\(method\)%5Cn%7C%20project%20_time%2C%20method%2C%20status%2C%20%5B'geo.city'%5D%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
* `extend parsedField = parse_json(...)` converts JSON text into a structured field you can access with dot notation.
* `project _time, method, status, geo.city` selects only the fields you need.
### Filter and project early [#filter-and-project-early]
A well-written query runs faster, consumes fewer resources, and gets you answers more efficiently. The two most important principles:
1. **Filter early.** Reduce the amount of data as soon as possible.
2. **Project only what you need.** Avoid selecting unnecessary fields.
Some datasets are wide, containing hundreds or thousands of fields. When you query these datasets with APL, use `project` to select only the fields you need. Without `project`, Axiom retrieves all fields for each event, which slows down queries.
**Sub-optimal:**
```kusto
['sample-http-logs']
| sort by _time desc
| take 10
```
This retrieves all fields for each of the 10 events.
**Optimized:**
```kusto
['sample-http-logs']
| project _time, method, status, uri, resp_body_size_bytes
| sort by _time desc
| take 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20project%20_time%2C%20method%2C%20status%2C%20uri%2C%20resp_body_size_bytes%5Cn%7C%20sort%20by%20_time%20desc%5Cn%7C%20take%2010%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
By adding `project`, the query ignores all other fields, minimizing I/O and reducing data sent over the network.
* Always use `project` or `project-away` after your `where` filters to reduce data volume. `project` keeps specified fields, `project-away` removes them.
* Place your most restrictive `where` filters as early as possible in the query.
### Virtual fields [#virtual-fields]
[Virtual fields](/query-data/virtual-fields) let you define new fields based on an APL expression. Instead of pre-processing data before sending it to Axiom, you create these fields on the fly during a query. This provides flexibility for analysis without altering the raw data.
**Example: Simple conversion**
This example converts a response body size from bytes to kilobytes:
* **Name:** `resp_size_kb`
* **Expression:** `resp_body_size_bytes / 1024`
**Example: Categorization**
This example uses conditional logic to segment data. Define a virtual field to categorize HTTP responses:
* **Name:** `response_category`
* **Expression:** `case(status >= 500, "Server Error", status >= 400, "Client Error", status >= 300, "Redirect", "Success")`
Now you can run queries like `... | summarize count() by response_category` to compare behavior across these groups.
* Use virtual fields to avoid sending redundant data. If you can derive a value, you don't need to add it to your raw logs.
* Use virtual fields to normalize data from different sources. If one service logs `request_time` and another logs `duration`, create a virtual field using `coalesce(request_time, duration)` to standardize them.
* For very common, expensive transformations queried frequently, consider performing them at ingest time instead.
### Factors impacting query performance [#factors-impacting-query-performance]
* **Catch-all queries:** Queries that don't specify fields with `project` select all fields. Avoid this on high-dimensionality datasets.
* **High cardinality `summarize` operations:** When the `by` field has very many unique values (like a `traceId`), the query may produce an enormous number of groups. Axiom has built-in limits to protect against this.
* **Mixed data types:** If a field has mixed types (for example, a status code is sometimes a number `200` and sometimes a string `"200"`), queries can produce unexpected results.
For best performance, aim for consistent typing. If you can't avoid mixed types, normalize the data at query time using typecasting functions like `tostring()` or `toint()`. For example, `| where tostring(status) startswith "2"` works reliably on a field with mixed types.
For more information, see [Performance](/reference/performance).
### Platform limits [#platform-limits]
* **Fields per dataset:** A dataset can have a maximum number of fields. While the limit is high, ingesting logs with thousands of fields can cause issues.
* **Data retention:** Datasets have a configurable retention period. Data older than this period is automatically deleted.
* **Query rate limits:** Axiom imposes rate limits on queries to ensure service stability.
For more information, see [Limits](/reference/limits).
## What’s next [#whats-next]
Check out the [list of example queries](/apl/tutorial) or explore the supported operators and functions:
* [Scalar functions](/apl/scalar-functions/string-functions)
* [Aggregation functions](/apl/aggregation-function/statistical-functions)
* [Tabular operators](/apl/tabular-operators/overview)
* [Scalar operators](/apl/scalar-operators/logical-operators)
---
# Query reference overview
Source: https://axiom.co/docs/apl/overview
Axiom provides two purpose-built query languages, each designed for different data types:
| | APL | MPL |
| :------------- | :----------------------------------------- | :------------------------------------------- |
| **Full name** | Axiom Processing Language | Metrics Processing Language |
| **Data types** | Logs, traces, events | Metrics |
| **Data store** | [EventDB](/platform-overview/architecture) | [MetricsDB](/platform-overview/architecture) |
| **Reference** | [APL reference](/apl/introduction) | [MPL reference](/mpl/introduction) |
## Why two query languages [#why-two-query-languages]
Logs, traces, and events are discrete records: each row is a self-contained entry with an arbitrary schema. The language designed for this kind of data is APL (Axiom Processing Language), which excels at slicing, extending, and summarizing data.
Metrics are structurally different. A metric is a named time series with a fixed set of tags. Rather than rows of events, you work with continuous streams of numerical values that need rate calculations, time-window alignment, tag-based grouping, and cross-metric arithmetic. Axiom stores metrics in [MetricsDB](/platform-overview/architecture), a purpose-built storage engine optimized for high-cardinality time-series data. MPL is the query language designed to match that storage model.
## APL (Axiom Processing Language) [#apl-axiom-processing-language]
APL is a text-based query language for querying logs, traces, and events stored in EventDB. It provides the flexibility to filter, manipulate, extend, and summarize your event data exactly the way you need it.
An APL query starts with a dataset name followed by a series of operators separated by the pipe character (`|`). Operators flow from left to right, top to bottom, transforming the data at each step.
```kusto
['sample-http-logs']
| where method == "GET" and status == "200"
| summarize count() by bin_auto(_time), ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20where%20method%20%3D%3D%20%5C%22GET%5C%22%20and%20status%20%3D%3D%20%5C%22200%5C%22%5Cn%7C%20summarize%20count\(\)%20by%20bin_auto\(_time\)%2C%20%5B'geo.country'%5D%22%7D)
APL supports a rich set of [tabular operators](/apl/tabular-operators/overview), [scalar functions](/apl/scalar-functions/string-functions), and [aggregation functions](/apl/aggregation-function/statistical-functions) that cover a wide range of use cases from simple filtering to complex joins, time-series analysis, and GenAI-specific computations.
For more information, see [Introduction to APL](/apl/introduction).
## MPL (Metrics Processing Language) [#mpl-metrics-processing-language]
MPL is a query language for querying metrics stored in MetricsDB. It combines a pipeline syntax similar to APL with expressive power suited to metrics workloads like rate calculations, time-window alignment, and tag-based grouping.
Support for MPL is currently in public preview. For more information, see [Feature states](/platform-overview/roadmap#feature-states).
An MPL query starts with a dataset and metric name, then pipes the data through filters and transformations.
```kusto
`otel-demo-metrics`:`go.memory.used`
| where `k8s.deployment.name` == "checkout"
| align to 5m using avg
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%60otel-demo-metrics%60%3A%60go.memory.used%60%20%7C%20where%20%60k8s.deployment.name%60%20%3D%3D%20%5C%22checkout%5C%22%20%7C%20align%20to%205m%20using%20avg%22%2C%22metricsDataset%22%3A%22otel-demo-metrics%22%7D)
MPL supports filtering by tags, mapping values with functions like `rate` and `fill::prev`, aligning data to time windows, grouping by tag dimensions, and computing across multiple metrics.
If you use PromQL, you can easily [translate your existing expressions to MPL](/mpl/migrate-metrics).
For more information, see [Introduction to MPL](/mpl/introduction).
## Write queries [#write-queries]
Write APL and MPL queries in the following ways:
* **Console**: Use the Query tab in the Axiom Console to write and run queries interactively. For more information, see [Query data using Editor](/query-data/query-editor).
* **API**: Run queries programmatically using Axiom API. For more information, see [API reference](/restapi/query).
* **AI**: Use natural language to generate APL queries in Console, or use [MCP Server](/console/intelligence/mcp-server) and [Skills](/console/intelligence/skills) to let AI agents query your data.
---
# Sample queries
Source: https://axiom.co/docs/apl/tutorial
This page shows you how to query your data using APL through a wide range of sample queries. You can try out each example in the [Axiom Playground](https://play.axiom.co/axiom-play-qf1k/query).
For an introduction to APL and to the structure of an APL query, see [Introduction to APL](/apl/introduction).
## Summarize data [#summarize-data]
[summarize](/apl/tabular-operators/summarize-operator) produces a table that aggregates the content of the dataset. Use the [aggregation functions](/apl/aggregation-function/statistical-functions) with the `summarize` operator to produce different fields.
The following query counts events by time bins.
```kusto
['sample-http-logs']
| summarize count() by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20summarize%20count%28%29%20by%20bin_auto%28_time%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
The example below summarizes the top 10 GitHub push events by maximum push ID.
```kusto
['github-push-event']
| summarize max_if = maxif(push_id, true) by size
| top 10 by max_if desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27github-push-event%27%5D%5Cn%7C%20summarize%20max_if%20%3D%20maxif%28push_id%2C%20true%29%20by%20size%5Cn%7C%20top%2010%20by%20max_if%20desc%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
The example below summarizes the distinct city count by server datacenter.
```kusto
['sample-http-logs']
| summarize cities = dcount(['geo.city']) by server_datacenter
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20summarize%20cities%20%3D%20dcount%28%5B%27geo.city%27%5D%29%20by%20server_datacenter%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
## Tabular operators [#tabular-operators]
### where [#where]
[where](/apl/tabular-operators/where-operator) filters the content of the dataset that meets a condition when executed.
The following query filters the data by `method` and `content_type`:
```kusto
['sample-http-logs']
| where method == "GET" and content_type == "application/octet-stream"
| project method , content_type
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20where%20method%20%3D%3D%20%5C%22GET%5C%22%20and%20content_type%20%3D%3D%20%5C%22application%2Foctet-stream%5C%22%5Cn%7C%20project%20method%20%2C%20content_type%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
### count [#count]
[count](/apl/tabular-operators/count-operator) returns the number of events from the input dataset.
```kusto
['sample-http-logs']
| count
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20count%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
### project [#project]
[project](/apl/tabular-operators/project-operator) selects a subset of fields.
```kusto
['sample-http-logs']
| project content_type, ['geo.country'], method, resp_body_size_bytes, resp_header_size_bytes
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20project%20content_type%2C%20%5B%27geo.country%27%5D%2C%20method%2C%20resp_body_size_bytes%2C%20resp_header_size_bytes%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
### take [#take]
[take](/apl/tabular-operators/take-operator) returns up to the specified number of rows.
```kusto
['sample-http-logs']
| take 100
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20take%20100%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
### limit [#limit]
The `limit` operator is an alias to the `take` operator.
```kusto
['sample-http-logs']
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20limit%2010%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
## Scalar functions [#scalar-functions]
### parse\_json [#parse_json]
[parse\_json](/apl/scalar-functions/string-functions#parse-json) extracts the JSON elements from an array.
```kusto
['sample-http-logs']
| project parsed_json = parse_json( "config_jsonified_metrics")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20project%20parsed_json%20%3D%20parse_json%28%20%5C%22config_jsonified_metrics%5C%22%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
### replace\_string [#replace_string]
[replace\_string](/apl/scalar-functions/string-functions#parse-json) replaces all string matches with another string.
```kusto
['sample-http-logs']
| extend replaced_string = replace_string( "creator", "method", "machala" )
| project replaced_string
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20replaced_string%20%3D%20replace_string%28%20%5C%22creator%5C%22%2C%20%5C%22method%5C%22%2C%20%5C%22machala%5C%22%20%29%5Cn%7C%20project%20replaced_string%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
### split [#split]
[split](/apl/scalar-functions/string-functions#split) splits a given string according to a given delimiter and returns a string array.
```kusto
['sample-http-logs']
| project split_str = split("method_content_metrics", "_")
| take 20
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20project%20split_str%20%3D%20split%28%5C%22method_content_metrics%5C%22%2C%20%5C%22_%5C%22%29%5Cn%7C%20take%2020%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
### strcat\_delim [#strcat_delim]
[strcat\_delim](/apl/scalar-functions/string-functions#strcat-delim) concatenates a string array into a string with a given delimiter.
```kusto
['sample-http-logs']
| project strcat = strcat_delim(":", ['geo.city'], resp_body_size_bytes)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20project%20strcat%20%3D%20strcat_delim%28%5C%22%3A%5C%22%2C%20%5B%27geo.city%27%5D%2C%20resp_body_size_bytes%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
### indexof [#indexof]
[indexof](/apl/scalar-functions/string-functions#indexof) reports the zero-based index of the first occurrence of a specified string within the input string.
```kusto
['sample-http-logs']
| extend based_index = indexof( ['geo.country'], content_type, 45, 60, resp_body_size_bytes ), specified_time = bin(resp_header_size_bytes, 30)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20based_index%20%3D%20%20indexof%28%20%5B%27geo.country%27%5D%2C%20content_type%2C%2045%2C%2060%2C%20resp_body_size_bytes%20%29%2C%20specified_time%20%3D%20bin%28resp_header_size_bytes%2C%2030%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
## Regex examples [#regex-examples]
**Remove leading characters**
```kusto
['sample-http-logs']
| project remove_cutset = trim_start_regex("[^a-zA-Z]", content_type )
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20project%20remove_cutset%20%3D%20trim_start_regex%28%5C%22%5B%5Ea-zA-Z%5D%5C%22%2C%20content_type%20%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Find logs from a city**
```kusto
['sample-http-logs']
| where tostring(geo.city) matches regex "^Camaquã$"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20where%20tostring%28%5B%27geo.city%27%5D%29%20matches%20regex%20%5C%22%5ECamaqu%C3%A3%24%5C%22%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Identify logs from a user agent**
```kusto
['sample-http-logs']
| where tostring(user_agent) matches regex "Mozilla/5.0"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20where%20tostring%28user_agent%29%20matches%20regex%20%5C%22Mozilla%2F5.0%5C%22%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Find logs with response body size in a certain range**
```kusto
['sample-http-logs']
| where toint(resp_body_size_bytes) >= 4000 and toint(resp_body_size_bytes) <= 5000
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20where%20toint%28resp_body_size_bytes%29%20%3E%3D%204000%20and%20toint%28resp_body_size_bytes%29%20%3C%3D%205000%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Find logs with user agents containing Windows NT**
```kusto
['sample-http-logs']
| where tostring(user_agent) matches regex @"Windows NT [\d\.]+"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?qid=m8yNkSVVjGq-s0z19c)
**Find logs with specific response header size**
```kusto
['sample-http-logs']
| where toint(resp_header_size_bytes) == 31
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20where%20toint%28resp_header_size_bytes%29%20%3D%3D%2031%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Find logs with specific request duration**
```kusto
['sample-http-logs']
| where toreal(req_duration_ms) < 1
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20where%20toreal%28req_duration_ms%29%20%3C%201%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Find logs where TLS is enabled and method is POST**
```kusto
['sample-http-logs']
| where tostring(is_tls) == "true" and tostring(method) == "POST"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20where%20tostring%28is_tls%29%20%3D%3D%20%5C%22true%5C%22%20and%20tostring%28method%29%20%3D%3D%20%5C%22POST%5C%22%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
## Array functions [#array-functions]
### array\_concat [#array_concat]
[array\_concat](/apl/scalar-functions/array-functions#array_concat) concatenates a number of dynamic arrays to a single array.
```kusto
['sample-http-logs']
| extend concatenate = array_concat( dynamic([5,4,3,87,45,2,3,45]))
| project concatenate
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20concatenate%20%3D%20array_concat%28%20dynamic%28%5B5%2C4%2C3%2C87%2C45%2C2%2C3%2C45%5D%29%29%5Cn%7C%20project%20concatenate%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
### array\_sum [#array_sum]
[array\_sum](/apl/scalar-functions/array-functions#array-sum) calculates the sum of elements in a dynamic array.
```kusto
['sample-http-logs']
| extend summary_array=dynamic([1,2,3,4])
| project summary_array=array_sum(summary_array)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20summary_array%3Ddynamic%28%5B1%2C2%2C3%2C4%5D%29%5Cn%7C%20project%20summary_array%3Darray_sum%28summary_array%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
## Conversion functions [#conversion-functions]
### todatetime [#todatetime]
[todatetime](/apl/scalar-functions/conversion-functions#todatetime) converts input to datetime scalar.
```kusto
['sample-http-logs']
| extend dated_time = todatetime("2026-08-16")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20dated_time%20%3D%20todatetime%28%5C%222026-08-16%5C%22%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
### dynamic\_to\_json [#dynamic_to_json]
[dynamic\_to\_json](/apl/scalar-functions/conversion-functions#dynamic-to-json) converts a scalar value of type dynamic to a canonical string representation.
```kusto
['sample-http-logs']
| extend dynamic_string = dynamic_to_json(dynamic([10,20,30,40 ]))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20dynamic_string%20%3D%20dynamic_to_json%28dynamic%28%5B10%2C20%2C30%2C40%20%5D%29%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
## Scalar operators [#scalar-operators]
APL supports a wide range of scalar operators:
* [String operators](/apl/scalar-operators/string-operators)
* [Logical operators](/apl/scalar-operators/logical-operators)
* [Numerical operators](/apl/scalar-operators/numerical-operators)
### contains [#contains]
The query below uses the `contains` operator to find the strings that contain the string `-bot` and `[bot]`:
```kusto
['github-issue-comment-event']
| extend bot = actor contains "-bot" or actor contains "[bot]"
| where bot == true
| summarize count() by bin_auto(_time), actor
| take 20
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27github-issue-comment-event%27%5D%5Cn%7C%20extend%20bot%20%3D%20actor%20contains%20%5C%22-bot%5C%22%20or%20actor%20contains%20%5C%22%5Bbot%5D%5C%22%5Cn%7C%20where%20bot%20%3D%3D%20true%5Cn%7C%20summarize%20count%28%29%20by%20bin_auto%28_time%29%2C%20actor%5Cn%7C%20take%2020%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
```kusto
['sample-http-logs']
| extend user_status = status contains "200" , agent_flow = user_agent contains "(Windows NT 6.4; AppleWebKit/537.36 Chrome/41.0.2225.0 Safari/537.36"
| where user_status == true
| summarize count() by bin_auto(_time), status
| take 15
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20user_status%20%3D%20status%20contains%20%5C%22200%5C%22%20%2C%20agent_flow%20%3D%20user_agent%20contains%20%5C%22%28Windows%20NT%206.4%3B%20AppleWebKit%2F537.36%20Chrome%2F41.0.2225.0%20Safari%2F537.36%5C%22%5Cn%7C%20where%20user_status%20%3D%3D%20true%5Cn%7C%20summarize%20count%28%29%20by%20bin_auto%28_time%29%2C%20status%5Cn%7C%20take%2015%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
## Hash functions [#hash-functions]
* [hash\_md5](/apl/scalar-functions/hash-functions#hash-md5) returns an MD5 hash value for the input value.
* [hash\_sha256](/apl/scalar-functions/hash-functions#hash-sha256) returns a sha256 hash value for the input value.
* [hash\_sha1](/apl/scalar-functions/hash-functions#hash-sha1) returns a sha1 hash value for the input value.
```kusto
['sample-http-logs']
| extend sha_256 = hash_md5( "resp_header_size_bytes" ), sha_1 = hash_sha1( content_type), md5 = hash_md5( method), sha512 = hash_sha512( "resp_header_size_bytes" )
| project sha_256, sha_1, md5, sha512
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20sha_256%20%3D%20hash_md5%28%20%5C%22resp_header_size_bytes%5C%22%20%29%2C%20sha_1%20%3D%20hash_sha1%28%20content_type%29%2C%20md5%20%3D%20hash_md5%28%20method%29%2C%20sha512%20%3D%20hash_sha512%28%20%5C%22resp_header_size_bytes%5C%22%20%29%5Cn%7C%20project%20sha_256%2C%20sha_1%2C%20md5%2C%20sha512%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
## Rounding functions [#rounding-functions]
* [floor()](/apl/scalar-functions/rounding-functions#floor) calculates the largest integer less than, or equal to, the specified numeric expression.
* [ceiling()](/apl/scalar-functions/rounding-functions#ceiling) calculates the smallest integer greater than, or equal to, the specified numeric expression.
* [bin()](/apl/scalar-functions/rounding-functions#bin) rounds values down to an integer multiple of a given bin size.
```kusto
['sample-http-logs']
| extend largest_integer_less = floor( resp_header_size_bytes ), smallest_integer_greater = ceiling( req_duration_ms ), integer_multiple = bin( resp_body_size_bytes, 5 )
| project largest_integer_less, smallest_integer_greater, integer_multiple
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20largest_integer_less%20%3D%20floor%28%20resp_header_size_bytes%20%29%2C%20smallest_integer_greater%20%3D%20ceiling%28%20req_duration_ms%20%29%2C%20integer_multiple%20%3D%20bin%28%20resp_body_size_bytes%2C%205%20%29%5Cn%7C%20project%20largest_integer_less%2C%20smallest_integer_greater%2C%20integer_multiple%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Truncate decimals using round function**
```kusto
['sample-http-logs']
| project rounded_value = round(req_duration_ms, 2)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20project%20rounded_value%20%3D%20round%28req_duration_ms%2C%202%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Truncate decimals using floor function**
```kusto
['sample-http-logs']
| project floor_value = floor(resp_body_size_bytes), ceiling_value = ceiling(req_duration_ms)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20project%20floor_value%20%3D%20floor%28resp_body_size_bytes%29%2C%20ceiling_value%20%3D%20ceiling%28req_duration_ms%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
## Other examples [#other-examples]
**List all unique groups**
```kusto
['sample-http-logs']
| distinct ['id'], is_tls
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20distinct%20%5B'id'%5D%2C%20is_tls%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Count of all events per service**
```kusto
['sample-http-logs']
| summarize Count = count() by server_datacenter
| order by Count desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20summarize%20Count%20%3D%20count%28%29%20by%20server_datacenter%5Cn%7C%20order%20by%20Count%20desc%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Change the time clause**
```kusto
['github-issues-event']
| where _time == ago(1m)
| summarize count(), sum(['milestone.number']) by _time=bin(_time, 1m)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27github-issues-event%27%5D%5Cn%7C%20where%20_time%20%3D%3D%20ago%281m%29%5Cn%7C%20summarize%20count%28%29%2C%20sum%28%5B%27milestone.number%27%5D%29%20by%20_time%3Dbin%28_time%2C%201m%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**HTTP 5xx responses for the last 7 days, one bar per day**
```kusto
['sample-http-logs']
| where _time > ago(7d)
| where req_duration_ms >= 5 and req_duration_ms < 6
| summarize count(), histogram(resp_header_size_bytes, 20) by bin(_time, 1d)
| order by _time desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20_time%20%3E%20ago\(7d\)%20%7C%20where%20req_duration_ms%20%3E%3D%205%20and%20req_duration_ms%20%3C%206%20%7C%20summarize%20count\(\)%2C%20histogram\(resp_header_size_bytes%2C%2020\)%20by%20bin\(_time%2C%201d\)%20%7C%20order%20by%20_time%20desc%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%227d%22%7D%7D)
**Implement a remapper on remote address logs**
```kusto
['sample-http-logs']
| extend RemappedStatus = case(req_duration_ms >= 0.57, "new data", resp_body_size_bytes >= 1000, "size bytes", resp_header_size_bytes == 40, "header values", "doesntmatch")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20extend%20RemappedStatus%20%3D%20case%28req_duration_ms%20%3E%3D%200.57%2C%20%5C%22new%20data%5C%22%2C%20resp_body_size_bytes%20%3E%3D%201000%2C%20%5C%22size%20bytes%5C%22%2C%20resp_header_size_bytes%20%3D%3D%2040%2C%20%5C%22header%20values%5C%22%2C%20%5C%22doesntmatch%5C%22%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Advanced aggregations**
```kusto
['sample-http-logs']
| extend prospect = ['geo.city'] contains "Okayama" or uri contains "/api/v1/messages/back"
| extend possibility = server_datacenter contains "GRU" or status contains "301"
| summarize count(), topk( user_agent, 6 ) by bin(_time, 10d), ['geo.country']
| take 4
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20prospect%20%3D%20%5B%27geo.city%27%5D%20contains%20%5C%22Okayama%5C%22%20or%20uri%20contains%20%5C%22%2Fapi%2Fv1%2Fmessages%2Fback%5C%22%5Cn%7C%20extend%20possibility%20%3D%20server_datacenter%20contains%20%5C%22GRU%5C%22%20or%20status%20contains%20%5C%22301%5C%22%5Cn%7C%20summarize%20count%28%29%2C%20topk%28%20user_agent%2C%206%20%29%20by%20bin%28_time%2C%2010d%29%2C%20%5B%27geo.country%27%5D%5Cn%7C%20take%204%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Search map fields**
```kusto
['otel-demo-traces']
| where isnotnull( ['attributes.custom'])
| extend extra = tostring(['attributes.custom'])
| search extra:"0PUK6V6EV0"
| project _time, trace_id, name, ['attributes.custom']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%5Cn%7C%20where%20isnotnull%28%20%5B'attributes.custom'%5D%29%5Cn%7C%20extend%20extra%20%3D%20tostring%28%5B'attributes.custom'%5D%29%5Cn%7C%20search%20extra%3A%5C%220PUK6V6EV0%5C%22%5Cn%7C%20project%20_time%2C%20trace_id%2C%20name%2C%20%5B'attributes.custom'%5D%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Configure processing rules**
```kusto
['sample-http-logs']
| where _sysTime > ago(1d)
| summarize count() by method
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20where%20_sysTime%20%3E%20ago%281d%29%5Cn%7C%20summarize%20count%28%29%20by%20method%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%221d%22%7D%7D)
**Return different values based on the evaluation of a condition**
```kusto
['sample-http-logs']
| extend MemoryUsageStatus = iff(req_duration_ms > 10000, "Highest", "Normal")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20MemoryUsageStatus%20%3D%20iff%28req_duration_ms%20%3E%2010000%2C%20%27Highest%27%2C%20%27Normal%27%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Working with different operators**
```kusto
['hn']
| extend superman = text contains "superman" or title contains "superman"
| extend batman = text contains "batman" or title contains "batman"
| extend hero = case(
superman and batman, "both",
superman, "superman ", // spaces change the color
batman, "batman ",
"none")
| where (superman or batman) and not (batman and superman)
| summarize count(), topk(type, 3) by bin(_time, 30d), hero
| take 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27hn%27%5D%5Cn%7C%20extend%20superman%20%3D%20text%20contains%20%5C%22superman%5C%22%20or%20title%20contains%20%5C%22superman%5C%22%5Cn%7C%20extend%20batman%20%3D%20text%20contains%20%5C%22batman%5C%22%20or%20title%20contains%20%5C%22batman%5C%22%5Cn%7C%20extend%20hero%20%3D%20case%28%5Cn%20%20%20%20superman%20and%20batman%2C%20%5C%22both%5C%22%2C%5Cn%20%20%20%20superman%2C%20%5C%22superman%20%20%20%5C%22%2C%20%2F%2F%20spaces%20change%20the%20color%5Cn%20%20%20%20batman%2C%20%5C%22batman%20%20%20%20%20%20%20%5C%22%2C%5Cn%20%20%20%20%5C%22none%5C%22%29%5Cn%7C%20where%20%28superman%20or%20batman%29%20and%20not%20%28batman%20and%20superman%29%5Cn%7C%20summarize%20count%28%29%2C%20topk%28type%2C%203%29%20by%20bin%28_time%2C%2030d%29%2C%20hero%5Cn%7C%20take%2010%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
```kusto
['sample-http-logs']
| summarize flow = dcount( content_type) by ['geo.country']
| take 50
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20summarize%20flow%20%3D%20dcount%28%20content_type%29%20by%20%5B%27geo.country%27%5D%5Cn%7C%20take%2050%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Get the JSON into a property bag using parse-json**
```kusto
example
| where isnotnull(log)
| extend parsed_log = parse_json(log)
| project service, parsed_log.level, parsed_log.message
```
**Get average response using project-keep**
```kusto
['sample-http-logs']
| where ['geo.country'] == "United States" or ['id'] == 'b2b1f597-0385-4fed-a911-140facb757ef'
| extend systematic_view = ceiling( resp_header_size_bytes )
| extend resp_avg = cos( resp_body_size_bytes )
| project-away systematic_view
| project-keep resp_avg
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20where%20%5B'geo.country'%5D%20%3D%3D%20%5C%22United%20States%5C%22%20or%20%5B'id'%5D%20%3D%3D%20%5C%22b2b1f597-0385-4fed-a911-140facb757ef%5C%22%5Cn%7C%20extend%20systematic_view%20%3D%20ceiling%28%20resp_header_size_bytes%20%29%5Cn%7C%20extend%20resp_avg%20%3D%20cos%28%20resp_body_size_bytes%20%29%5Cn%7C%20project-away%20systematic_view%5Cn%7C%20project-keep%20resp_avg%5Cn%7C%20take%205%22%7D)
**Combine multiple percentiles into a single chart**
```kusto
['sample-http-logs']
| summarize percentiles_array(req_duration_ms, 50, 75, 90) by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20percentiles_array\(req_duration_ms%2C%2050%2C%2075%2C%2090\)%20by%20bin_auto\(_time\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Combine mathematical functions**
```kusto
['sample-http-logs']
| extend tangent = tan( req_duration_ms ), cosine = cos( resp_header_size_bytes ), absolute_input = abs( req_duration_ms ), sine = sin( resp_header_size_bytes ), power_factor = pow( req_duration_ms, 4)
| extend angle_pi = degrees( resp_body_size_bytes ), pie = pi()
| project tangent, cosine, absolute_input, angle_pi, pie, sine, power_factor
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20tangent%20%3D%20tan%28%20req_duration_ms%20%29%2C%20cosine%20%3D%20cos%28%20resp_header_size_bytes%20%29%2C%20absolute_input%20%3D%20abs%28%20req_duration_ms%20%29%2C%20sine%20%3D%20sin%28%20resp_header_size_bytes%20%29%2C%20power_factor%20%3D%20pow%28%20req_duration_ms%2C%204%29%5Cn%7C%20extend%20angle_pi%20%3D%20degrees%28%20resp_body_size_bytes%20%29%2C%20pie%20%3D%20pi%28%29%5Cn%7C%20project%20tangent%2C%20cosine%2C%20absolute_input%2C%20angle_pi%2C%20pie%2C%20sine%2C%20power_factor%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
```kusto
['github-issues-event']
| where actor !endswith "[bot]"
| where repo startswith "kubernetes/"
| where action == "opened"
| summarize count() by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27github-issues-event%27%5D%5Cn%7C%20where%20actor%20%21endswith%20%5C%22%5Bbot%5D%5C%22%5Cn%7C%20where%20repo%20startswith%20%5C%22kubernetes%2F%5C%22%5Cn%7C%20where%20action%20%3D%3D%20%5C%22opened%5C%22%5Cn%7C%20summarize%20count%28%29%20by%20bin_auto%28_time%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Change global configuration attributes**
```kusto
['sample-http-logs']
| extend status = coalesce(status, "info")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20status%20%3D%20coalesce\(status%2C%20%5C%22info%5C%22\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Set defualt value on event field**
```kusto
['sample-http-logs']
| project status = case(
isnotnull(status) and status != "", content_type, // use the contenttype if it’s not null and not an empty string
"info" // default value
)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20project%20status%20%3D%20case\(isnotnull\(status\)%20and%20status%20!%3D%20%5C%22%5C%22%2C%20content_type%2C%20%5C%22info%5C%22\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
**Extract nested payment amount from custom attributes map field**
```kusto
['otel-demo-traces']
| extend amount = ['attributes.custom']['app.payment.amount']
| where isnotnull( amount)
| project _time, trace_id, name, amount, ['attributes.custom']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20amount%20%3D%20%5B'attributes.custom'%5D%5B'app.payment.amount'%5D%20%7C%20where%20isnotnull\(%20amount\)%20%7C%20project%20_time%2C%20trace_id%2C%20name%2C%20amount%2C%20%5B'attributes.custom'%5D%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
**Filtering GitHub issues by label identifier**
```kusto
['github-issues-event']
| extend data = tostring(labels)
| where labels contains "d73a4a"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'github-issues-event'%5D%20%7C%20extend%20data%20%3D%20tostring\(labels\)%20%7C%20where%20labels%20contains%20'd73a4a'%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
**Aggregate trace counts by HTTP method attribute in custom map**
```kusto
['otel-demo-traces']
| extend httpFlavor = tostring(['attributes.custom'])
| summarize Count=count() by ['attributes.http.method']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20httpFlavor%20%3D%20tostring\(%5B'attributes.custom'%5D\)%20%7C%20summarize%20Count%3Dcount\(\)%20by%20%5B'attributes.http.method'%5D%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
---
# Introduction to MPL
Source: https://axiom.co/docs/mpl/introduction
MPL is a metric-focused query language that combines the simplicity of APL with the expressive power of PromQL. It enables effective querying, transformation, and aggregation of metric data, supporting diverse observability use cases.
If you use PromQL, your existing expressions can be translated to MPL for quick onboarding and greater flexibility. For more information, see [Migrate PromQL queries to Axiom](/mpl/migrate-metrics).
Support for MPL (Metrics Processing Language) is currently in public preview. For more information, see [Feature states](/platform-overview/roadmap#feature-states).
## Limitations [#limitations]
The current implementation of MPL comes with the following limitations:
* You can only query one dataset in a query.
## Concepts [#concepts]
* **Dataset:** A group of related metrics.
* **Metric:** Two-dimensional time series data with a metric name and a set of tags.
* **Tag:** Key-value pair identifying a series.
* **Series:** A unique combination of a metric and tag set.
## Query structure [#query-structure]
A typical MPL query contains the following:
1. **Source**: Defines dataset, metric, and optional time range
2. **Filter**: Applies conditions to series via tags
3. **Transformation** can be the following:
* **Map:** Maps the data to a new value.
* **Align:** Aggregates the data over time to align to a given time interval.
* **Group:** Aggregates the data over tag values.
* **Bucket:** A two-dimensional transformation that aggregates along both the time and tag dimensions.
**Example:**
```kusto
`otel-demo-metrics`:`go.memory.used`
| where `k8s.deployment.name` == "checkout"
| align to 5m using avg
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%60otel-demo-metrics%60%3A%60go.memory.used%60%20%7C%20where%20%60k8s.deployment.name%60%20%3D%3D%20%5C%22checkout%5C%22%20%7C%20align%20to%205m%20using%20avg%22%2C%22metricsDataset%22%3A%22otel-demo-metrics%22%7D)
This example queries the `otel-demo-metrics` dataset's `go.memory.used` metric one hour before the current time. It filters results to the `frontend` service and aggregates values over 5-minute time windows into their average.
## Elements of queries [#elements-of-queries]
The following explains each element of an MPL query.
To learn more about the language features of MPL, see [Language features](#language-features).
### Directives [#directives]
Use `set` directives at the start of a query to control query behavior and visualization settings.
Directives must appear before the query body.
**Syntax:**
```kusto
set = ;
```
#### Supported directives [#supported-directives]
The following directives are currently supported in Axiom's MPL implementation:
| Directive | Description |
| ------------- | ----------------------------------------------------------- |
| `custom_unit` | Sets the unit used when visualizing query results in Axiom. |
Use string values for `custom_unit`. Set it to `"1"` to render values as unitless numbers.
**Examples:**
```kusto
set custom_unit = "ms";
`otel-demo-metrics`:`http.server.request.duration`
| align to 5m using avg
```
```kusto
set custom_unit = "1";
`otel-demo-metrics`:`http.server.request.duration`
| align to 5m using avg
```
### Source [#source]
Specify the dataset, the metric, and optional time bounds.
**Syntax:**
```kusto
:[][ as ]
```
* **dataset**: Name of the dataset.
* **metric**: Name of the metric.
* **time range**: Optional: The time range of the query. For more information, see [Time ranges](#time-ranges).
* **alias**: Optional: Renames the metric for later use.
**Examples:**
```kusto
`otel-demo-metrics`:`go.memory.used`[1h..]
`otel-demo-metrics`:`go.memory.used`[2h..5m]
`otel-demo-metrics`:`go.memory.used`[2025-03-01T13:00:00Z..+1h] as mem_usage
```
### Filter [#filter]
Use `where` to filter series based on tag values.
**Syntax:**
```kusto
| where
```
A filter expression can be one of the following:
* ` ` — a single tag filter
* ` and ` — logical AND of two expressions
* ` or ` — logical OR of two expressions
* `not ` — negation of an expression
* `()` — parentheses to control order of evaluation
Available operators for single tag filters:
* Equality: `==`, `!=`
* Comparisons: `<`, `<=`, `>`, `>=`
The value must be one of the [supported data types](#data-types).
**Examples:**
```kusto
| where environment == "production" and status_code >= 200 and status_code < 300
| where (environment == "production" or environment == "staging") and not status_code == 500
```
### Map [#map]
Use `map` to transform individual values.
Available functions:
| Function | Description |
| --------------------- | -------------------------------------------------------------------- |
| `rate` | Computes the per-second rate of change for a metric. |
| `increase` | Calculates the increase between the data point and the previous one. |
| `min(arg)` | Returns the minimum between the argument and the value. |
| `max(arg)` | Returns the maximum between the argument and the value. |
| `abs` | Returns the absolute value of each data point. |
| `fill::prev` | Fills missing values using the previous non-null value. |
| `fill::const(arg)` | Fills missing values with a constant. |
| `interpolate::linear` | Linear interpolation of missing values. |
| `+`, `-`, `*`, `/` | Performs the respective mathematical calculation on each value. |
**Examples:**
```kusto
// Calculate rate per second for the metric
| map rate
// Add 5 to each value
| map + 5
// Fill empty values with the latest value
| map fill::prev
// Fill empty values with zeros
| map fill::const(0)
```
#### filter:: functions [#filter-functions]
Use `filter::` functions to remove data points that don't match a condition. Data points that don't match are removed from the series entirely. Unlike `where`, which filters series based on tag values, `filter::` operates on the numeric values of data points within a series.
| Function | Description |
| ---------------- | ---------------------------------------------------- |
| `filter::eq(v)` | Keeps only data points equal to `v`. |
| `filter::neq(v)` | Keeps only data points not equal to `v`. |
| `filter::gt(v)` | Keeps only data points greater than `v`. |
| `filter::gte(v)` | Keeps only data points greater than or equal to `v`. |
| `filter::lt(v)` | Keeps only data points less than `v`. |
| `filter::lte(v)` | Keeps only data points less than or equal to `v`. |
**Example:**
```kusto
// Remove data points where latency exceeds 400ms
| map filter::lt(0.4)
```
#### is:: functions [#is-functions]
Use `is::` functions to test data points against a condition. Matching data points are set to `1.0` and non-matching data points are set to `0.0`. The series retains all its data points.
Use `is::` instead of `filter::` when you need to preserve the shape of the time series, for example in SLO calculations where gaps in data would produce incorrect results.
| Function | Description |
| ------------ | ---------------------------------------------------------------------------- |
| `is::eq(v)` | Sets data points equal to `v` to `1.0`, all others to `0.0`. |
| `is::neq(v)` | Sets data points not equal to `v` to `1.0`, all others to `0.0`. |
| `is::gt(v)` | Sets data points greater than `v` to `1.0`, all others to `0.0`. |
| `is::gte(v)` | Sets data points greater than or equal to `v` to `1.0`, all others to `0.0`. |
| `is::lt(v)` | Sets data points less than `v` to `1.0`, all others to `0.0`. |
| `is::lte(v)` | Sets data points less than or equal to `v` to `1.0`, all others to `0.0`. |
**Example:**
```kusto
// Set to 1.0 where latency is within SLO (below 400ms), 0.0 otherwise
| map is::lt(0.4)
```
### Align [#align]
Use `align` to aggregate over time windows. You can specify the time window and the aggregation function to apply.
If you omit `to `, `align` aggregates over the full query range and returns a single value per series.
**Syntax:**
```kusto
| align to using
| align using
```
Available aggregation functions:
| Function | Description |
| ------------ | -------------------------------------- |
| `avg` | Averages values in each interval. |
| `count` | Counts non-null values per interval. |
| `max` | Takes the maximum value per interval. |
| `min` | Takes the minimum value per interval. |
| `prom::rate` | PromQL-style rate calculation. |
| `sum` | Sums values in each interval. |
| `last` | Takes the last value in each interval. |
**Examples:**
```kusto
// Calculate the average over 5-minute time windows
| align to 5m using avg
// Count the data points in the last hour
| align to 1h using count
// Sum over the full query range
| align using sum
```
### Group [#group]
Use `group by` to combine series by tags.
**Syntax:**
```kusto
| group [by , ] using
```
If you don't specify tags, Axiom aggregates all series into one group.
Available aggregation functions:
| Function | Description |
| -------- | ------------------------------------- |
| `avg` | Averages values in each interval. |
| `sum` | Sums values in each interval. |
| `min` | Takes the minimum value per interval. |
| `max` | Takes the maximum value per interval. |
| `count` | Counts non-null values per interval. |
**Examples:**
```kusto
// Calculate the number of series
| group using count
// Sum all series into a single total
| group using sum
// Group data by the `service` and `namespace` tags using the `sum` aggregation
| group by service, namespace using sum
```
### Bucket [#bucket]
Use `bucket` to aggregate over time and tag dimensions simultaneously.
If you omit `to `, `bucket` aggregates over the full query range and returns a single value per series.
**Syntax:**
```kusto
| bucket [by ] to using
| bucket [by ] using
```
Available functions:
| Function | Description |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `histogram(specs)` | Aggregates non-histogram series into buckets. `specs` is one or more quantile values between 0 and 1, or aggregation functions (`count`, `avg`, `sum`, `min`, `max`). |
| `interpolate_cumulative_histogram(mode, specs)` | Aggregates cumulative-temporality histogram series. `mode` is `rate` or `increase`. `specs` is one or more quantile values or aggregation functions. |
| `interpolate_delta_histogram(specs)` | Aggregates delta-temporality histogram series. `specs` is one or more quantile values or aggregation functions. |
`interpolate_cumulative_histogram` works on histogram metrics using cumulative temporality. `interpolate_delta_histogram` works on histogram metrics using delta temporality.
**Examples:**
```kusto
// Bucket over the `service` and `endpoint` tags using the histogram aggregation
| bucket by service, endpoint to 5m using histogram(max)
// Compute the 50th and 99th percentiles of request duration for histogram data stored using cumulative temporality
| bucket by service to 1m using interpolate_cumulative_histogram(rate, 0.50, 0.99)
// Compute the 50th and 99th percentiles of request duration for histogram data stored using delta temporality
| bucket by service to 1m using interpolate_delta_histogram(0.50, 0.99)
```
### Extend [#extend]
Use `extend` to add new tags to every series in the query result. `extend` supports `${}` interpolation to reference existing tag values or parameters.
`extend` can only be applied after aggregations (after `align`, `group`, `bucket`, and `map`).
**Syntax:**
```kusto
| extend = [, = ...]
```
* The value must be a string, integer, float, or boolean literal.
* The tag name must be net-new: if any input series already has a tag with the same name, the query fails. To overwrite an existing tag, first drop it with `group by`, then re-add it with `extend`.
**Examples:**
```kusto
// Add a constant string tag
| extend environment = "production"
// Reference existing tag values
`my-metrics`:http_latency
| where host is string and port is int
| align to 5m using avg
| extend url = "http://${host}:${port}"
```
## Other operations [#other-operations]
### Compute [#compute]
Combine multiple metrics in one query block.
**Syntax:**
```kusto
(
,
)
| compute using
```
Available operators:
| Operator | Description |
| -------- | ------------------------------------ |
| `+` | Adds subquery results. |
| `-` | Subtracts one subquery from another. |
| `*` | Multiplies subquery results. |
| `/` | Divides one subquery by another. |
| `min` | Minimum across result series. |
| `max` | Maximum across result series. |
| `avg` | Average across result series. |
`compute` uses strict intersection semantics. It only combines series with matching tag sets, and it only emits values for timestamps present on both sides. If you want results for timestamps where only one side has a value, fill missing values using `map` functions.
```kusto
// Use 0 as the default for addition
(
| map fill::const(0),
| map fill::const(0)
)
| compute total using +
// Use 1 as the default for multiplication
(
| map fill::const(1),
| map fill::const(1)
)
| compute product using *
```
**Example:**
```kusto
// Return the average error rate over the past 5 minutes
(
`otel-demo-metrics`:`http.server.request.duration`
| where `http.response.status_code` >= 500
| map rate
| align to 5m using avg
| group using sum,
`otel-demo-metrics`:`http.server.request.duration`
| map rate
| align to 5m using avg
| group using sum
)
| compute error_rate using /
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22\(%20%60otel-demo-metrics%60%3A%60http.server.request.duration%60%20%7C%20where%20%60http.response.status_code%60%20%3E%3D%20500%20%7C%20map%20rate%20%7C%20align%20to%205m%20using%20avg%20%7C%20group%20using%20sum%2C%20%60otel-demo-metrics%60%3A%60http.server.request.duration%60%20%7C%20map%20rate%20%7C%20align%20to%205m%20using%20avg%20%7C%20group%20using%20sum%20\)%20%7C%20compute%20error_rate%20using%20%2F%22%2C%22metricsDataset%22%3A%22otel-demo-metrics%22%7D)
## Language features [#language-features]
### Data types [#data-types]
* Strings: `"string"`
Use double quotes (`"`) to enclose the string. Don't use single quotes (`'`).
* Integers: `42`
* Floats: `3.14`
* Booleans: `true`, `false`
* Regex: `#/.*metrics.*/`
### Identifier naming rules [#identifier-naming-rules]
Identifiers represent fields, metrics, datasets, function names, and other named entities in your query.
Valid identifier names are case-sensitive and follow these rules:
* Start with an ASCII letter.
* Followed by zero or more ASCII letters, digits, or underscores (`_`).
### Quote identifiers [#quote-identifiers]
Quote an identifier in your MPL query if any of the following is true:
* The identifier name doesn't match the rules for [valid identifier names](#identifier-naming-rules).
* The identifier name is identical to one of the reserved keywords of the MPL query language. For example, `by` or `where`.
If any of the above is true, you must quote the identifier by enclosing it in backticks (`` ` ``). For example, `` `my-field` ``.
If none of the above is true, you don't need to quote the identifier in your MPL query. For example, `myfield`. In this case, quoting the identifier name is optional.
### Built-in variables [#built-in-variables]
MPL provides the following built-in variables that can be used in queries:
| Variable | Type | Description |
| ------------- | -------- | ----------------------------------------------------------------------------------------- |
| `$__interval` | duration | Provides a suitable interval size for charting, depending on the time range of the query. |
**Example:**
```kusto
`otel-demo-metrics`:`http.server.request.duration`
| align to $__interval using avg
```
### Time ranges [#time-ranges]
**Syntax:**
```kusto
[..?]
```
Define time ranges with the following:
* **Start time:** Inclusive beginning of the time range.
* **End time:** Optional, exclusive end of the time range. If you don't specify the end time, Axiom uses the current time.
Separate the start and the end times with `..`.
Time can be defined in one of the following ways:
* Relative time. The time unit can be one of the following:
* `ms` for milliseconds (will be rounded to seconds)
* `s` for seconds
* `m` for minutes
* `h` for hours
* `d` for days
* `w` for weeks
* `M` for months
* `y` for years
Examples: `-1h`, `+5m`
* Unix epoch timestamp in seconds. For example: `1723982394`
* An RFC3339 timestamp. For example: `2025-03-01T13:00:00Z`
**Examples:**
```kusto
// One hour ago until the current time
[1h..]
// One hour after a Unix timestamp
[1747077736..+1h]
// One hour before a Unix timestamp
[-1h..1747077736]
// One hour before an RFC3339 date
[-1h..2025-03-01T13:00:00Z]
```
---
# Migrate PromQL queries to Axiom
Source: https://axiom.co/docs/mpl/migrate-metrics
To migrate your PromQL queries to Metrics Processing Language (MPL), choose one of the following options:
* Manually translate your PromQL queries to MPL. This page explains the differences between MPL and PromQL and provides examples of how to migrate your queries.
* Use the [Query metrics skill](/console/intelligence/skills/query-metrics) to translate your PromQL queries to MPL.
## Differences between MPL and PromQL [#differences-between-mpl-and-promql]
While MPL and PromQL are both designed for querying metrics data, they differ in fundamental ways that impact how queries are written and interpreted. This section outlines the key differences to help you adapt PromQL workflows when migrating to Axiom.
### Richer type system [#richer-type-system]
Prometheus treats all label values as strings, which often leads users to rely heavily on regular expressions for filtering. MPL, by contrast, supports a broader and more expressive type system, including native types like numbers, booleans, and timestamps. This means you can write cleaner, type-aware queries and avoid the pitfalls of string-only comparisons.
For OpenTelemetry (OTel) data, this difference means that MPL preserves source types, enabling more accurate and efficient filtering and aggregation.
### Labels versus fields [#labels-versus-fields]
PromQL is optimized for a sparse label space and performs poorly with high-cardinality label sets. This design limits how many dimensions can be encoded as labels.
MPL doesn't impose the same restrictions. It encourages richer, high-cardinality data ingestion, allowing you to store and query more attributes per event without performance degradation.
Additionally, PromQL users often use enrichment queries to simulate dimensional joins and join label values from one series into another. MPL doesn't currently support this pattern because it diverges from OpenTelemetry semantics, which emphasize event-based modeling over metric enrichment.
### Histogram behavior [#histogram-behavior]
Histogram support differs significantly. In PromQL, histogram queries typically yield one time series per bucket or a single quantile series depending on the function.
MPL allows histogram operations that can output multiple series in a single query, providing greater flexibility for analyzing distributions or rendering percentile summaries directly.
### Regular expression syntax [#regular-expression-syntax]
In PromQL, regular expressions are written as string literals using the `=~` operator. This requires escaping special characters using double quotes and backslashes, which can be error-prone.
MPL treats regular expressions as a first-class type. Use the `==` and `!=` operators with a regex literal prefixed by `#/`. There is no `=~` operator in MPL.
```kusto
// PromQL
service=~"checkout|payment"
// MPL equivalent
| where service == #/checkout|payment/
```
Regex is a native type in MPL. Denote regex using `#/` and `/` instead of quotation marks (`'` or `"`).
In regex, escape slash (`/`), but don't escape quotation marks (`'` or `"`).
## Examples [#examples]
### Average over time [#average-over-time]
Calculate the fraction of time JVM CPU utilization stays below 80% over a 7-day window.
```kusto Prometheus example
avg_over_time(
(
max(jvm_cpu_recent_utilization_ratio) < bool 0.8
)[7d:]
)
```
```kusto MPL equivalent
`otel-demo-metrics`:`jvm.cpu.recent_utilization`
| group using max
| map is::lt(0.8)
| align to 7d using avg
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%60otel-demo-metrics%60%3A%60jvm.cpu.recent_utilization%60%20%7C%20group%20using%20max%20%7C%20map%20is%3A%3Alt\(0.8\)%20%7C%20align%20to%207d%20using%20avg%22%2C%22metricsDataset%22%3A%22otel-demo-metrics%22%7D)
### Multiply each value by 100 [#multiply-each-value-by-100]
Convert CPU utilization from a ratio (0–1) to a percentage (0–100).
```kusto Prometheus example
process_cpu_utilization_ratio{} * 100
```
```kusto MPL equivalent
`otel-demo-metrics`:`process.cpu.utilization`
| map * 100
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%60otel-demo-metrics%60%3A%60process.cpu.utilization%60%20%7C%20map%20*%20100%22%2C%22metricsDataset%22%3A%22otel-demo-metrics%22%7D)
### Calculate average error rate [#calculate-average-error-rate]
Calculate the average error rate per 5-minute windows for HTTP server requests.
```kusto Prometheus example
sum(rate(http_server_request_duration_seconds_count{status_code=~"5.."}[5m]))
/
sum(rate(http_server_request_duration_seconds_count{}[5m]))
```
```kusto MPL equivalent
(
`otel-demo-metrics`:`http.server.request.duration`
| where `http.response.status_code` >= 500
| map rate
| align to 5m using avg
| group using sum,
`otel-demo-metrics`:`http.server.request.duration`
| map rate
| align to 5m using avg
| group using sum
)
| compute error_rate using /
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22\(%20%60otel-demo-metrics%60%3A%60http.server.request.duration%60%20%7C%20where%20%60http.response.status_code%60%20%3E%3D%20500%20%7C%20map%20rate%20%7C%20align%20to%205m%20using%20avg%20%7C%20group%20using%20sum%2C%20%60otel-demo-metrics%60%3A%60http.server.request.duration%60%20%7C%20map%20rate%20%7C%20align%20to%205m%20using%20avg%20%7C%20group%20using%20sum%20\)%20%7C%20compute%20error_rate%20using%20%2F%22%2C%22metricsDataset%22%3A%22otel-demo-metrics%22%7D)
### Calculate rate [#calculate-rate]
Calculate the rate of HTTP requests by method, route, and status code.
```kusto Prometheus example
sum by (method, route, status_code) (
rate(
http_server_request_duration_seconds_count{
status_code=~"[123].."
}[5m]
)
)
```
```kusto MPL equivalent
`otel-demo-metrics`:`http.server.request.duration`
| where `http.response.status_code` == #/[123]../
| map rate
| align to 5m using avg
| group by `http.request.method`, `http.route`, `http.response.status_code` using sum
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%60otel-demo-metrics%60%3A%60http.server.request.duration%60%20%7C%20where%20%60http.response.status_code%60%20%3D%3D%20%23%2F%5B123%5D..%2F%20%7C%20map%20rate%20%7C%20align%20to%205m%20using%20avg%20%7C%20group%20by%20%60http.request.method%60%2C%20%60http.route%60%2C%20%60http.response.status_code%60%20using%20sum%22%2C%22metricsDataset%22%3A%22otel-demo-metrics%22%7D)
---
# Sample MPL queries
Source: https://axiom.co/docs/mpl/sample-queries
## Sum rate [#sum-rate]
Calculate the rate of successful HTTP requests by method and route.
```kusto
`otel-demo-metrics`:`http.server.request.duration`
| where `axiom.histogram` == "count"
| where `http.response.status_code` < 400
| align to 5m using prom::rate
| group by `http.request.method`, `http.route` using sum
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%60otel-demo-metrics%60%3A%60http.server.request.duration%60%20%7C%20where%20%60axiom.histogram%60%20%3D%3D%20%5C%22count%5C%22%20%7C%20where%20%60http.response.status_code%60%20%3C%20400%20%7C%20align%20to%205m%20using%20prom%3A%3Arate%20%7C%20group%20by%20%60http.request.method%60%2C%20%60http.route%60%20using%20sum%22%2C%22metricsDataset%22%3A%22otel-demo-metrics%22%7D)
## Histogram [#histogram]
Calculate the 90th and 99th percentile HTTP request durations by service.
```kusto
`otel-demo-metrics`:`http.server.request.duration`
| where `http.response.status_code` < 400
| bucket by `service.name` to 5m using interpolate_delta_histogram(0.90, 0.99)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%60otel-demo-metrics%60%3A%60http.server.request.duration%60%20%7C%20where%20%60http.response.status_code%60%20%3C%20400%20%7C%20bucket%20by%20%60service.name%60%20to%205m%20using%20interpolate_delta_histogram\(0.90%2C%200.99\)%22%2C%22metricsDataset%22%3A%22otel-demo-metrics%22%7D)
### Cumulative histogram [#cumulative-histogram]
`interpolate_cumulative_histogram` works on histogram metrics using cumulative temporality. `interpolate_delta_histogram` works on histogram metrics using delta temporality.
```kusto
`otel-demo-metrics`:`http.server.request.duration`
| where `http.response.status_code` < 400
| bucket by `http.request.method`, `http.route` to 5m using interpolate_cumulative_histogram(rate, 0.90, 0.99)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%60otel-demo-metrics%60%3A%60http.server.request.duration%60%20%7C%20where%20%60http.response.status_code%60%20%3C%20400%20%7C%20bucket%20by%20%60http.request.method%60%2C%20%60http.route%60%20to%205m%20using%20interpolate_cumulative_histogram\(rate%2C%200.90%2C%200.99\)%22%2C%22metricsDataset%22%3A%22otel-demo-metrics%22%7D)
## Compute [#compute]
### Compute error rate [#compute-error-rate]
Calculate the fraction of HTTP requests returning 5xx errors.
```kusto
(
`otel-demo-metrics`:`http.server.request.duration`
| where `http.response.status_code` >= 500
| map rate
| align to 5m using avg
| group using sum,
`otel-demo-metrics`:`http.server.request.duration`
| map rate
| align to 5m using avg
| group using sum
)
| compute error_rate using /
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22\(%20%60otel-demo-metrics%60%3A%60http.server.request.duration%60%20%7C%20where%20%60http.response.status_code%60%20%3E%3D%20500%20%7C%20map%20rate%20%7C%20align%20to%205m%20using%20avg%20%7C%20group%20using%20sum%2C%20%60otel-demo-metrics%60%3A%60http.server.request.duration%60%20%7C%20map%20rate%20%7C%20align%20to%205m%20using%20avg%20%7C%20group%20using%20sum%20\)%20%7C%20compute%20error_rate%20using%20%2F%22%2C%22metricsDataset%22%3A%22otel-demo-metrics%22%7D)
## Service Level Objectives (SLO) [#service-level-objectives-slo]
### SLO compliance over time [#slo-compliance-over-time]
Calculate the fraction of time JVM CPU utilization stays below 80% over a 7-day window.
```kusto
`otel-demo-metrics`:`jvm.cpu.recent_utilization`
| group using max
| map is::lt(0.8)
| align to 7d using avg
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%60otel-demo-metrics%60%3A%60jvm.cpu.recent_utilization%60%20%7C%20group%20using%20max%20%7C%20map%20is%3A%3Alt\(0.8\)%20%7C%20align%20to%207d%20using%20avg%22%2C%22metricsDataset%22%3A%22otel-demo-metrics%22%7D)
### SLO histogram [#slo-histogram]
Calculate the fraction of requests completing within the `400ms` latency SLO target, measured weekly.
```kusto
`otel-demo-metrics`:`http.server.request.duration`
| bucket to 5m using interpolate_cumulative_histogram(rate, 0.99)
| map is::lt(0.4)
| align to 7d using avg
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%60otel-demo-metrics%60%3A%60http.server.request.duration%60%20%7C%20bucket%20to%205m%20using%20interpolate_cumulative_histogram\(rate%2C%200.99\)%20%7C%20map%20is%3A%3Alt\(0.4\)%20%7C%20align%20to%207d%20using%20avg%22%2C%22metricsDataset%22%3A%22otel-demo-metrics%22%7D)
---
# Create annotation
Source: https://axiom.co/docs/restapi/endpoints/createAnnotation
---
# Create dashboard
Source: https://axiom.co/docs/restapi/endpoints/createDashboard
---
# Create dataset
Source: https://axiom.co/docs/restapi/endpoints/createDataset
---
# Create group
Source: https://axiom.co/docs/restapi/endpoints/createGroup
---
# Create map field
Source: https://axiom.co/docs/restapi/endpoints/createMapField
---
# Create monitor
Source: https://axiom.co/docs/restapi/endpoints/createMonitor
---
# Create notifier
Source: https://axiom.co/docs/restapi/endpoints/createNotifier
---
# Create org
Source: https://axiom.co/docs/restapi/endpoints/createOrg
---
# Create role
Source: https://axiom.co/docs/restapi/endpoints/createRole
---
# Create saved query
Source: https://axiom.co/docs/restapi/endpoints/createStarred
---
# Create API token
Source: https://axiom.co/docs/restapi/endpoints/createToken
---
# Create user
Source: https://axiom.co/docs/restapi/endpoints/createUser
---
# Create view
Source: https://axiom.co/docs/restapi/endpoints/createView
---
# Create virtual field
Source: https://axiom.co/docs/restapi/endpoints/createVirtualField
---
# Delete annotation
Source: https://axiom.co/docs/restapi/endpoints/deleteAnnotation
---
# Delete dashboard
Source: https://axiom.co/docs/restapi/endpoints/deleteDashboard
---
# Delete dataset
Source: https://axiom.co/docs/restapi/endpoints/deleteDataset
---
# Delete group
Source: https://axiom.co/docs/restapi/endpoints/deleteGroup
---
# Delete map fields
Source: https://axiom.co/docs/restapi/endpoints/deleteMapField
---
# Delete monitor
Source: https://axiom.co/docs/restapi/endpoints/deleteMonitor
---
# Delete notifier
Source: https://axiom.co/docs/restapi/endpoints/deleteNotifier
---
# Delete role
Source: https://axiom.co/docs/restapi/endpoints/deleteRole
---
# Delete saved query
Source: https://axiom.co/docs/restapi/endpoints/deleteStarred
---
# Delete API token
Source: https://axiom.co/docs/restapi/endpoints/deleteToken
---
# Delete view
Source: https://axiom.co/docs/restapi/endpoints/deleteView
---
# Delete virtual field
Source: https://axiom.co/docs/restapi/endpoints/deleteVirtualField
---
# Retrieve annotation
Source: https://axiom.co/docs/restapi/endpoints/getAnnotation
---
# List all annotations
Source: https://axiom.co/docs/restapi/endpoints/getAnnotations
---
# Retrieve current user
Source: https://axiom.co/docs/restapi/endpoints/getCurrentUser
Using this endpoint, you can retrieve your own user. Authorize the request with a [personal access token (PAT)](/reference/tokens).
---
# Retrieve dashboard
Source: https://axiom.co/docs/restapi/endpoints/getDashboard
If you authenticate with an API token, you can only retrieve a dashboard shared with everyone in your organization or with a group. You can't retrieve private dashboards.
---
# List all dashboards
Source: https://axiom.co/docs/restapi/endpoints/getDashboards
If you authenticate with an API token, this endpoint doesn't return private dashboards. It only returns dashboards shared with everyone in your organization or with a group.
---
# Retrieve dataset
Source: https://axiom.co/docs/restapi/endpoints/getDataset
---
# Get metric tag values for a dataset
Source: https://axiom.co/docs/restapi/endpoints/getDatasetMetricTagValues
---
# Get metric tags for a dataset
Source: https://axiom.co/docs/restapi/endpoints/getDatasetMetricTags
---
# Get metrics for a dataset
Source: https://axiom.co/docs/restapi/endpoints/getDatasetMetrics
---
# Get tag values for a dataset
Source: https://axiom.co/docs/restapi/endpoints/getDatasetTagValues
---
# Get tags for a dataset
Source: https://axiom.co/docs/restapi/endpoints/getDatasetTags
---
# List all datasets
Source: https://axiom.co/docs/restapi/endpoints/getDatasets
---
# Retrieve field in dataset
Source: https://axiom.co/docs/restapi/endpoints/getFieldForDataset
---
# List all fields in dataset
Source: https://axiom.co/docs/restapi/endpoints/getFieldsForDataset
---
# Retrieve group
Source: https://axiom.co/docs/restapi/endpoints/getGroupById
---
# List all map fields
Source: https://axiom.co/docs/restapi/endpoints/getMapFields
---
# Retrieve monitor
Source: https://axiom.co/docs/restapi/endpoints/getMonitor
---
# Retrieve monitor history
Source: https://axiom.co/docs/restapi/endpoints/getMonitorHistory
---
# List all monitors
Source: https://axiom.co/docs/restapi/endpoints/getMonitors
The API endpoint `/v2/monitors` only returns monitors for datasets that your API token has query access to. If the API returns an empty list, ensure your token has query permissions for the datasets used by your monitors.
---
# Retrieve notifier
Source: https://axiom.co/docs/restapi/endpoints/getNotifier
---
# List all notifiers
Source: https://axiom.co/docs/restapi/endpoints/getNotifiers
---
# Retrieve org
Source: https://axiom.co/docs/restapi/endpoints/getOrg
---
# List all orgs
Source: https://axiom.co/docs/restapi/endpoints/getOrgs
---
# Retrieve role
Source: https://axiom.co/docs/restapi/endpoints/getRoleById
---
# Retrieve saved query
Source: https://axiom.co/docs/restapi/endpoints/getStarred
---
# List all saved queries
Source: https://axiom.co/docs/restapi/endpoints/getStarredQueries
---
# Retrieve API token
Source: https://axiom.co/docs/restapi/endpoints/getToken
---
# List all API tokens
Source: https://axiom.co/docs/restapi/endpoints/getTokens
---
# Retrieve user
Source: https://axiom.co/docs/restapi/endpoints/getUser
---
# List all users
Source: https://axiom.co/docs/restapi/endpoints/getUsers
---
# Retrieve view
Source: https://axiom.co/docs/restapi/endpoints/getView
---
# List all views
Source: https://axiom.co/docs/restapi/endpoints/getViews
---
# Retrieve virtual field
Source: https://axiom.co/docs/restapi/endpoints/getVirtualField
---
# List all virtual fields
Source: https://axiom.co/docs/restapi/endpoints/getVirtualFields
---
# Ingest data
Source: https://axiom.co/docs/restapi/endpoints/ingestIntoDataset
Use this endpoint to ingest data into the US East 1 (AWS) edge deployment. For more information, see [Ingest data](/restapi/ingest) and [Edge deployments](/reference/edge-deployments).
The base domain for this endpoint is `https://api.axiom.co`, irrespective of your edge deployment.
To ingest data to a specific edge deployment, use the [Ingest data to edge deployment](/restapi/endpoints/ingestToDataset) endpoint.
---
# Ingest data to edge deployment
Source: https://axiom.co/docs/restapi/endpoints/ingestToDataset
Use this endpoint to ingest data into a specific edge deployment. For more information, see [Ingest data](/restapi/ingest) and [Edge deployments](/reference/edge-deployments).
The base domain for this endpoint is the base domain of the edge deployment where you want to ingest data.
This endpoint only supports API tokens. Personal access tokens (PATs) aren't supported. For more information, see [Tokens](/reference/tokens).
---
# List all groups
Source: https://axiom.co/docs/restapi/endpoints/listGroups
---
# List all roles
Source: https://axiom.co/docs/restapi/endpoints/listRoles
---
# Patch dashboard element
Source: https://axiom.co/docs/restapi/endpoints/patchDashboard
---
# Provision org
Source: https://axiom.co/docs/restapi/endpoints/provisionOrg
For an overview of the full lifecycle, see [Agent-created organizations](/console/intelligence/agent-created-orgs).
---
# Run query
Source: https://axiom.co/docs/restapi/endpoints/queryApl
This endpoint allows you to query data stored in any edge deployment, but query results are routed through the US East 1 (AWS) deployment. This means that if you store data in an edge deployment other than US East 1 (AWS), query results leave the edge deployment where your data is stored. For more information, see [Edge deployments](/reference/edge-deployments).
The base domain for this endpoint is `https://api.axiom.co`, irrespective of your edge deployment.
To query data without results leaving the edge deployment where your data is stored, use the [Run APL query to edge deployment](/restapi/endpoints/queryEdge) or [Run batch query to edge deployment](/restapi/endpoints/queryBatch) endpoints.
---
# Run batch query to edge deployment
Source: https://axiom.co/docs/restapi/endpoints/queryBatch
---
# Run query (legacy)
Source: https://axiom.co/docs/restapi/endpoints/queryDataset
---
# Run APL query to edge deployment
Source: https://axiom.co/docs/restapi/endpoints/queryEdge
This is the edge equivalent of the standard [Run query](/restapi/endpoints/queryApl) endpoint. Send the request to your edge deployment's base domain at the path `/v1/query/_apl`. The standard `/v1/datasets/_apl` path isn't served on edge domains, so requests to it return a `404`.
If you use an Axiom SDK, set the `edge` option on the client and it sends queries to this endpoint automatically. For more information, see [Configure region](/guides/javascript#configure-region).
---
# Run MPL query to edge deployment
Source: https://axiom.co/docs/restapi/endpoints/queryMetrics
Support for MPL queries is currently in public preview. For more information, see [Feature states](/platform-overview/roadmap#feature-states).
---
# Regenerate API token
Source: https://axiom.co/docs/restapi/endpoints/regenerateToken
---
# Delete user from org
Source: https://axiom.co/docs/restapi/endpoints/removeUserFromOrg
---
# Trim dataset
Source: https://axiom.co/docs/restapi/endpoints/trimDataset
When the endpoint returns the status code 200, it means that the deletion request is queued. The deletion itself might take several hours to complete.
---
# Update annotation
Source: https://axiom.co/docs/restapi/endpoints/updateAnnotation
---
# Update current user
Source: https://axiom.co/docs/restapi/endpoints/updateCurrentUser
Using this endpoint, you can update your own user. Authorize the request with a [personal access token (PAT)](/reference/tokens).
You can’t update other users with this endpoint, and you can’t use an API token to authorize the request.
---
# Update dashboard
Source: https://axiom.co/docs/restapi/endpoints/updateDashboard
---
# Update dataset
Source: https://axiom.co/docs/restapi/endpoints/updateDataset
---
# Update field
Source: https://axiom.co/docs/restapi/endpoints/updateFieldForDataset
---
# Update group
Source: https://axiom.co/docs/restapi/endpoints/updateGroup
---
# Update list of map fields
Source: https://axiom.co/docs/restapi/endpoints/updateMapFields
In the body of the API request that you send to this endpoint, specify a list of field names:
* Fields you haven’t previously defined as map fields but include in the list become map fields.
* Fields you have previously defined as map fields and include in the list remain map fields.
* Fields you have previously defined as map fields but exclude from the list are removed.
---
# Update monitor
Source: https://axiom.co/docs/restapi/endpoints/updateMonitor
---
# Update notifier
Source: https://axiom.co/docs/restapi/endpoints/updateNotifier
---
# Update org
Source: https://axiom.co/docs/restapi/endpoints/updateOrg
---
# Update role
Source: https://axiom.co/docs/restapi/endpoints/updateRole
---
# Update saved query
Source: https://axiom.co/docs/restapi/endpoints/updateStarred
---
# Update user role
Source: https://axiom.co/docs/restapi/endpoints/updateUserRole
---
# Update view
Source: https://axiom.co/docs/restapi/endpoints/updateView
---
# Update virtual field
Source: https://axiom.co/docs/restapi/endpoints/updateVirtualField
---
# Vacuum dataset
Source: https://axiom.co/docs/restapi/endpoints/vacuumDataset
When the endpoint returns the status code 200, it means that the vacuuming request is queued. The vacuuming itself might take several hours to complete.
---
# Agent-created organizations
Source: https://axiom.co/docs/console/intelligence/agent-created-orgs
An AI agent can create a real, fully functional Axiom organization with a single unauthenticated HTTP request. The response includes an API token the agent can use immediately to create datasets, ingest and query data, and build dashboards and monitors.
The organization starts out temporary. A human follows the claim URL returned at provisioning time to take ownership and make the organization permanent, keeping everything the agent built. If nobody claims the organization within 24 hours, Axiom permanently deletes it together with all its data.
## Provision an organization [#provision-an-organization]
Send a POST request to `https://api.axiom.co/v2/orgs/provision`. The endpoint is public: no authentication header is required.
The request body is a JSON object. Both fields are optional, so the minimal valid body is `{}`.
| Field | Type | Description |
| :--------------- | :----- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | string | Optional display name for the organization. If omitted, Axiom generates a name like `Agent Org 3f2a91bc`. The owner can rename the organization after claiming it. |
| `edgeDeployment` | string | Optional [edge deployment](/reference/edge-deployments) where the organization stores its data. If omitted, the organization is created in the default edge deployment, `US East 1 (AWS)`. |
For example:
```bash
curl -X 'POST' 'https://api.axiom.co/v2/orgs/provision' \
-H 'Content-Type: application/json' \
-d '{ "name": "canary-experiments" }'
```
A successful request returns `200 OK` with a body like the following:
```json
{
"id": "canary-experiments-x7q2",
"name": "canary-experiments",
"defaultEdgeDeployment": "cloud.us-east-1.aws",
"expiresAt": "2026-07-17T09:14:07Z",
"claimUrl": "https://app.axiom.co/canary-experiments-x7q2/claim?token=a3d2f9c8-4b1e-4f6a-9c3d-8e7b2a1f0d4e",
"token": "xaat-8b4d2f0a-1c9e-4d7b-b3a5-6f2e8c1d9a70"
}
```
| Field | Description |
| :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | The organization ID. Use it in API routes that require an org ID. |
| `name` | The display name of the organization. |
| `defaultEdgeDeployment` | The edge deployment where the organization stores its data. |
| `expiresAt` | The time at which the organization is deleted if it hasn’t been claimed. This is 24 hours after provisioning. |
| `claimUrl` | The URL a human opens to take ownership of the organization. Surface this URL to the user. Treat it as a secret: anyone who opens it can claim the organization. |
| `token` | An API token with full permissions on the organization. Treat it as a secret. Use it as a Bearer token in the `Authorization` header of subsequent API requests. |
The API token and the claim URL are returned exactly once, in this response. They can never be retrieved again, by anyone. Store the token before doing anything else, and treat both values as secrets: the token grants full API access to the organization, and anyone who opens the claim URL can take ownership of it. If the token is lost, the organization and its data are inaccessible over the API until a human claims the organization and creates a new token.
## Work in the organization [#work-in-the-organization]
The organization is not a sandbox: it behaves like any other Axiom organization. Using the returned token, the agent can immediately:
* [Create datasets](/restapi/endpoints/createDataset)
* [Ingest data](/restapi/ingest)
* [Query data](/restapi/query)
* [Create dashboards](/restapi/endpoints/createDashboard)
* [Create monitors](/restapi/endpoints/createMonitor)
The acting identity inside the organization is a synthetic agent user that Axiom creates during provisioning. This user owns the organization until a human claims it.
## Claim the organization [#claim-the-organization]
To keep the organization beyond its 24-hour lifespan, a human must claim it:
1. Open the `claimUrl` from the provisioning response in a browser.
2. Sign in to Axiom. If you don’t have an Axiom account yet, one is created as part of the flow.
3. You become the owner of the organization and the organization becomes permanent.
Claiming keeps everything the agent built: datasets, ingested data, dashboards, monitors, and other configuration all carry over unchanged.
Claiming also has the following effects:
* The temporary markers are removed. The organization no longer expires and behaves like any other Axiom organization on the Personal plan.
* The throttled limits are lifted and the full [Personal plan limits](/reference/limits) apply.
* The synthetic agent user is removed. The API token issued at provisioning continues to work, so the agent keeps its API access. As the new owner, you can revoke it or create additional [API tokens](/reference/tokens) at any time.
Claim links are single-use: after an organization has been claimed, its claim URL is invalid.
## Limits for unclaimed organizations [#limits-for-unclaimed-organizations]
While an organization is unclaimed, reduced limits apply that are sized for its 24-hour lifespan:
* **Ingest and query:** The organization can ingest up to 10 GB of data and use up to 1 GB-hour of query compute during its 24-hour lifespan.
* **Notifiers:** Agents can create and configure monitors and notifiers, but notifiers are disabled: notifications aren’t delivered until the organization is claimed.
* **Provisioning:** An agent can create at most 3 organizations in a day.
Claiming the organization restores the full Personal plan allowances and enables notifiers.
## Expiry [#expiry]
If the organization isn’t claimed within 24 hours (see `expiresAt` in the provisioning response), Axiom deletes it: the organization, all datasets and ingested data, dashboards, monitors, the API token, and the synthetic agent user are permanently removed. Deleted organizations can’t be recovered.
## Error behavior [#error-behavior]
If provisioning returns `429 Too Many Requests`, you have exceeded the provisioning rate limit. Retry later with backoff, and don’t provision more than one organization for the same task.
For the full request and response schema, see the [API reference](/restapi/endpoints/provisionOrg).
---
# AI agents
Source: https://axiom.co/docs/console/intelligence/ai-agents-overview
Axiom provides two complementary approaches for integrating AI agents with your data: Axiom MCP Server and Axiom Skills. Both enable AI agents to query and analyze your Axiom data, but they differ in how they handle credentials, context, and capabilities.
If an agent doesn’t have an Axiom organization to work in yet, it can provision a temporary one itself and hand you a claim link later. For more information, see [Agent-created organizations](/console/intelligence/agent-created-orgs).
## Choose between Axiom MCP Server and Axiom Skills [#choose-between-axiom-mcp-server-and-axiom-skills]
| Aspect | Axiom MCP Server | Axiom Skills |
| ----------------------- | --------------------------------------------- | --------------------------------------------- |
| **Credential handling** | OAuth-based isolation—agents never see tokens | Requires configured API tokens |
| **Context usage** | Tool definitions loaded upfront | On-demand loading, lower context overhead |
| **Capabilities** | Curated, read-only operations | Flexible, extensible beyond curated tools |
| **Methodology** | Query execution only | Includes investigation methodology and memory |
| **Setup complexity** | Browser-based OAuth flow | Manual token configuration |
### When to use Axiom MCP Server [#when-to-use-axiom-mcp-server]
Consider [Axiom MCP Server](/console/intelligence/mcp-server) when:
* You want curated, read-only operations with no risk of destructive actions.
* You prefer OAuth-based credential isolation where agents never see tokens.
* You're comfortable with the context overhead MCP introduces when tool definitions are loaded upfront.
### When to use Axiom Skills [#when-to-use-axiom-skills]
Consider [Axiom Skills](/console/intelligence/skills) when:
* You want lower context usage and on-demand loading of capabilities.
* You need flexibility beyond the curated Axiom MCP Server tool set.
* You want structured investigation methodology alongside data access.
* You're comfortable configuring properly scoped tokens.
### Use both Axiom MCP Server and Axiom Skills [#use-both-axiom-mcp-server-and-axiom-skills]
Axiom Skills can complement Axiom MCP Server by providing investigation methodology, memory systems, and APL guidance without duplicating the query interface. Configure your agent to:
1. Use Axiom MCP Server for executing Axiom queries.
2. Use Axiom Skills for structured investigation methodology and learning from past incidents.
This approach gives you the security benefits of Axiom MCP Server's credential isolation while gaining the systematic debugging capabilities of Axiom Skills.
## Token hygiene for AI agents [#token-hygiene-for-ai-agents]
Proper token scoping is essential for secure AI agent integration.
The guidance below applies when you use Axiom Skills with direct API access or Axiom MCP Server with local setup.
You don't need to follow the guidance below for remote Axiom MCP Server. It uses OAuth for authentication and handles credential isolation automatically. The OAuth process creates appropriately scoped sessions that you can revoke at any time, and agents never see your tokens.
* [Create a new API token](/reference/tokens#api-tokens) specifically for the AI agent. This limits the blast radius if the token is compromised.
* Never use personal access tokens for AI agent use. Personal access tokens have full control over your Axiom account.
* Select only the minimum permissions needed for the agent to perform its tasks:
* Grant query permission on specific datasets the agent needs to access.
* Avoid ingest permissions unless explicitly required. If the agent needs to ingest data, scope the token to the specific datasets.
* Never grant delete, admin, or organization-level permissions.
* Set a short expiry for the token (hours or days rather than months).
* Rotate tokens regularly as part of your security practices.
## Get started [#get-started]
Connect AI agents to Axiom using the Model Context Protocol with OAuth-based credential isolation.
Give AI agents structured investigation capabilities with axiom-sre for hypothesis-driven debugging.
---
# Axiom MCP Server
Source: https://axiom.co/docs/console/intelligence/mcp-server
Axiom MCP Server is a [Model Context Protocol](https://modelcontextprotocol.io/) server implementation that enables AI agents to query your data using Axiom Processing Language (APL).
For guidance on when to use Axiom MCP Server and Axiom Skills, see [AI agents](/console/intelligence/ai-agents-overview).
If your agent doesn’t have an Axiom organization to work in yet, it can provision a temporary one itself and hand you a claim link later. For more information, see [Agent-created organizations](/console/intelligence/agent-created-orgs).
## Current capabilities [#current-capabilities]
### Supported MCP tools [#supported-mcp-tools]
Axiom MCP Server supports the following MCP [tools](https://modelcontextprotocol.io/docs/concepts/tools):
* `checkMonitors`: Check all monitors and their current statuses
* `createDashboard`: Create a dashboard from a JSON document
* `createMonitor`: Create a new monitor
* `createNotifier`: Create a new notifier
* `deleteDashboard`: Delete a dashboard by UID
* `deleteMonitor`: Delete a monitor by ID
* `deleteNotifier`: Delete a notifier by ID
* `exportDashboard`: Export a dashboard as JSON
* `getDashboard`: Get dashboard details
* `getDatasetSchema`: Get dataset schema
* `getMetricTagValues`: Get values for a specific tag in a dataset
* `getMonitorHistory`: Get alert history for a specific monitor
* `getSavedQueries`: Retrieve saved APL queries
* `listDashboards`: List dashboards
* `listDatasets`: List available Axiom datasets
* `listMetricTags`: List tags available for filtering in a dataset
* `listMetrics`: List available metric names in a dataset
* `listNotifiers`: List all configured notifiers
* `queryApl`: Execute APL queries against Axiom datasets
* `queryMetrics`: Execute MPL queries against Axiom datasets
* `searchMetrics`: Search for metrics matching a known tag value
* `updateDashboard`: Update a dashboard by UID
* `updateDashboardChart`: Patch a single chart in an existing dashboard by chart ID
* `updateMonitor`: Update an existing monitor by ID
* `updateNotifier`: Update an existing notifier by ID
### Supported MCP prompts [#supported-mcp-prompts]
Axiom MCP Server supports the following MCP [prompts](https://modelcontextprotocol.io/docs/concepts/prompts):
* `correlate-events-across-datasets`: Find patterns and correlations between events across multiple datasets
* `data-quality-investigation`: Investigate data quality issues including missing data, inconsistencies, and collection problems
* `detect-anomalies-in-events`: Generic anomaly detection using statistical analysis and pattern recognition across any dataset
* `establish-performance-baseline`: Establish performance baselines for a dataset to enable effective monitoring and anomaly detection
* `explore-unknown-dataset`: Exploration of an unknown dataset to understand its structure, content, and potential use cases
* `monitor-health-analysis`: Comprehensive analysis of monitor health, alert patterns, and effectiveness
Axiom plans to support MCP [resources](https://modelcontextprotocol.io/docs/concepts/resources) in the future.
Axiom MCP Server works with all AI agents that support MCP.
### Query results routing [#query-results-routing]
The Axiom-hosted MCP Server is located in the US. When you query your data using the Axiom MCP Server using a remote MCP connection, query results are routed through US infrastructure.
## Setup [#setup]
### Claude [#claude]
If you’re on the Pro, Team, or Enterprise plan:
1. Add Axiom to the list of remote MCP servers:
* If you’re on the Pro plan, follow the [Claude documentation](https://support.anthropic.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp#h_3d1a65aded) to add Axiom to the list of remote MCP servers using the URL `https://mcp.axiom.co/mcp`.
* If you’re on the Team or Enterprise plan, ask your organization admin to follow the [Claude documentation](https://support.anthropic.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp#h_3d1a65aded) to add Axiom to the list of remote MCP servers using the URL `https://mcp.axiom.co/mcp`.
2. In [Claude.ai](https://claude.ai/settings/connectors) or [Claude Desktop](https://claude.ai/download), go to **Settings > Connectors**.
3. Find Axiom in the list.
4. Click **Connect**.
5. Authenticate the request in your browser.
In [Claude Code](https://claude.ai/code), follow the [Claude Code documentation](https://code.claude.com/docs/en/mcp#installing-mcp-servers) to add Axiom to the list of remote MCP servers using the URL `https://mcp.axiom.co/mcp`.
If you’re on the Free plan:
1. Install [Claude Desktop](https://claude.ai/download).
2. In Claude Desktop, go to **Settings > Developers**, and then click **Edit Config**.
3. Add the following to `claude_desktop_config.json`:
```json claude_desktop_config.json
{
"mcpServers": {
"axiom": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://mcp.axiom.co/mcp"
]
}
}
}
```
4. Restart Claude Desktop.
5. Authenticate the request in your browser.
### Cursor [#cursor]
1. Install [Cursor](https://cursor.com/home).
2. [Install Axiom MCP Server](https://cursor.com/en/install-mcp?name=axiom\&config=eyJ1cmwiOiJodHRwczovL21jcC5heGlvbS5jby9tY3AifQ%3D%3D).
3. Authenticate the request in your browser.
### Other clients [#other-clients]
If your agent supports connecting to remote MCP servers directly:
1. In your AI agent, add a remote MCP connection with the following details:
* **Name:** `Axiom`
* **Server URL:** `https://mcp.axiom.co/mcp`
For AI agents that require server-sent events (SSE), use the server URL `https://mcp.axiom.co/sse`.
2. Authenticate the request in your browser. You can later revoke access on the Profile page of the Axiom Console.
If your agent doesn’t support connecting to remote MCP servers directly, use the [mcp-remote library](https://www.npmjs.com/package/mcp-remote):
1. Add the `mcp-remote` library to the configuration file of your AI agent. For example:
```json
{
"mcpServers": {
"axiom": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://mcp.axiom.co/mcp"
]
}
}
}
```
2. Restart your AI agent.
3. Authenticate the request in your browser. You can later revoke access on the Profile page of the Axiom Console.
### Header-based authentication [#header-based-authentication]
If your AI agent doesn’t support browser-based OAuth authentication (for example, Agent Builder from OpenAI), authenticate by passing headers directly:
1. [Create a personal access token](/reference/tokens#personal-access-tokens-pat).
2. [Determine organization ID](/reference/tokens#determine-organization-id).
3. Configure your AI agent to use the server URL `https://mcp.axiom.co/mcp`.
4. Configure your AI agent to send the following headers with each request to the server URL:
* **Authorization:** `Bearer PERSONAL_ACCESS_TOKEN`
* **x-axiom-org-id:** `ORGANIZATION_ID`
Replace `PERSONAL_ACCESS_TOKEN` with the personal access token you have generated.
If your AI agent only supports setting the Authorization header, pass the organization ID as a URL parameter: `https://mcp.axiom.co/mcp?org-id=ORGANIZATION_ID`
The setup explained above uses a remote MCP connection and is the recommended approach for most use cases. Alternatively, deploy Axiom MCP Server locally on your machine for more control.
## Local setup [#local-setup]
With the local setup:
* You run Axiom MCP Server yourself.
* You authenticate using an API token instead of OAuth 2.0.
* You can’t use the MCP prompts mentioned in [Current capabilities](#current-capabilities).
### Install [#install]
Run the following to install the latest built binary from [GitHub](https://github.com/axiomhq/axiom-mcp/releases):
```bash
go install github.com/axiomhq/axiom-mcp@latest
```
Axiom MCP Server is an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/mcp-server-axiom).
### Create API token [#create-api-token]
### Configure [#configure]
Configure your AI agent to use the MCP Server in one of the following ways:
* Use a [config file](#config-file)
* Use [environment variables](#environment-variables)
#### Config file [#config-file]
1. Create a config file where you specify the authentication and configuration details. For example:
```bash config.txt
token API_TOKEN
url https://api.axiom.co
query-rate 1
query-burst 1
datasets-rate 1
datasets-burst 1
monitors-rate 1
monitors-burst 1
```
Optionally, configure the rate and the burst limits at the query, dataset, and monitor levels. The rate limit is the maximum number of requests that your AI agent can make per second. The burst limit is the maximum number of requests that your AI agent can make in a short period (burst) before the rate limit applies to further requests.
2. In the settings of your AI agent, reference the binary file of Axiom MCP Server and the config file you have previously created.
For example, if you use the Claude desktop app, add `axiom` to the `mcpServers` section of the Claude configuration file. The default path is `~/Library/Application Support/Claude/claude_desktop_config.json` on MacOS and `%APPDATA%\Claude\claude_desktop_config.json` on Windows.
```json claude_desktop_config.json
{
"mcpServers": {
"axiom": {
"command": "PATH_AXIOM_MCP_BINARY",
"args": [
"--config",
"PATH_AXIOM_MCP_CONFIG"
]
}
}
}
```
Replace `PATH_AXIOM_MCP_BINARY` with the path the binary file of Axiom MCP Server.
Replace `PATH_AXIOM_MCP_CONFIG` with the path the config file you have previously created.
#### Environment variables [#environment-variables]
In the settings of your AI agent, use environment variables to specify the authentication details and the binary file of Axiom MCP Server.
For example, if you use the Claude desktop app, add `axiom` to the `mcpServers` section of the Claude configuration file. The default path is `~/Library/Application Support/Claude/claude_desktop_config.json` on MacOS and `%APPDATA%\Claude\claude_desktop_config.json` on Windows.
```json claude_desktop_config.json
{
"mcpServers": {
"axiom": {
"command": "PATH_AXIOM_MCP_BINARY",
"env": {
"AXIOM_TOKEN": "API_TOKEN",
"AXIOM_URL": "https://api.axiom.co",
"AXIOM_QUERY_RATE": "1",
"AXIOM_QUERY_BURST": "1",
"AXIOM_DATASETS_RATE": "1",
"AXIOM_DATASETS_BURST": "1",
"AXIOM_MONITORS_RATE": "1",
"AXIOM_MONITORS_BURST": "1"
}
}
}
}
```
Replace `PATH_AXIOM_MCP_BINARY` with the path the binary file of Axiom MCP Server.
Optionally, configure the rate and the burst limits at the query, dataset, and monitor levels. The rate limit is the maximum number of requests that your AI agent can make per second. The burst limit is the maximum number of requests that your AI agent can make in a short period (burst) before the rate limit applies to further requests.
## Token hygiene [#token-hygiene]
The remote MCP Server uses OAuth for authentication which handles credential isolation automatically. Agents never see your tokens. You can [revoke access](#revoke-access) at any time.
For the local setup or when using [Axiom Skills](/console/intelligence/skills), you configure API tokens directly. For detailed guidance on secure token configuration, see [Token hygiene for AI agents](/console/intelligence/ai-agents-overview#token-hygiene-for-ai-agents).
## Use MCP Server [#use-mcp-server]
After setting up the Axiom MCP Server, you can:
* Ask your own questions about your event data. For example:
* “List datasets.”
* “Get the data schema for the dataset `logs`.”
* “Get the most common status codes in the last 30 minutes in the dataset `logs`.”
* Use the [prebuilt prompts](#supported-mcp-prompts). For example, in Claude, click **+ > Add from Axiom** to access them.
To answer your questions, your AI client can use the [supported MCP tools](#supported-mcp-tools).
## Revoke access [#revoke-access]
Using the procedure above, you authorized your AI client to access your event data in Axiom.
To revoke access:
1. In Axiom, go to **Settings > Profile**.
2. In the Sessions section, find the session where you authorized your AI client, and then click **Delete** on the right.
If you connected to Axiom MCP Server using the `mcp-remote` library, clear the `.mcp-auth` folder. This is where Axiom MCP Server stores credential information. On a Mac, the default path is `~/.mcp-auth`.
---
# Skills for AI agents
Source: https://axiom.co/docs/console/intelligence/skills
Axiom Skills are instruction files that give AI coding agents specialized capabilities. Unlike Axiom MCP Server, which provides a standardized protocol for tool discovery and execution, Axiom Skills work by embedding methodology and context directly into the agent's prompt.
For guidance on when to use Axiom Skills and Axiom MCP Server, see [AI agents](/console/intelligence/ai-agents-overview).
Axiom Skills are open-source and welcome your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/skills).
## Available skills [#available-skills]
Axiom provides the following skills:
Create and manage monitors and notifiers using the Axiom API
Design and build Axiom dashboards via API with chart types, APL patterns, and layouts
Analyze query patterns, find unused data, and build cost optimization dashboards
Query and explore OpenTelemetry metrics in Axiom's MetricsDB using MPL
Expert SRE investigator for incidents and debugging with hypothesis-driven methodology
Translate Splunk SPL queries to Axiom APL for migrations
---
# Spotlight
Source: https://axiom.co/docs/console/intelligence/spotlight
Spotlight allows you to highlight a region of event data and automatically identify how it deviates from baseline across different fields. Instead of manually crafting queries to investigate anomalies, Spotlight analyzes every field in your data and presents the most significant differences through intelligent visualizations.
This page explains how Spotlight works in the Axiom Console. For more information about the `spotlight` APL function, see [spotlight](/apl/aggregation-function/spotlight).
Spotlight is particularly useful for:
* **Root cause analysis**: Quickly identify why certain traces are slower, errors are occurring, or performance is degraded.
* **Anomaly investigation**: Understand what makes problematic events different from normal baseline behavior.
* **Pattern discovery**: Spot trends and correlations in your data that might not be immediately obvious.
## How Spotlight works [#how-spotlight-works]
Spotlight compares two sets of events:
* **Comparison set**: The events you select by highlighting a region on a chart.
* **Baseline set**: All other events that contributed to the chart.
For each field present in your data, Spotlight does the following:
* Calculates the differences between the two sets and ranks them by significance.
* Displays the most interesting differences using visualizations that adapt to your data types.
* Gives an AI-generated summary that helps you interpret the visualizations.
## Use Spotlight [#use-spotlight]
### Start Spotlight analysis [#start-spotlight-analysis]
1. In the **Query** tab, create a query that produces a heatmap or time series chart.
2. On the chart, click and drag to select the region you want to investigate.
3. In the selection tooltip, click **Run Spotlight**.
Spotlight analyzes the selected events and displays the results in a new panel showing the most significant differences across all fields.
Alternatively, start Spotlight from a table view by right-clicking on the value you want to select and choosing **Run spotlight**.
### Interpret results [#interpret-results]
Spotlight displays results using two types of visualization, depending on your data:
* **Bar charts** for categorical fields (strings, booleans)
* Compares the proportion of events that have a given value for selected and baseline events.
* Useful for understanding differences in status codes, service names, or boolean flags.
* **Boxplots** for numeric fields (integers, floats, timespans) with many distinct values
* Shows the range of values in both comparison and baseline sets.
* Identifies the minimum, P25, P75, and maximum values.
* Useful for understanding differences in response times or other numeric quantities.
For each visualization, Axiom displays the proportion of selected and baseline events (where the field is present).
### Dig deeper [#dig-deeper]
To dig deeper, iteratively refine your Spotlight analysis or jump to a view of matching events.
1. **Filter and re-run**: Right-click specific values in the results and select **Re-run spotlight** to filter your data and run Spotlight again with a more focused scope.
2. **Show events**: Rick-click specific values in the results and select **Show events** to filter your data and see matching events.
## Spotlight limitations [#spotlight-limitations]
* **Custom attributes**: Currently, custom attributes in OTel spans aren’t included in the Spotlight results. Axiom will soon support custom attributes in Spotlight.
* **Complex queries**: Spotlight works well for queries with maximum one aggregation step. Complex queries with multiple aggregations aren’t supported.
## Example workflows [#example-workflows]
### Investigate slow traces [#investigate-slow-traces]
1. Create a heatmap of trace durations. For example, run the following query:
```kusto
['otel-demo-traces']
| summarize histogram(duration, 20) by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20histogram\(duration%2C%2020\)%20by%20bin_auto\(_time\)%22%7D)
2. Select the region showing the slowest traces.
3. Run Spotlight to see if slow traces are associated with specific endpoints, regions, or user segments.
### Understand error spikes [#understand-error-spikes]
1. Build a time series of error-level logs. For example, run the following query:
```kusto
['sample-http-logs']
| where status startswith "5"
| summarize count() by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20startswith%20'5'%20%7C%20summarize%20count\(\)%20by%20bin_auto\(_time\)%22%7D)
2. Select the time period where errors spiked.
3. Run Spotlight to identify if there’s anything different about the selected errors.
---
# Instrumentation with Axiom AI SDK
Source: https://axiom.co/docs/use-cases/llm-observability/axiom-ai-sdk-instrumentation
This page explains how to set up instrumentation in your TypeScript generative AI app using Axiom AI SDK.
Axiom AI SDK is an open-source project and welcomes your contributions. For more information, see the [GitHub repository](https://github.com/axiomhq/ai).
Alternatively, [instrument your app manually](/use-cases/llm-observability/manual-instrumentation). For more information on instrumentation approaches, see [LLM observability](/use-cases/llm-observability).
## Prerequisite [#prerequisite]
Install the Axiom AI SDK by running the following:
```sh
npm i @axiomhq/ai
```
Set the `AXIOM_TOKEN` and `AXIOM_DATASET` environment variables using an [API token](/reference/tokens) and the name of a dataset you want to send telemetry to.
## Instrument AI SDK calls [#instrument-ai-sdk-calls]
Axiom AI SDK provides helper functions for [Vercel AI SDK](https://ai-sdk.dev/docs) to wrap your existing AI model client. The `wrapAISDKModel` function takes an existing AI model object and returns an instrumented version that automatically generates trace data for every call.
Choose one of the following common Vercel AI SDK providers. For the full list of providers, see the [Vercel documentation](https://ai-sdk.dev/providers/ai-sdk-providers).
1. Run the following in your terminal to install the Vercel AI SDK and the OpenAI provider.
```sh
npm i ai @ai-sdk/openai
```
2. Create the file `src/shared/openai.ts` with the following content:
```ts /src/shared/openai.ts
import { createOpenAI } from '@ai-sdk/openai';
import { wrapAISDKModel } from 'axiom/ai';
const openaiProvider = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
// Wrap the model to enable automatic tracing
export const gpt4o = wrapAISDKModel(openaiProvider('gpt-4o'));
export const gpt4oMini = wrapAISDKModel(openaiProvider('gpt-4o-mini'));
```
1. Run the following in your terminal to install the Vercel AI SDK and the Anthropic provider.
```sh
npm i ai @ai-sdk/anthropic
```
2. Create the file `src/shared/anthropic.ts` with the following content:
```ts /src/shared/anthropic.ts
import { createAnthropic } from '@ai-sdk/anthropic';
import { wrapAISDKModel } from 'axiom/ai';
const anthropicProvider = createAnthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
// Wrap the model to enable automatic tracing
export const claude35Sonnet = wrapAISDKModel(anthropicProvider('claude-3-5-sonnet-20241022'));
export const claude35Haiku = wrapAISDKModel(anthropicProvider('claude-3-5-haiku-20241022'));
```
1. Run the following in your terminal to install the Vercel AI SDK and the Gemini provider.
```sh
npm i ai @ai-sdk/google
```
2. Create the file `src/shared/gemini.ts` with the following content:
```ts /src/shared/gemini.ts
import { createGoogleGenerativeAI } from '@ai-sdk/google';
import { wrapAISDKModel } from 'axiom/ai';
const geminiProvider = createGoogleGenerativeAI({
apiKey: process.env.GEMINI_API_KEY,
});
// Wrap the model to enable automatic tracing
export const gemini20Flash = wrapAISDKModel(geminiProvider('gemini-2.0-flash-exp'));
export const gemini15Pro = wrapAISDKModel(geminiProvider('gemini-1.5-pro'));
```
1. Run the following in your terminal to install the Vercel AI SDK and the Grok provider.
```sh
npm i ai @ai-sdk/xai
```
2. Create the file `src/shared/grok.ts` with the following content:
```ts /src/shared/grok.ts
import { createXai } from '@ai-sdk/xai';
import { wrapAISDKModel } from 'axiom/ai';
const grokProvider = createXai({
apiKey: process.env.XAI_API_KEY,
});
// Wrap the model to enable automatic tracing
export const grokBeta = wrapAISDKModel(grokProvider('grok-beta'));
export const grok2Mini = wrapAISDKModel(grokProvider('grok-2-mini'));
```
To instrument calls without a Vercel AI SDK provider, use the generic Vercel AI Gateway provider.
To instrument calls without a Vercel AI SDK provider, use the generic Vercel AI Gateway provider. For more information, see the [Vercel documentation](https://ai-sdk.dev/providers/ai-sdk-providers/ai-gateway).
1. Run the following in your terminal to install the Vercel AI SDK:
```sh
npm i ai
```
2. Create the file `src/shared/openai.ts` with the following content:
```ts /src/shared/openai.ts
import { createGateway } from 'ai';
import { wrapAISDKModel } from 'axiom';
const gateway = createGateway({
apiKey: process.env.OPENAI_API_KEY,
});
// Wrap the model to enable automatic tracing
export const gpt4o = wrapAISDKModel(gateway('openai/gpt-4o'));
```
The rest of the page explains how to work with OpenAI. The process is similar for other LLMs.
## Add context [#add-context]
The `withSpan` function allows you to add crucial business context to your traces. It creates a parent span around your LLM call and attaches metadata about the `capability` and `step` that you execute.
```ts /src/app/page.tsx
import { withSpan } from 'axiom/ai';
import { generateText } from 'ai';
import { gpt4o } from '@/shared/openai';
export default async function Page() {
const userId = 123;
// Use withSpan to define the capability and step
const res = await withSpan({ capability: 'get_capital', step: 'generate_answer' }, (span) => {
// You have access to the OTel span to add custom attributes
span.setAttribute('user_id', userId);
return generateText({
model: gpt4o, // Use the wrapped model
messages: [
{
role: 'user',
content: 'What is the capital of Spain?',
},
],
});
});
return {res.text}
;
}
```
## Instrument tool calls [#instrument-tool-calls]
For many AI capabilities, the LLM call is only part of the story. If your capability uses tools to interact with external data or services, observing the performance and outcome of those tools is critical. Axiom AI SDK provides the `wrapTool` and `wrapTools` functions to automatically instrument your Vercel AI SDK tool definitions.
The `wrapTool` helper takes your tool’s name and its definition and returns an instrumented version. This wrapper creates a dedicated child span for every tool execution, capturing its arguments, output, and any errors.
```ts /src/app/generate-text/page.tsx
import { tool } from 'ai';
import { z } from 'zod';
import { wrapTool } from 'axiom/ai';
import { generateText } from 'ai';
import { gpt4o } from '@/shared/openai';
// In your generateText call, provide wrapped tools
const { text, toolResults } = await generateText({
model: gpt4o,
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'How do I get from Paris to Berlin?' },
],
tools: {
// Wrap each tool with its name
findDirections: wrapTool(
'findDirections', // The name of the tool
tool({
description: 'Find directions to a location',
inputSchema: z.object({
from: z.string(),
to: z.string(),
}),
execute: async (params) => {
// Your tool logic here...
return { directions: `To get from ${params.from} to ${params.to}, use a teleporter.` };
},
})
)
}
});
```
## Complete example [#complete-example]
Example of how all three instrumentation functions work together in a single, real-world example:
```ts /src/app/page.tsx expandable
import { withSpan, wrapAISDKModel, wrapTool } from 'axiom/ai';
import { generateText, tool } from 'ai';
import { createOpenAI } from '@ai-sdk/openai';
import { z } from 'zod';
// 1. Create and wrap the AI model client
const openaiProvider = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
const gpt4o = wrapAISDKModel(openaiProvider('gpt-4o'));
// 2. Define and wrap your tool(s)
const findDirectionsTool = wrapTool(
'findDirections', // The tool name must be passed to the wrapper
tool({
description: 'Find directions to a location',
inputSchema: z.object({ from: z.string(), to: z.string() }),
execute: async ({ from, to }) => ({
directions: `To get from ${from} to ${to}, use a teleporter.`,
}),
})
);
// 3. In your application logic, use `withSpan` to add context
// and call the AI model with your wrapped tools.
export default async function Page() {
const userId = 123;
const { text } = await withSpan({ capability: 'get_directions', step: 'generate_ai_response' }, async (span) => {
// You have access to the OTel span to add custom attributes
span.setAttribute('user_id', userId);
return generateText({
model: gpt4o, // Use the wrapped model
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'How do I get from Paris to Berlin?' },
],
tools: {
findDirections: findDirectionsTool, // Use the wrapped tool
},
});
});
return {text}
;
}
```
This demonstrates the three key steps to rich observability:
1. **`wrapAISDKModel`**: Automatically captures telemetry for the LLM provider call
2. **`wrapTool`**: Instruments the tool execution with detailed spans
3. **`withSpan`**: Creates a parent span that ties everything together under a business capability
## What’s next? [#whats-next]
After sending traces to Axiom:
* View your [traces](/query-data/traces) in Console
* Set up [monitors and alerts](/monitor-data/monitors) based on your AI telemetry data
* Learn about the [GenAI APL functions](/apl/scalar-functions/genai-functions) for querying AI telemetry
---
# Generative AI attributes
Source: https://axiom.co/docs/use-cases/llm-observability/gen-ai-attributes
After you instrument your app, every LLM call sends a detailed span to your Axiom dataset. The spans are enriched with standardized `gen_ai.*` attributes that make your AI interactions easy to query and analyze.
Key attributes include the following:
## Span identification [#span-identification]
* `gen_ai.capability.name`: The high-level capability name you defined in `withSpan`.
* `gen_ai.step.name`: The specific step within the capability.
* `gen_ai.operation.name`: The operation type. For example: `chat`, `execute_tool`.
## Model information [#model-information]
* `gen_ai.provider.name`: The model provider. For example: `openai`, `anthropic`.
* `gen_ai.request.model`: The model requested for the completion.
* `gen_ai.response.model`: The model that actually fulfilled the request.
* `gen_ai.output.type`: The output type. For example: `text`, `json`.
## Token usage [#token-usage]
* `gen_ai.usage.input_tokens`: The number of tokens in the prompt.
* `gen_ai.usage.output_tokens`: The number of tokens in the generated response.
## Messages [#messages]
* `gen_ai.input.messages`: The full, rendered prompt or message history sent to the model (as a JSON string).
* `gen_ai.output.messages`: The full response from the model (as a JSON string).
* `gen_ai.response.finish_reasons`: The reason the model stopped generating tokens. For example: `stop`, `tool-calls`.
* `gen_ai.response.id`: The unique identifier for the model response.
## Tool attributes [#tool-attributes]
* `gen_ai.tool.name`: The name of the executed tool.
* `gen_ai.tool.call.arguments`: The arguments passed to the tool (as a JSON string).
* `gen_ai.tool.call.result`: The result returned by the tool (as a JSON string).
## Additional attributes [#additional-attributes]
For a more thorough list of attributes, see the [OpenTelemetry Semantic Conventions for Generative AI](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/).
## What’s next? [#whats-next]
After capturing and analyzing production telemetry:
* [Visualize traces](/query-data/traces) in Console.
* Use [GenAI APL functions](/apl/scalar-functions/genai-functions) to query and analyze your AI telemetry.
---
# Manual instrumentation
Source: https://axiom.co/docs/use-cases/llm-observability/manual-instrumentation
Manually instrumenting your generative AI apps using language-agnostic OpenTelemetry tooling gives you full control over your instrumentation while ensuring compatibility with Axiom’s LLM observability features.
Alternatively, instrument your app with Axiom AI SDK. For more information on instrumentation approaches, see [LLM observability](/use-cases/llm-observability).
For more information on sending OpenTelemetry data to Axiom, see [Send OpenTelemetry data to Axiom](/send-data/opentelemetry#send-opentelemetry-data-to-axiom) for examples in multiple languages.
## Required attributes [#required-attributes]
Axiom’s conventions for AI spans are based on version 1.37 of the [OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/) for generative client AI spans.
Axiom requires the following attributes in your data to properly recognize your spans:
* `gen_ai.operation.name` identifies AI spans. It’s also a required attribute in the OpenTelemetry specification.
* `gen_ai.capability.name` provides context about the specific capability being used within the AI operation.
* `gen_ai.step.name` allows you to break down the AI operation into individual steps for more granular tracking.
### `gen_ai.operation.name` [#gen_aioperationname]
Axiom currently provides custom UI for the following operations related to the `gen_ai.operation.name` attribute:
* `chat`: Chat completion
* `execute_tool`: Tool execution
Other possible values include:
* `generate_content`: Multimodal content generation
* `embeddings`: Vector embeddings
* `create_agent`: Create AI agents
* `invoke_agent`: Invoke existing agents
* `text_completion`: Text completion. This is a legacy value and has been deprecated by OpenAI and many other providers.
For more information, see the OpenTelemetry documentation on [GenAI Attributes](https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/#gen-ai-operation-name).
## Recommended attributes [#recommended-attributes]
Axiom recommends the following attributes to get the most out of Axiom’s AI telemetry features:
### `axiom.gen_ai` attributes [#axiomgen_ai-attributes]
* `axiom.gen_ai.schema_url`: Schema URL for the Axiom AI conventions. For example: `https://axiom.co/ai/schemas/0.0.2`
* `axiom.gen_ai.sdk.name`: Name of the SDK. For example: `my-ai-instrumentation-sdk`
* `axiom.gen_ai.sdk.version`: Version of the SDK. For example: `1.2.3`
### Chat spans [#chat-spans]
| Attribute | Type | Required | Description |
| :------------------------------- | :------------------------ | :------------- | :---------------------------------------------- |
| `gen_ai.provider.name` | string | Required | Provider (openai, anthropic, aws.bedrock, etc.) |
| `gen_ai.request.model` | string | When available | Model requested (gpt-4, claude-3, etc.) |
| `gen_ai.response.model` | string | When available | Model that fulfilled the request |
| `gen_ai.input.messages` | Messages\[] (stringified) | Recommended | Input conversation history |
| `gen_ai.output.messages` | Messages\[] (stringified) | Recommended | Model response messages |
| `gen_ai.usage.input_tokens` | integer | Recommended | Input token count |
| `gen_ai.usage.output_tokens` | integer | Recommended | Output token count |
| `gen_ai.request.choice_count` | integer | When >1 | Number of completion choices requested |
| `gen_ai.response.id` | string | Recommended | Unique response identifier |
| `gen_ai.response.finish_reasons` | string\[] | Recommended | Why generation stopped |
| `gen_ai.conversation.id` | string | When available | Conversation/session identifier |
### Tool spans [#tool-spans]
For tool operations (`execute_tool`), include these additional attributes:
| Attribute | Type | Required | Description |
| :------------------------ | :----- | :------------- | :----------------------------------------- |
| `gen_ai.tool.name` | string | Required | Name of the executed tool |
| `gen_ai.tool.call.id` | string | When available | Tool call identifier |
| `gen_ai.tool.type` | string | When available | Tool type (function, extension, datastore) |
| `gen_ai.tool.description` | string | When available | Tool description |
| `gen_ai.tool.arguments` | string | When available | Tool arguments |
| `gen_ai.tool.message` | string | When available | Tool message |
For more information, see [GenAI Attributes](https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/#genai-attributes).
### Agent spans [#agent-spans]
For agent operations (`create_agent`, `invoke_agent`), include these additional attributes:
| Attribute | Type | Required | Description |
| :------------------------- | :----- | :------------- | :------------------------------ |
| `gen_ai.agent.id` | string | When available | Unique agent identifier |
| `gen_ai.agent.name` | string | When available | Human-readable agent name |
| `gen_ai.agent.description` | string | When available | Agent description/purpose |
| `gen_ai.conversation.id` | string | When available | Conversation/session identifier |
## Span naming [#span-naming]
Ensure span names follow the OpenTelemetry conventions for generative AI spans. For example, the suggested span names for common values of `gen_ai.operation.name` are the following:
* `chat {gen_ai.request.model}`
* `execute_tool {gen_ai.tool.name}`
* `embeddings {gen_ai.request.model}`
* `generate_content {gen_ai.request.model}`
* `text_completion {gen_ai.request.model}`
* `create_agent {gen_ai.agent.name}`
* `invoke_agent {gen_ai.agent.name}`
For more information, see the OpenTelemetry documentation on span naming:
* [AI spans](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/#spans)
* [Agent spans](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/)
* [Tool span](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/#execute-tool-span)
## Messages [#messages]
Messages support four different roles, each with specific content formats. They follow OpenTelemetry’s structured format:
### System messages [#system-messages]
System messages are messages that the system adds to set the behavior of the assistant. They typically contain instructions or context for the AI model.
```json
{
"role": "system",
"parts": [
{"type": "text", "content": "You are a helpful assistant"}
]
}
```
### User messages [#user-messages]
User messages are messages that users send to the AI model. They typically contain questions, commands, or other input from the user.
```json
{
"role": "user",
"parts": [
{"type": "text", "content": "Weather in Paris?"}
]
}
```
### Assistant messages [#assistant-messages]
Assistant messages are messages that the AI model sends back to the user. They typically contain responses, answers, or other output from the model.
```json
{
"role": "assistant",
"parts": [
{"type": "text", "content": "Hi there!"},
{"type": "tool_call", "id": "call_123", "name": "get_weather", "arguments": {"location": "Paris"}}
],
"finish_reason": "stop"
}
```
### Tool messages [#tool-messages]
Tool messages are messages that contain the results of tool calls made by the AI model. They typically contain the output or response from the tool.
```json
{
"role": "tool",
"parts": [
{"type": "tool_call_response", "id": "call_123", "response": "rainy, 57°F"}
]
}
```
### Content part types [#content-part-types]
* `text`: Text content with `content` field
* `tool_call`: Tool invocation with `id`, `name`, `arguments`
* `tool_call_response`: Tool result with `id`, `response`
For more information, see the OpenTelemetry documentation:
* [Recording content on attributes](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/#recording-content-on-attributes)
* JSON schema for [inputs](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-input-messages.json) and [outputs](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-output-messages.json)
## Example trace structure [#example-trace-structure]
### Chat completion [#chat-completion]
Example of a properly structured chat completion trace:
```typescript TypeScript expandable
import { trace, SpanKind, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('my-app');
// Create a span for the AI operation
return tracer.startActiveSpan('chat gpt-4', {
kind: SpanKind.CLIENT
}, (span) => {
try {
// (Your AI operation logic here...)
span.setAttributes({
// Set operation name
'gen_ai.operation.name': 'chat',
// Set capability and step
'gen_ai.capability.name': 'customer_support',
'gen_ai.step.name': 'respond_to_greeting',
// Set other attributes
'gen_ai.provider.name': 'openai',
'gen_ai.request.model': 'gpt-4',
'gen_ai.response.model': 'gpt-4',
'gen_ai.usage.input_tokens': 150,
'gen_ai.usage.output_tokens': 75,
'gen_ai.input.messages': JSON.stringify([
{ role: 'user', parts: [{ type: 'text', content: 'Hello, how are you?' }] }
]),
'gen_ai.output.messages': JSON.stringify([
{ role: 'assistant', parts: [{ type: 'text', content: 'I\'m doing well, thank you!' }], finish_reason: 'stop' }
])
});
return /* your result */;
} catch (error) {
span.recordException(error);
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
throw error; // rethrow if you want upstream to see it
} finally {
span.end();
}
});
```
```python Python expandable
from opentelemetry import trace
from opentelemetry.trace import SpanKind
import json
tracer = trace.get_tracer("my-app")
# Create a span for the AI operation
with tracer.start_as_current_span("chat gpt-4", kind=SpanKind.CLIENT) as span:
# (Your AI operation logic here...)
span.set_attributes({
# Set operation name
"gen_ai.operation.name": "chat",
# Set capability and step
"gen_ai.capability.name": "customer_support",
"gen_ai.step.name": "respond_to_greeting",
# Set other attributes
"gen_ai.provider.name": "openai",
"gen_ai.request.model": "gpt-4",
"gen_ai.response.model": "gpt-4",
"gen_ai.usage.input_tokens": 150,
"gen_ai.usage.output_tokens": 75,
"gen_ai.input.messages": json.dumps([
{"role": "user", "parts": [{"type": "text", "content": "Hello, how are you?"}]}
]),
"gen_ai.output.messages": json.dumps([
{"role": "assistant", "parts": [{"type": "text", "content": "I'm doing well, thank you!"}], "finish_reason": "stop"}
])
})
```
### Tool execution [#tool-execution]
Example of a tool execution within an agent:
```typescript TypeScript expandable
import { trace, SpanKind, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('my-agent-app');
// Create a span for tool execution
return tracer.startActiveSpan('execute_tool get_weather', {
kind: SpanKind.CLIENT
}, (span) => {
try {
// (Your tool call logic here...)
span.setAttributes({
// Set operation name
'gen_ai.operation.name': 'execute_tool',
// Set capability and step
'gen_ai.capability.name': 'weather_assistance',
'gen_ai.step.name': 'fetch_current_weather',
// Set other attributes
'gen_ai.tool.name': 'get_weather',
'gen_ai.tool.type': 'function',
'gen_ai.tool.call.id': 'call_abc123',
'gen_ai.tool.arguments': JSON.stringify({ location: 'New York', units: 'celsius' }),
'gen_ai.tool.message': JSON.stringify({ temperature: 22, condition: 'sunny', humidity: 65 }),
});
return /* your result */;
} catch (error) {
span.recordException(error);
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
throw error; // rethrow if you want upstream to see it
} finally {
span.end();
}
});
```
```python Python expandable
from opentelemetry import trace
from opentelemetry.trace import SpanKind
import json
tracer = trace.get_tracer("my-agent-app")
# Create a span for tool execution
with tracer.start_as_current_span("execute_tool get_weather", kind=SpanKind.CLIENT) as span:
span.set_attributes({
# Set operation name
"gen_ai.operation.name": "execute_tool",
# Set capability and step
"gen_ai.step.name": "fetch_current_weather",
"gen_ai.capability.name": "weather_assistance",
# Set other attributes
"gen_ai.tool.name": "get_weather",
"gen_ai.tool.type": "function",
"gen_ai.tool.call.id": "call_abc123",
"gen_ai.tool.arguments": json.dumps({"location": "New York", "units": "celsius"}),
"gen_ai.tool.message": json.dumps({"temperature": 22, "condition": "sunny", "humidity": 65}),
})
```
## What’s next? [#whats-next]
After sending traces with the proper semantic conventions:
* View your [traces](/query-data/traces) in Console
* Set up [monitors and alerts](/monitor-data/monitors) based on your AI telemetry data
* Learn about the [GenAI APL functions](/apl/scalar-functions/genai-functions) for querying AI telemetry
---
# Redaction policies
Source: https://axiom.co/docs/use-cases/llm-observability/redaction-policies
Axiom AI SDK provides flexible redaction policies to control what data is captured in OpenTelemetry spans. This allows you to balance observability needs with privacy and compliance requirements.
## Built-in redaction policies [#built-in-redaction-policies]
Axiom AI SDK provides two built-in redaction policies:
| Policy | What gets captured | What gets excluded | When to use |
| :--------------------------------------------------- | :-------------------------------------- | :----------------------------------------------- | :----------------- |
| [AxiomDefault](#axiomdefault-policy) | Full data | – | Full observability |
| [OpenTelemetryDefault](#opentelemetrydefault-policy) | Model metadata, token usage, error info | Prompt text, AI responses, tool args and results | Privacy-first |
If you don’t specify a redaction policy, Axiom AI SDK applies `AxiomDefault`.
To determine which redaction policy fits your needs, see the following comparison:
### AxiomDefault policy [#axiomdefault-policy]
By default, Axiom AI SDK captures all data for maximum observability.
**What gets captured:**
* Full prompt text and AI responses in chat spans
* Complete tool arguments and return values on tool spans
* All standard OpenTelemetry attributes (model name, token usage, etc.)
Capturing full message content increases span size and storage costs.
**When to use:**
* You need full visibility into AI interactions
* Data privacy isn’t a concern
* Debugging complex AI workflows
### OpenTelemetryDefault policy [#opentelemetrydefault-policy]
The OpenTelemetry default policy excludes sensitive content.
**What gets captured:**
* Model metadata (name, provider, version)
* Token usage and performance metrics
* Error information and status codes
**What gets excluded:**
* Prompt text and AI responses
* Tool arguments and return values
**When to use:**
* Handling sensitive or personal data
* Compliance requirements restrict data capture
* You only need performance and error metrics
### What gets captured [#what-gets-captured]
To determine which redaction policy fits your needs, see the following examples about what gets captured with each defaultpolicy:
```json
{
"gen_ai.operation.name": "chat",
"gen_ai.request.model": "gpt-4o-mini",
"gen_ai.input.messages": "[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Hello, how are you?\"}]}]",
"gen_ai.output.messages": "[{\"role\":\"assistant\",\"content\":\"I'm doing well, thank you for asking!\"}]",
"gen_ai.usage.input_tokens": 12,
"gen_ai.usage.output_tokens": 15,
"gen_ai.usage.total_tokens": 27
}
```
```json
{
"gen_ai.tool.name": "weather_lookup",
"gen_ai.tool.description": "Get current weather for a location",
"gen_ai.tool.arguments": "{\"location\":\"San Francisco\",\"units\":\"celsius\"}",
"gen_ai.tool.message": "{\"temperature\":18,\"condition\":\"partly cloudy\"}"
}
```
```json
{
"gen_ai.operation.name": "chat",
"gen_ai.request.model": "gpt-4o-mini",
"gen_ai.usage.input_tokens": 12,
"gen_ai.usage.output_tokens": 15,
"gen_ai.usage.total_tokens": 27
}
```
Message content (`gen_ai.input.messages` and `gen_ai.output.messages`) is excluded for privacy.
```json
{
"gen_ai.tool.name": "weather_lookup",
"gen_ai.tool.description": "Get current weather for a location"
}
```
Tool arguments and results (`gen_ai.tool.arguments` and `gen_ai.tool.message`) are excluded for privacy.
## Global configuration [#global-configuration]
Set a default redaction policy for your entire app using `initAxiomAI`:
```ts Full data capture
import { trace } from '@opentelemetry/api';
import { initAxiomAI, RedactionPolicy } from 'axiom/ai';
const tracer = trace.getTracer("my-tracer");
initAxiomAI({ tracer, redactionPolicy: RedactionPolicy.AxiomDefault });
```
```ts Privacy-first
import { trace } from '@opentelemetry/api';
import { initAxiomAI, RedactionPolicy } from 'axiom/ai';
const tracer = trace.getTracer("my-tracer");
initAxiomAI({ tracer, redactionPolicy: RedactionPolicy.OpenTelemetryDefault });
```
`initAxiomAI` is called in your instrumentation file (`/src/instrumentation.ts`). For setup instructions, see [Instrumentation with Axiom AI SDK](/use-cases/llm-observability/axiom-ai-sdk-instrumentation).
## Per-operation override [#per-operation-override]
You can configure different policies for each operation. Axiom resolves redaction policies in the following order (from highest to lowest precedence):
1. Per-operation policy
2. Global policy
3. Default policy
Override the global or default policy for specific operations by passing a `redactionPolicy` to `withSpan`:
```ts
import { withSpan, RedactionPolicy } from 'axiom/ai';
import { generateText } from 'ai';
const result = await withSpan(
{ capability: 'customer_support', step: 'handle_sensitive_query' },
async (span) => {
span.setAttribute('user.id', userId);
return generateText({
model: wrappedModel,
prompt: 'Process this sensitive customer data...'
});
},
{ redactionPolicy: RedactionPolicy.OpenTelemetryDefault }
);
```
## Custom redaction policies [#custom-redaction-policies]
Create custom policies by defining an `AxiomAIRedactionPolicy` object:
```ts
import { trace } from '@opentelemetry/api';
import { initAxiomAI, AxiomAIRedactionPolicy } from 'axiom/ai';
const tracer = trace.getTracer("my-tracer");
// Custom policy: capture messages but not tool payloads
const customPolicy: AxiomAIRedactionPolicy = {
captureMessageContent: 'full',
mirrorToolPayloadOnToolSpan: false
};
initAxiomAI({ tracer, redactionPolicy: customPolicy });
```
The `AxiomAIRedactionPolicy` object has two properties:
Controls whether prompt and response text is included in chat spans.
* `'full'`: Include complete message content
* `'off'`: Exclude all message content
Controls whether tool arguments and results are duplicated on tool spans.
* `true`: Mirror tool data for easier querying
* `false`: Only capture tool metadata (name, description)
The [built-in policies](#built-in-redaction-policies) configure the `AxiomAIRedactionPolicy` object in the following way:
| Default policy | captureMessageContent | mirrorToolPayloadOnToolSpan |
| :------------------- | :-------------------- | :-------------------------- |
| AxiomDefault | `'full'` | `true` |
| OpenTelemetryDefault | `'off'` | `false` |
## Related documentation [#related-documentation]
Learn how to instrument your AI applications with Axiom AI SDK
Understand the OpenTelemetry attributes captured by Axiom AI SDK
---
# arg_max
Source: https://axiom.co/docs/apl/aggregation-function/arg-max
The `arg_max` aggregation in APL helps you identify the row with the maximum value for an expression and return additional fields from that record. Use `arg_max` when you want to determine key details associated with a row where the expression evaluates to the maximum value. If you group your data, `arg_max` finds the row within each group where a particular expression evaluates to the maximum value.
This aggregation is particularly useful in scenarios like the following:
* Pinpoint the slowest HTTP requests in log data and retrieve associated details (like URL, status code, and user agent) for the same row.
* Identify the longest span durations in OpenTelemetry traces with additional context (like span name, trace ID, and attributes) for the same row.
* Highlight the highest severity security alerts in logs along with relevant metadata (such as alert type, source, and timestamp) for the same row.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| summarize arg_max(expression, field1[, field2, ...])
```
### Parameters [#parameters]
| Parameter | Description |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `expression` | The expression whose maximum value determines the selected record. |
| `field1, field2` | The additional fields to retrieve from the record with the maximum numeric value. Use `*` as a wildcard to return all fields from the row. |
The wildcard `*` is useful to return all fields from the row with the maximum value, but it increases query complexity and decreases performance.
### Returns [#returns]
Returns a row where the expression evaluates to the maximum value for each group (or the entire dataset if no grouping is specified), containing the fields specified in the query.
### Name fields [#name-fields]
You can name fields in the parameters of `arg_max` using the syntax `arg_max(name1=expression, name2=field1)`. This specifies the field names that appear in the output.
**Query**
```kusto
['otel-demo-traces']
| summarize arg_max(duration, longestSpan=span_id), arg_max(numEvents=array_length(events), spanWithMostEvents=span_id)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20arg_max\(duration%2C%20longestSpan%3Dspan_id\)%2C%20arg_max\(numEvents%3Darray_length\(events\)%2C%20spanWithMostEvents%3Dspan_id\)%22%7D)
**Output**
| duration | longestSpan | numEvents | spanWithMostEvents |
| ---------------- | ---------------- | --------- | ------------------ |
| `5m0.004487127s` | 1a9f979bb25f6bbd | 408 | db13ffc3394905b5 |
## Use case examples [#use-case-examples]
Find the slowest path for each HTTP method in the `['sample-http-logs']` dataset.
**Query**
```kusto
['sample-http-logs']
| summarize arg_max(req_duration_ms, uri) by method
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20arg_max\(req_duration_ms%2C%20uri\)%20by%20method%22%7D)
**Output**
| uri | method | req\_duration\_ms |
| ------------- | ------ | ----------------- |
| /home | GET | 1200 |
| /api/products | POST | 2500 |
This query identifies the slowest path for each HTTP method.
Identify the span with the longest duration for each service in the `['otel-demo-traces']` dataset.
**Query**
```kusto
['otel-demo-traces']
| summarize arg_max(duration, span_id, trace_id) by ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20arg_max\(duration%2C%20span_id%2C%20trace_id\)%20by%20%5B'service.name'%5D%22%7D)
**Output**
| service.name | span\_id | trace\_id | duration |
| --------------- | -------- | --------- | -------- |
| frontend | span123 | trace456 | 3s |
| checkoutservice | span789 | trace012 | 5s |
This query identifies the span with the longest duration for each service, returning the `span_id`, `trace_id`, and `duration`.
Find the highest status code for each country in the `['sample-http-logs']` dataset.
**Query**
```kusto
['sample-http-logs']
| summarize arg_max(toint(status), uri) by ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20arg_max\(toint\(status\)%2C%20uri\)%20by%20%5B'geo.country'%5D%22%7D)
**Output**
| geo.country | uri | status |
| ----------- | ---------- | ------ |
| USA | /admin | 500 |
| Canada | /dashboard | 503 |
This query identifies the URI with the highest status code for each country.
## List of related aggregations [#list-of-related-aggregations]
* [arg\_min](/apl/aggregation-function/arg-min): Retrieves the record with the minimum value for a numeric field.
* [max](/apl/aggregation-function/max): Retrieves the maximum value for a numeric field but doesn’t return additional fields.
* [percentile](/apl/aggregation-function/percentile): Provides the value at a specific percentile of a numeric field.
## Other query languages [#other-query-languages]
Splunk SPL doesn’t have an equivalent to `arg_max`. You can use `stats` with a combination of `max` and `by` clauses to evaluate the maximum value of a single numberic field. APL provides a dedicated `arg_max` aggregation that evaluates expressions.
```sql Splunk example
| stats max(req_duration_ms) as max_duration by id, uri
```
```kusto APL equivalent
['sample-http-logs']
| summarize arg_max(req_duration_ms, id, uri)
```
In ANSI SQL, you typically use a subquery to find the maximum value and then join it back to the original table to retrieve additional fields. APL’s `arg_max` provides a more concise and efficient alternative.
```sql SQL example
WITH MaxValues AS (
SELECT id, MAX(req_duration_ms) as max_duration
FROM sample_http_logs
GROUP BY id
)
SELECT logs.id, logs.uri, MaxValues.max_duration
FROM sample_http_logs logs
JOIN MaxValues
ON logs.id = MaxValues.id;
```
```kusto APL equivalent
['sample-http-logs']
| summarize arg_max(req_duration_ms, id, uri)
```
---
# arg_min
Source: https://axiom.co/docs/apl/aggregation-function/arg-min
The `arg_min` aggregation in APL allows you to identify the row in a dataset where an expression evaluates to the minimum value. You can use this to retrieve other associated fields in the same row, making it particularly useful for pinpointing details about the smallest value in large datasets. If you group your data, `arg_min` finds the row within each group where a particular expression evaluates to the minimum value.
This aggregation is particularly useful in scenarios like the following:
* Pinpoint the shortest HTTP requests in log data and retrieve associated details (like URL, status code, and user agent) for the same row.
* Identify the fastest span durations in OpenTelemetry traces with additional context (like span name, trace ID, and attributes) for the same row.
* Highlight the lowest severity security alerts in logs along with relevant metadata (such as alert type, source, and timestamp) for the same row.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| summarize arg_min(expression, field1, ..., fieldN)
```
### Parameters [#parameters]
* `expression`: The expression to evaluate for the minimum value.
* `field1, ..., fieldN`: Additional fields to return from the row with the minimum value. Use `*` as a wildcard to return all fields from the row.
The wildcard `*` is useful to return all fields from the row with the minimum value, but it increases query complexity and decreases performance.
### Returns [#returns]
Returns a row where the expression evaluates to the minimum value for each group (or the entire dataset if no grouping is specified), containing the fields specified in the query.
### Name fields [#name-fields]
You can name fields in the parameters of `arg_min` using the syntax `arg_min(name1=expression, name2=field1)`. This specifies the field names that appear in the output.
**Query**
```kusto
['otel-demo-traces']
| summarize arg_min(duration, longestSpan=span_id), arg_min(numEvents=array_length(events), spanWithMostEvents=span_id)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20arg_min\(duration%2C%20longestSpan%3Dspan_id\)%2C%20arg_min\(numEvents%3Darray_length\(events\)%2C%20spanWithMostEvents%3Dspan_id\)%22%7D)
**Output**
| duration | longestSpan | numEvents | spanWithMostEvents |
| -------- | ---------------- | --------- | ------------------ |
| `190ns` | 1a9f979bb25f6bbd | 1 | db13ffc3394905b5 |
## Use case examples [#use-case-examples]
You can use `arg_min` to identify the path with the shortest duration and its associated details for each method.
**Query**
```kusto
['sample-http-logs']
| summarize arg_min(req_duration_ms, uri) by method
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20arg_min\(req_duration_ms%2C%20uri\)%20by%20method%22%7D)
**Output**
| req\_duration\_ms | uri | method |
| ----------------- | ---------- | ------ |
| 0.1 | /api/login | POST |
This query identifies the paths with the shortest duration for each method and provides details about the path.
Use `arg_min` to find the span with the shortest duration for each service and retrieve its associated details.
**Query**
```kusto
['otel-demo-traces']
| summarize arg_min(duration, trace_id, span_id, kind) by ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20arg_min\(duration%2C%20trace_id%2C%20span_id%2C%20kind\)%20by%20%5B'service.name'%5D%22%7D)
**Output**
| duration | trace\_id | span\_id | service.name | kind |
| -------- | --------- | -------- | ------------ | ------ |
| 00:00:01 | abc123 | span456 | frontend | server |
This query identifies the span with the shortest duration for each service along with its metadata.
Find the lowest status code for each country in the `['sample-http-logs']` dataset.
**Query**
```kusto
['sample-http-logs']
| summarize arg_min(toint(status), uri) by ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20arg_min\(toint\(status\)%2C%20uri\)%20by%20%5B'geo.country'%5D%22%7D)
**Output**
| geo.country | uri | status |
| ----------- | ---------- | ------ |
| USA | /admin | 200 |
| Canada | /dashboard | 201 |
This query identifies the URI with the lowest status code for each country.
## List of related aggregations [#list-of-related-aggregations]
* [arg\_max](/apl/aggregation-function/arg-max): Returns the row with the maximum value for a numeric field, useful for finding peak metrics.
* [min](/apl/aggregation-function/min): Returns only the minimum value of a numeric field without additional fields.
* [percentile](/apl/aggregation-function/percentile): Provides the value at a specific percentile of a numeric field.
## Other query languages [#other-query-languages]
Splunk SPL doesn’t have an equivalent to `arg_min`. You can use `stats` with a combination of `values` and `first` clauses to evaluate the minimum value of a single numberic field. APL provides a dedicated `arg_min` aggregation that evaluates expressions.
```sql Splunk example
| stats min(req_duration_ms) as minDuration by id
| where req_duration_ms=minDuration
```
```kusto APL equivalent
['sample-http-logs']
| summarize arg_min(req_duration_ms, id, uri)
```
In ANSI SQL, achieving similar functionality often requires a combination of `MIN`, `GROUP BY`, and `JOIN` to retrieve the associated fields. APL's `arg_min` eliminates the need for multiple steps by directly returning the row with the minimum value.
```sql SQL example
SELECT id, uri
FROM sample_http_logs
WHERE req_duration_ms = (
SELECT MIN(req_duration_ms)
FROM sample_http_logs
);
```
```kusto APL equivalent
['sample-http-logs']
| summarize arg_min(req_duration_ms, id, uri)
```
---
# avg
Source: https://axiom.co/docs/apl/aggregation-function/avg
The `avg` aggregation in APL calculates the average value of a numeric field across a set of records. You can use this aggregation when you need to determine the mean value of numerical data, such as request durations, response times, or other performance metrics. It’s useful in scenarios such as performance analysis, trend identification, and general statistical analysis.
When to use `avg`:
* When you want to analyze the average of numeric values over a specific time range or set of data.
* For comparing trends, like average request duration or latency across HTTP requests.
* To provide insight into system or user performance, such as the average duration of transactions in a service.
## Usage [#usage]
### Syntax [#syntax]
```kusto
summarize avg(ColumnName) [by GroupingColumn]
```
### Parameters [#parameters]
* **ColumnName**: The numeric field you want to calculate the average of.
* **GroupingColumn** (optional): A column to group the results by. If not specified, the average is calculated over all records.
### Returns [#returns]
* A table with the average value for the specified field, optionally grouped by another column.
## Use case examples [#use-case-examples]
This example calculates the average request duration for HTTP requests, grouped by status.
**Query**
```kusto
['sample-http-logs']
| summarize avg(req_duration_ms) by status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20summarize%20avg\(req_duration_ms\)%20by%20status%22%7D)
**Output**
| status | avg\_req\_duration\_ms |
| ------ | ---------------------- |
| 200 | 350.4 |
| 404 | 150.2 |
This query calculates the average request duration (in milliseconds) for each HTTP status code.
This example calculates the average span duration for each service to analyze performance across services.
**Query**
```kusto
['otel-demo-traces']
| summarize avg(duration) by ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%5Cn%7C%20summarize%20avg\(duration\)%20by%20%5B'service.name'%5D%22%7D)
**Output**
| service.name | avg\_duration |
| ------------ | ------------- |
| frontend | 500ms |
| cartservice | 250ms |
This query calculates the average duration of spans for each service.
In security logs, you can calculate the average request duration by country to analyze regional performance trends.
**Query**
```kusto
['sample-http-logs']
| summarize avg(req_duration_ms) by ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20summarize%20avg\(req_duration_ms\)%20by%20%5B'geo.country'%5D%22%7D)
**Output**
| geo.country | avg\_req\_duration\_ms |
| ----------- | ---------------------- |
| US | 400.5 |
| DE | 250.3 |
This query calculates the average request duration for each country from where the requests originated.
## List of related aggregations [#list-of-related-aggregations]
* [**sum**](/apl/aggregation-function/sum): Use `sum` to calculate the total of a numeric field. This is useful when you want the total of values rather than their average.
* [**count**](/apl/aggregation-function/count): The `count` function returns the total number of records. It’s useful when you want to count occurrences rather than averaging numerical values.
* [**min**](/apl/aggregation-function/min): The `min` function returns the minimum value of a numeric field. Use this when you’re interested in the smallest value in your dataset.
* [**max**](/apl/aggregation-function/max): The `max` function returns the maximum value of a numeric field. This is useful for finding the largest value in the data.
* [**stdev**](/apl/aggregation-function/stdev): This function calculates the standard deviation of a numeric field, providing insight into how spread out the data is around the mean.
## Other query languages [#other-query-languages]
In Splunk SPL, the `avg` function works similarly, but the syntax differs slightly. Here’s how to write the equivalent query in APL.
```sql Splunk example
| stats avg(req_duration_ms) by status
```
```kusto APL equivalent
['sample-http-logs']
| summarize avg(req_duration_ms) by status
```
In ANSI SQL, the `avg` aggregation is used similarly, but APL has a different syntax for structuring the query.
```sql SQL example
SELECT status, AVG(req_duration_ms)
FROM sample_http_logs
GROUP BY status
```
```kusto APL equivalent
['sample-http-logs']
| summarize avg(req_duration_ms) by status
```
---
# avgif
Source: https://axiom.co/docs/apl/aggregation-function/avgif
The `avgif` aggregation function in APL allows you to calculate the average value of a field, but only for records that satisfy a given condition. This function is particularly useful when you need to perform a filtered aggregation, such as finding the average response time for requests that returned a specific status code or filtering by geographic regions. The `avgif` function is highly valuable in scenarios like log analysis, performance monitoring, and anomaly detection, where focusing on subsets of data can provide more accurate insights.
## Usage [#usage]
### Syntax [#syntax]
```kusto
summarize avgif(expr, predicate) by grouping_field
```
### Parameters [#parameters]
* **`expr`**: The field for which you want to calculate the average.
* **`predicate`**: A boolean condition that filters which records are included in the calculation.
* **`grouping_field`**: (Optional) A field by which you want to group the results.
### Returns [#returns]
The function returns the average of the values from the `expr` field for the records that satisfy the `predicate`. If no records match the condition, the result is `null`.
## Use case examples [#use-case-examples]
In this example, you calculate the average request duration for HTTP status 200 in different cities.
**Query**
```kusto
['sample-http-logs']
| summarize avgif(req_duration_ms, status == "200") by ['geo.city']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20avgif%28req_duration_ms%2C%20status%20%3D%3D%20%22200%22%29%20by%20%5B%27geo.city%27%5D%22%7D)
**Output**
| geo.city | avg\_req\_duration\_ms |
| -------- | ---------------------- |
| New York | 325 |
| London | 400 |
| Tokyo | 275 |
This query calculates the average request duration (`req_duration_ms`) for HTTP requests that returned a status of 200 (`status == "200"`), grouped by the city where the request originated (`geo.city`).
In this example, you calculate the average span duration for traces that ended with HTTP status 500.
**Query**
```kusto
['otel-demo-traces']
| summarize avgif(duration, status == "500") by ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20summarize%20avgif%28duration%2C%20status%20%3D%3D%20%22500%22%29%20by%20%5B%27service.name%27%5D%22%7D)
**Output**
| service.name | avg\_duration |
| --------------- | ------------- |
| checkoutservice | 500ms |
| frontend | 600ms |
| cartservice | 475ms |
This query calculates the average span duration (`duration`) for traces where the status code is 500 (`status == "500"`), grouped by the service name (`service.name`).
In this example, you calculate the average request duration for failed HTTP requests (status code 400 or higher) by country.
**Query**
```kusto
['sample-http-logs']
| summarize avgif(req_duration_ms, toint(status) >= 400) by ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20avgif%28req_duration_ms%2C%20toint%28status%29%20%3E%3D%20400%29%20by%20%5B%27geo.country%27%5D%22%7D)
**Output**
| geo.country | avg\_req\_duration\_ms |
| ----------- | ---------------------- |
| USA | 450 |
| Canada | 500 |
| Germany | 425 |
This query calculates the average request duration (`req_duration_ms`) for failed HTTP requests (`status >= 400`), grouped by the country of origin (`geo.country`).
## List of related aggregations [#list-of-related-aggregations]
* [**minif**](/apl/aggregation-function/minif): Returns the minimum value of an expression, filtered by a predicate. Use when you want to find the smallest value for a subset of data.
* [**maxif**](/apl/aggregation-function/maxif): Returns the maximum value of an expression, filtered by a predicate. Use when you are looking for the largest value within specific conditions.
* [**countif**](/apl/aggregation-function/countif): Counts the number of records that match a condition. Use when you want to know how many records meet a specific criterion.
* [**sumif**](/apl/aggregation-function/sumif): Sums the values of a field that match a given condition. Ideal for calculating the total of a subset of data.
## Other query languages [#other-query-languages]
In Splunk, you achieve similar functionality using the combination of a `stats` function with conditional filtering. In APL, `avgif` provides this filtering inline as part of the aggregation function, which can simplify your queries.
```sql Splunk example
| stats avg(req_duration_ms) by id where status = "200"
```
```kusto APL equivalent
['sample-http-logs']
| summarize avgif(req_duration_ms, status == "200") by id
```
In ANSI SQL, you can use a `CASE` statement inside an `AVG` function to achieve similar behavior. APL simplifies this with `avgif`, allowing you to specify the condition directly.
```sql SQL example
SELECT id, AVG(CASE WHEN status = '200' THEN req_duration_ms ELSE NULL END)
FROM sample_http_logs
GROUP BY id
```
```kusto APL equivalent
['sample-http-logs']
| summarize avgif(req_duration_ms, status == "200") by id
```
---
# count
Source: https://axiom.co/docs/apl/aggregation-function/count
The `count` aggregation in APL returns the total number of records in a dataset or the total number of records that match specific criteria. This function is useful when you need to quantify occurrences, such as counting log entries, user actions, or security events.
When to use `count`:
* To count the total number of events in log analysis, such as the number of HTTP requests or errors.
* To monitor system usage, such as the number of transactions or API calls.
* To identify security incidents by counting failed login attempts or suspicious activities.
## Usage [#usage]
### Syntax [#syntax]
```kusto
summarize count() [by GroupingColumn]
```
### Parameters [#parameters]
* **GroupingColumn** (optional): A column to group the count results by. If not specified, the total number of records across the dataset is returned.
### Returns [#returns]
* A table with the count of records for the entire dataset or grouped by the specified column.
## Use case examples [#use-case-examples]
In log analysis, you can count the number of HTTP requests by status to get a sense of how many requests result in different HTTP status codes.
**Query**
```kusto
['sample-http-logs']
| summarize count() by status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20summarize%20count\(\)%20by%20status%22%7D)
**Output**
| status | count |
| ------ | ----- |
| 200 | 1500 |
| 404 | 200 |
This query counts the total number of HTTP requests for each status code in the logs.
For OpenTelemetry traces, you can count the total number of spans for each service, which helps you monitor the distribution of requests across services.
**Query**
```kusto
['otel-demo-traces']
| summarize count() by ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%5Cn%7C%20summarize%20count\(\)%20by%20%5B'service.name'%5D%22%7D)
**Output**
| service.name | count |
| ------------ | ----- |
| frontend | 1000 |
| cartservice | 500 |
This query counts the number of spans for each service in the OpenTelemetry traces dataset.
In security logs, you can count the number of requests by country to identify where the majority of traffic or suspicious activity originates.
**Query**
```kusto
['sample-http-logs']
| summarize count() by ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20summarize%20count\(\)%20by%20%5B'geo.country'%5D%22%7D)
**Output**
| geo.country | count |
| ----------- | ----- |
| US | 3000 |
| DE | 500 |
This query counts the number of requests originating from each country.
## List of related aggregations [#list-of-related-aggregations]
* [**sum**](/apl/aggregation-function/sum): Use `sum` to calculate the total sum of a numeric field, as opposed to counting the number of records.
* [**avg**](/apl/aggregation-function/avg): The `avg` function calculates the average of a numeric field. Use it when you want to determine the mean value of data instead of the count.
* [**min**](/apl/aggregation-function/min): The `min` function returns the minimum value of a numeric field, helping to identify the smallest value in a dataset.
* [**max**](/apl/aggregation-function/max): The `max` function returns the maximum value of a numeric field, useful for identifying the largest value.
* [**countif**](/apl/aggregation-function/countif): The `countif` function allows you to count only records that meet specific conditions, giving you more flexibility in your count queries.
## Other query languages [#other-query-languages]
In Splunk SPL, the `count` function works similarly to APL, but the syntax differs slightly.
```sql Splunk example
| stats count by status
```
```kusto APL equivalent
['sample-http-logs']
| summarize count() by status
```
In ANSI SQL, the `count` function works similarly, but APL uses different syntax for querying.
```sql SQL example
SELECT status, COUNT(*)
FROM sample_http_logs
GROUP BY status
```
```kusto APL equivalent
['sample-http-logs']
| summarize count() by status
```
---
# countif
Source: https://axiom.co/docs/apl/aggregation-function/countif
The `countif` aggregation function in Axiom Processing Language (APL) counts the number of records that meet a specified condition. You can use this aggregation to filter records based on a specific condition and return a count of matching records. This is particularly useful for log analysis, security audits, and tracing events when you need to isolate and count specific data subsets.
Use `countif` when you want to count occurrences of certain conditions, such as HTTP status codes, errors, or actions in telemetry traces.
## Usage [#usage]
### Syntax [#syntax]
```kusto
countif(condition)
```
### Parameters [#parameters]
* **condition**: A boolean expression that filters the records based on a condition. Only records where the condition evaluates to `true` are counted.
### Returns [#returns]
The function returns the number of records that match the specified condition.
## Use case examples [#use-case-examples]
In log analysis, you might want to count how many HTTP requests returned a 500 status code to detect server errors.
**Query**
```kusto
['sample-http-logs']
| summarize countif(status == '500')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20countif\(status%20%3D%3D%20'500'\)%22%7D)
**Output**
| count\_errors |
| ------------- |
| 72 |
This query counts the number of HTTP requests with a `500` status, helping you identify how many server errors occurred.
In OpenTelemetry traces, you might want to count how many requests were initiated by the client service kind.
**Query**
```kusto
['otel-demo-traces']
| summarize countif(kind == 'client')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20countif\(kind%20%3D%3D%20'client'\)%22%7D)
**Output**
| count\_client\_kind |
| ------------------- |
| 345 |
This query counts how many requests were initiated by the `client` service kind, providing insight into the volume of client-side traffic.
In security logs, you might want to count how many HTTP requests originated from a specific city, such as New York.
**Query**
```kusto
['sample-http-logs']
| summarize countif(['geo.city'] == 'New York')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20countif\(%5B'geo.city'%5D%20%3D%3D%20'New%20York'\)%22%7D)
**Output**
| count\_nyc\_requests |
| -------------------- |
| 87 |
This query counts how many HTTP requests originated from New York, which can help detect traffic from a particular location for security analysis.
## List of related aggregations [#list-of-related-aggregations]
* [**count**](/apl/aggregation-function/count): Counts all records in a dataset without applying a condition. Use this when you need the total count of records, regardless of any specific condition.
* [**sumif**](/apl/aggregation-function/sumif): Adds up the values of a field for records that meet a specific condition. Use `sumif` when you want to sum values based on a filter.
* [**dcountif**](/apl/aggregation-function/dcountif): Counts distinct values of a field for records that meet a condition. This is helpful when you need to count unique occurrences.
* [**avgif**](/apl/aggregation-function/avgif): Calculates the average value of a field for records that match a condition, useful for performance monitoring.
* [**maxif**](/apl/aggregation-function/maxif): Returns the maximum value of a field for records that meet a condition. Use this when you want to find the highest value in filtered data.
## Other query languages [#other-query-languages]
In Splunk SPL, conditional counting is typically done using the `eval` function combined with `stats`. APL provides a more streamlined approach with the `countif` function, which performs conditional counting directly.
```sql Splunk example
| stats count(eval(status="500")) AS error_count
```
```kusto APL equivalent
['sample-http-logs']
| summarize countif(status == '500')
```
In ANSI SQL, conditional counting is achieved by using the `COUNT` function with a `CASE` statement. In APL, `countif` simplifies this process by offering a direct approach to conditional counting.
```sql SQL example
SELECT COUNT(CASE WHEN status = '500' THEN 1 END) AS error_count
FROM sample_http_logs
```
```kusto APL equivalent
['sample-http-logs']
| summarize countif(status == '500')
```
---
# dcount
Source: https://axiom.co/docs/apl/aggregation-function/dcount
The `dcount` aggregation function in Axiom Processing Language (APL) counts the distinct values in a column. This function is essential when you need to know the number of unique values, such as counting distinct users, unique requests, or distinct error codes in log files.
Use `dcount` for analyzing datasets where it’s important to identify the number of distinct occurrences, such as unique IP addresses in security logs, unique user IDs in app logs, or unique trace IDs in OpenTelemetry traces.
The `dcount` aggregation in APL is a statistical aggregation that returns estimated results. The estimation comes with the benefit of speed at the expense of accuracy. This means that `dcount` is fast and light on resources even on a large or high-cardinality dataset, but it doesn’t provide precise results.
## Usage [#usage]
### Syntax [#syntax]
```kusto
dcount(column_name)
```
### Parameters [#parameters]
* **column\_name**: The name of the column for which you want to count distinct values.
### Returns [#returns]
The function returns the count of distinct values found in the specified column.
## Use case examples [#use-case-examples]
In log analysis, you can count how many distinct users accessed the service.
**Query**
```kusto
['sample-http-logs']
| summarize dcount(id)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20dcount\(id\)%22%7D)
**Output**
| distinct\_users |
| --------------- |
| 45 |
This query counts the distinct values in the `id` field, representing the number of unique users who accessed the system.
In OpenTelemetry traces, you can count how many unique trace IDs are recorded.
**Query**
```kusto
['otel-demo-traces']
| summarize dcount(trace_id)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20dcount\(trace_id\)%22%7D)
**Output**
| distinct\_traces |
| ---------------- |
| 321 |
This query counts the distinct trace IDs in the dataset, helping you determine how many unique traces are being captured.
In security logs, you can count how many distinct IP addresses were logged.
**Query**
```kusto
['sample-http-logs']
| summarize dcount(['geo.city'])
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20dcount\(%5B'geo.city'%5D\)%22%7D)
**Output**
| distinct\_cities |
| ---------------- |
| 35 |
This query counts the number of distinct cities recorded in the logs, which helps analyze the geographic distribution of traffic.
## List of related aggregations [#list-of-related-aggregations]
* [**count**](/apl/aggregation-function/count): Counts the total number of records in the dataset, including duplicates. Use it when you need to know the overall number of records.
* [**countif**](/apl/aggregation-function/countif): Counts records that match a specific condition. Use `countif` when you want to count records based on a filter or condition.
* [**dcountif**](/apl/aggregation-function/dcountif): Counts the distinct values in a column but only for records that meet a condition. It’s useful when you need a filtered distinct count.
* [**sum**](/apl/aggregation-function/sum): Sums the values in a column. Use this when you need to add up values rather than counting distinct occurrences.
* [**avg**](/apl/aggregation-function/avg): Calculates the average value for a column. Use this when you want to find the average of a specific numeric field.
## Other query languages [#other-query-languages]
In Splunk SPL, you can count distinct values using the `dc` function within the `stats` command. In APL, the `dcount` function offers similar functionality.
```sql Splunk example
| stats dc(user_id) AS distinct_users
```
```kusto APL equivalent
['sample-http-logs']
| summarize dcount(id)
```
In ANSI SQL, distinct counting is typically done using `COUNT` with the `DISTINCT` keyword. In APL, `dcount` provides a direct and efficient way to count distinct values.
```sql SQL example
SELECT COUNT(DISTINCT user_id) AS distinct_users
FROM sample_http_logs
```
```kusto APL equivalent
['sample-http-logs']
| summarize dcount(id)
```
---
# dcountif
Source: https://axiom.co/docs/apl/aggregation-function/dcountif
The `dcountif` aggregation function in Axiom Processing Language (APL) counts the distinct values in a column that meet a specific condition. This is useful when you want to filter records and count only the unique occurrences that satisfy a given criterion.
Use `dcountif` in scenarios where you need a distinct count but only for a subset of the data, such as counting unique users from a specific region, unique error codes for specific HTTP statuses, or distinct traces that match a particular service type.
## Usage [#usage]
### Syntax [#syntax]
```kusto
dcountif(column_name, condition)
```
### Parameters [#parameters]
* **column\_name**: The name of the column for which you want to count distinct values.
* **condition**: A boolean expression that filters the records. Only records that meet the condition will be included in the distinct count.
### Returns [#returns]
The function returns the count of distinct values that meet the specified condition.
## Use case examples [#use-case-examples]
In log analysis, you might want to count how many distinct users accessed the service and received a successful response (HTTP status 200).
**Query**
```kusto
['sample-http-logs']
| summarize dcountif(id, status == '200')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20dcountif\(id%2C%20status%20%3D%3D%20'200'\)%22%7D)
**Output**
| distinct\_successful\_users |
| --------------------------- |
| 50 |
This query counts the distinct users (`id` field) who received a successful HTTP response (status 200), helping you understand how many unique users had successful requests.
In OpenTelemetry traces, you might want to count how many unique trace IDs are recorded for a specific service, such as `frontend`.
**Query**
```kusto
['otel-demo-traces']
| summarize dcountif(trace_id, ['service.name'] == 'frontend')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20dcountif\(trace_id%2C%20%5B'service.name'%5D%20%3D%3D%20'frontend'\)%22%7D)
**Output**
| distinct\_frontend\_traces |
| -------------------------- |
| 123 |
This query counts the number of distinct trace IDs that belong to the `frontend` service, providing insight into the volume of unique traces for that service.
In security logs, you might want to count how many unique IP addresses were logged for requests that resulted in a 403 status (forbidden access).
**Query**
```kusto
['sample-http-logs']
| summarize dcountif(['geo.city'], status == '403')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20dcountif\(%5B'geo.city'%5D%2C%20status%20%3D%3D%20'403'\)%22%7D)
**Output**
| distinct\_cities\_forbidden |
| --------------------------- |
| 20 |
This query counts the number of distinct cities (`geo.city` field) where requests resulted in a `403` status, helping you identify potential unauthorized access attempts from different regions.
## List of related aggregations [#list-of-related-aggregations]
* [**dcount**](/apl/aggregation-function/dcount): Counts distinct values without applying any condition. Use this when you need to count unique values across the entire dataset.
* [**countif**](/apl/aggregation-function/countif): Counts records that match a specific condition, without focusing on distinct values. Use this when you need to count records based on a filter.
* [**dcountif**](/apl/aggregation-function/dcountif): Use this function to get a distinct count for records that meet a condition. It combines both filtering and distinct counting.
* [**sumif**](/apl/aggregation-function/sumif): Sums values in a column for records that meet a condition. This is useful when you need to sum data points after filtering.
* [**avgif**](/apl/aggregation-function/avgif): Calculates the average value of a column for records that match a condition. Use this when you need to find the average based on a filter.
## Other query languages [#other-query-languages]
In Splunk SPL, counting distinct values conditionally is typically achieved using a combination of `eval` and `dc` in the `stats` function. APL simplifies this with the `dcountif` function, which handles both filtering and distinct counting in a single step.
```sql Splunk example
| stats dc(eval(status="200")) AS distinct_successful_users
```
```kusto APL equivalent
['sample-http-logs']
| summarize dcountif(id, status == '200')
```
In ANSI SQL, conditional distinct counting can be done using a combination of `COUNT(DISTINCT)` and `CASE`. APL's `dcountif` function provides a more concise and readable way to handle conditional distinct counting.
```sql SQL example
SELECT COUNT(DISTINCT CASE WHEN status = '200' THEN user_id END) AS distinct_successful_users
FROM sample_http_logs
```
```kusto APL equivalent
['sample-http-logs']
| summarize dcountif(id, status == '200')
```
---
# histogram
Source: https://axiom.co/docs/apl/aggregation-function/histogram
The `histogram` aggregation in APL allows you to create a histogram that groups numeric values into intervals or “bins.” This is useful for visualizing the distribution of data, such as the frequency of response times, request durations, or other continuous numerical fields. You can use it to analyze patterns and trends in datasets like logs, traces, or metrics. It’s especially helpful when you need to summarize a large volume of data into a digestible form, providing insights on the distribution of values.
The `histogram` aggregation is ideal for identifying peaks, valleys, and outliers in your data. For example, you can analyze the distribution of request durations in web server logs or span durations in OpenTelemetry traces to understand performance bottlenecks.
The `histogram` aggregation in APL is a statistical aggregation that returns estimated results. The estimation comes with the benefit of speed at the expense of accuracy. This means that `histogram` is fast and light on resources even on a large or high-cardinality dataset, but it doesn’t provide precise results.
## Usage [#usage]
### Syntax [#syntax]
```kusto
histogram(numeric_field, number_of_bins)
```
### Parameters [#parameters]
* `numeric_field`: The numeric field to create a histogram for. For example, request duration or span duration.
* `number_of_bins`: The number of bins (intervals) to use for grouping the numeric values.
### Returns [#returns]
The `histogram` aggregation returns a table where each row represents a bin, along with the number of occurrences (counts) that fall within each bin.
## Use case examples [#use-case-examples]
You can use the `histogram` aggregation to analyze the distribution of request durations in web server logs.
**Query**
```kusto
['sample-http-logs']
| summarize histogram(req_duration_ms, 100) by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20histogram\(req_duration_ms%2C%20100\)%20by%20bin_auto\(_time\)%22%7D)
**Output**
| req\_duration\_ms\_bin | count |
| ---------------------- | ----- |
| 0 | 50 |
| 100 | 200 |
| 200 | 120 |
This query creates a histogram that groups request durations into bins of 100 milliseconds and shows the count of requests in each bin. It helps you visualize how frequently requests fall within certain duration ranges.
In OpenTelemetry traces, you can use the `histogram` aggregation to analyze the distribution of span durations.
**Query**
```kusto
['otel-demo-traces']
| summarize histogram(duration, 100) by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20histogram\(duration%2C%20100\)%20by%20bin_auto\(_time\)%22%7D)
**Output**
| duration\_bin | count |
| ------------- | ----- |
| 0.1s | 30 |
| 0.2s | 120 |
| 0.3s | 50 |
This query groups the span durations into 100ms intervals, making it easier to spot latency issues in your traces.
In security logs, the `histogram` aggregation helps you understand the frequency distribution of request durations to detect anomalies or attacks.
**Query**
```kusto
['sample-http-logs']
| where status == '200'
| summarize histogram(req_duration_ms, 50) by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20%3D%3D%20'200'%20%7C%20summarize%20histogram\(req_duration_ms%2C%2050\)%20by%20bin_auto\(_time\)%22%7D)
**Output**
| req\_duration\_ms\_bin | count |
| ---------------------- | ----- |
| 0 | 150 |
| 50 | 400 |
| 100 | 100 |
This query analyzes the request durations for HTTP 200 (Success) responses, helping you identify patterns in security-related events.
## List of related aggregations [#list-of-related-aggregations]
* [**percentile**](/apl/aggregation-function/percentile): Use `percentile` when you need to find the specific value below which a percentage of observations fall, which can provide more precise distribution analysis.
* [**avg**](/apl/aggregation-function/avg): Use `avg` for calculating the average value of a numeric field, useful when you are more interested in the central tendency rather than distribution.
* [**sum**](/apl/aggregation-function/sum): The `sum` function adds up the total values in a numeric field, helpful for determining overall totals.
* [**count**](/apl/aggregation-function/count): Use `count` when you need a simple tally of rows or events, often in conjunction with `histogram` for more basic summarization.
## Other query languages [#other-query-languages]
In Splunk SPL, a similar operation to APL's `histogram` is the `timechart` or `histogram` command, which groups events into time buckets. However, in APL, the `histogram` function focuses on numeric values, allowing you to control the number of bins precisely.
```splunk Splunk example
| stats count by duration | timechart span=10 count
```
```kusto APL equivalent
['sample-http-logs']
| summarize count() by histogram(req_duration_ms, 10)
```
In ANSI SQL, you can use the `GROUP BY` clause combined with range calculations to achieve a similar result to APL’s `histogram`. However, APL’s `histogram` function simplifies the process by automatically calculating bin intervals.
```sql SQL example
SELECT COUNT(*), FLOOR(req_duration_ms/10)*10 as duration_bin
FROM sample_http_logs
GROUP BY duration_bin
```
```kusto APL equivalent
['sample-http-logs']
| summarize count() by histogram(req_duration_ms, 10)
```
---
# histogramif
Source: https://axiom.co/docs/apl/aggregation-function/histogramif
The `histogramif` aggregation in APL creates a histogram that groups numeric values into intervals (bins) for rows where a specified condition evaluates to true. This is useful when you want to visualize the distribution of data conditionally—for example, analyzing response times only for successful requests or examining span durations only for specific services.
You use `histogramif` when you need to combine filtering and distribution analysis in a single operation, making your queries more efficient and expressive.
Like the `histogram` aggregation, `histogramif` returns estimated results. The estimation provides speed benefits at the expense of precision, making it fast and resource-efficient even on large or high-cardinality datasets.
## Usage [#usage]
### Syntax [#syntax]
```kusto
histogramif(numeric_field, number_of_bins, condition)
```
### Parameters [#parameters]
| Name | Type | Description |
| ---------------- | ------ | --------------------------------------------------------------------------------------- |
| `numeric_field` | `real` | The numeric field to create a histogram for, such as request duration or response size. |
| `number_of_bins` | `long` | The number of intervals (bins) to use for grouping the numeric values. |
| `condition` | `bool` | A boolean expression that determines which rows to include in the histogram. |
### Returns [#returns]
A table where each row represents a bin, along with the number of occurrences (counts) that fall within each bin for rows where the condition evaluates to true.
## Use case examples [#use-case-examples]
Use `histogramif` to analyze the distribution of request durations only for successful HTTP requests.
**Query**
```kusto
['sample-http-logs']
| summarize histogramif(req_duration_ms, 100, status == '200') by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20histogramif\(req_duration_ms%2C%20100%2C%20status%20%3D%3D%20'200'\)%20by%20bin_auto\(_time\)%22%7D)
This query creates a histogram of request durations grouped into 100ms bins, but only includes requests with a `200` HTTP status code. This helps you understand the performance characteristics of successful requests.
Use `histogramif` to analyze span duration distributions for specific services in your OpenTelemetry traces.
**Query**
```kusto
['otel-demo-traces']
| summarize histogramif(duration, 50, ['service.name'] == 'frontend') by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20histogramif\(duration%2C%2050%2C%20%5B'service.name'%5D%20%3D%3D%20'frontend'\)%20by%20bin_auto\(_time\)%22%7D)
This query groups span durations into 50ms intervals, focusing only on the frontend service. This helps you identify performance patterns specific to that service.
Use `histogramif` to examine the distribution of request durations for specific geographic regions, helping you identify regional performance issues or anomalies.
**Query**
```kusto
['sample-http-logs']
| summarize histogramif(req_duration_ms, 50, ['geo.country'] == 'United States') by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20histogramif\(req_duration_ms%2C%2050%2C%20%5B'geo.country'%5D%20%3D%3D%20'United%20States'\)%20by%20bin_auto\(_time\)%22%7D)
This query analyzes request duration patterns for traffic originating from the US, helping you identify geographic performance variations or security patterns.
## List of related aggregations [#list-of-related-aggregations]
* [histogram](/apl/aggregation-function/histogram): Use `histogram` when you want to create a distribution without a condition. Use `histogramif` when you need to filter rows first.
* [countif](/apl/aggregation-function/countif): Use `countif` for simple conditional counting. Use `histogramif` when you need distribution analysis with a condition.
* [avgif](/apl/aggregation-function/avgif): Use `avgif` when you need the average of values matching a condition. Use `histogramif` for full distribution analysis.
* [percentileif](/apl/aggregation-function/percentileif): Use `percentileif` to find specific percentile values conditionally. Use `histogramif` for a complete distribution overview.
* [sumif](/apl/aggregation-function/sumif): Use `sumif` for conditional sums. Use `histogramif` when you need to understand the distribution of conditional values.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically combine filtering with histogram operations using separate commands. APL's `histogramif` consolidates this into a single aggregation, simplifying your query logic.
```sql Splunk example
| where status='200'
| timechart span=10 count by duration
```
```kusto APL equivalent
['sample-http-logs']
| summarize histogramif(req_duration_ms, 10, status == '200')
```
In ANSI SQL, you combine `WHERE` clauses with `CASE` statements and `GROUP BY` to achieve conditional histograms. APL's `histogramif` provides a more concise syntax for this pattern.
```sql SQL example
SELECT FLOOR(req_duration_ms/10)*10 as duration_bin, COUNT(*)
FROM sample_http_logs
WHERE status = '200'
GROUP BY duration_bin
```
```kusto APL equivalent
['sample-http-logs']
| summarize histogramif(req_duration_ms, 10, status == '200')
```
---
# make_list_if
Source: https://axiom.co/docs/apl/aggregation-function/make-list-if
The `make_list_if` aggregation function in APL creates a list of values from a given field, conditioned on a Boolean expression. This function is useful when you need to gather values from a column that meet specific criteria into a single array. By using `make_list_if`, you can aggregate data based on dynamic conditions, making it easier to perform detailed analysis.
This aggregation is ideal in scenarios where filtering at the aggregation level is required, such as gathering only the successful requests or collecting trace spans of a specific service in OpenTelemetry data. It’s particularly useful when analyzing logs, tracing information, or security events, where conditional aggregation is essential for understanding trends or identifying issues.
## Usage [#usage]
### Syntax [#syntax]
```kusto
summarize make_list_if(expression, condition)
```
### Parameters [#parameters]
* `expression`: The field or expression whose values will be included in the list.
* `condition`: A Boolean condition that determines which values from `expression` are included in the result.
### Returns [#returns]
The function returns an array containing all values from `expression` that meet the specified `condition`.
## Use case examples [#use-case-examples]
In this example, we will gather a list of request durations for successful HTTP requests.
**Query**
```kusto
['sample-http-logs']
| summarize make_list_if(req_duration_ms, status == '200') by id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D+%7C+summarize+make_list_if%28req_duration_ms%2C+status+%3D%3D+%27200%27%29+by+id%22%7D)
**Output**
| id | req\_duration\_ms\_list |
| --- | ----------------------- |
| 123 | \[100, 150, 200] |
| 456 | \[300, 350, 400] |
This query aggregates request durations for HTTP requests that returned a status of ‘200’ for each user ID.
Here, we will aggregate the span durations for `cartservice` where the status code indicates success.
**Query**
```kusto
['otel-demo-traces']
| summarize make_list_if(duration, status_code == '200' and ['service.name'] == 'cartservice') by trace_id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D+%7C+summarize+make_list_if%28duration%2C+status_code+%3D%3D+%27200%27+and+%5B%27service.name%27%5D+%3D%3D+%27cartservice%27%29+by+trace_id%22%7D)
**Output**
| trace\_id | duration\_list |
| --------- | --------------------- |
| abc123 | \[00:01:23, 00:01:45] |
| def456 | \[00:02:12, 00:03:15] |
This query collects span durations for successful requests to the `cartservice` by `trace_id`.
In this case, we gather a list of IP addresses from security logs where the HTTP status is `403` (Forbidden) and group them by the country of origin.
**Query**
```kusto
['sample-http-logs']
| summarize make_list_if(uri, status == '403') by ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D+%7C+summarize+make_list_if%28uri%2C+status+%3D%3D+%27403%27%29+by+%5B%27geo.country%27%5D%22%7D)
**Output**
| geo.country | uri\_list |
| ----------- | ---------------------- |
| USA | \['/login', '/admin'] |
| Canada | \['/admin', '/secure'] |
This query collects a list of URIs that resulted in a `403` error, grouped by the country where the request originated.
## List of related aggregations [#list-of-related-aggregations]
* [**make\_list**](/apl/aggregation-function/make-list): Aggregates all values into a list without any conditions. Use `make_list` when you don’t need to filter the values based on a condition.
* [**countif**](/apl/aggregation-function/countif): Counts the number of records that satisfy a specific condition. Use `countif` when you need a count of occurrences rather than a list of values.
* [**avgif**](/apl/aggregation-function/avgif): Calculates the average of values that meet a specified condition. Use `avgif` for numerical aggregations where you want a conditional average instead of a list.
## Other query languages [#other-query-languages]
In Splunk, you would typically use the `eval` and `stats` commands to create conditional lists. In APL, the `make_list_if` function serves a similar purpose by allowing you to aggregate data into a list based on a condition.
```sql Splunk example
| stats list(field) as field_list by condition
```
```kusto APL equivalent
summarize make_list_if(field, condition)
```
In ANSI SQL, conditional aggregation often involves the use of `CASE` statements combined with aggregation functions such as `ARRAY_AGG`. In APL, `make_list_if` directly applies a condition to the aggregation.
```sql SQL example
SELECT ARRAY_AGG(CASE WHEN condition THEN field END) FROM table
```
```kusto APL equivalent
summarize make_list_if(field, condition)
```
---
# make_list
Source: https://axiom.co/docs/apl/aggregation-function/make-list
The `make_list` aggregation function in Axiom Processing Language (APL) collects all values from a specified column into a dynamic array for each group of rows in a dataset. This aggregation is particularly useful when you want to consolidate multiple values from distinct rows into a single grouped result.
For example, if you have multiple log entries for a particular user, you can use `make_list` to gather all request URIs accessed by that user into a single list. You can also apply `make_list` to various contexts, such as trace aggregation, log analysis, or security monitoring, where collating related events into a compact form is needed.
Key uses of `make_list`:
* Consolidating values from multiple rows into a list per group.
* Summarizing activity (for example, list all HTTP requests by a user).
* Generating traces or timelines from distributed logs.
## Usage [#usage]
### Syntax [#syntax]
```kusto
make_list(column)
```
### Parameters [#parameters]
* `column`: The name of the column to collect into a list.
### Returns [#returns]
The `make_list` function returns a dynamic array that contains all values of the specified column for each group of rows.
## Use case examples [#use-case-examples]
In log analysis, `make_list` is useful for collecting all URIs a user has accessed in a session. This can help in identifying browsing patterns or tracking user activity.
**Query**
```kusto
['sample-http-logs']
| summarize uris=make_list(uri) by id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20uris%3Dmake_list%28uri%29%20by%20id%22%7D)
**Output**
| id | uris |
| ------- | --------------------------------- |
| user123 | \[‘/home’, ‘/profile’, ‘/cart’] |
| user456 | \[‘/search’, ‘/checkout’, ‘/pay’] |
This query collects all URIs accessed by each user, providing a compact view of user activity in the logs.
In OpenTelemetry traces, `make_list` can help in gathering the list of services involved in a trace by consolidating all service names related to a trace ID.
**Query**
```kusto
['otel-demo-traces']
| summarize services=make_list(['service.name']) by trace_id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20summarize%20services%3Dmake_list%28%5B%27service.name%27%5D%29%20by%20trace_id%22%7D)
**Output**
| trace\_id | services |
| --------- | ----------------------------------------------- |
| trace\_a | \[‘frontend’, ‘cartservice’, ‘checkoutservice’] |
| trace\_b | \[‘productcatalogservice’, ‘loadgenerator’] |
This query aggregates all service names associated with a particular trace, helping trace spans across different services.
In security logs, `make_list` is useful for collecting all IPs or cities from where a user has initiated requests, aiding in detecting anomalies or patterns.
**Query**
```kusto
['sample-http-logs']
| summarize cities=make_list(['geo.city']) by id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20cities%3Dmake_list%28%5B%27geo.city%27%5D%29%20by%20id%22%7D)
**Output**
| id | cities |
| ------- | ---------------------------- |
| user123 | \[‘New York’, ‘Los Angeles’] |
| user456 | \[‘Berlin’, ‘London’] |
This query collects the cities from which each user has made HTTP requests, useful for geographical analysis or anomaly detection.
## List of related aggregations [#list-of-related-aggregations]
* [**make\_set**](/apl/aggregation-function/make-set): Similar to `make_list`, but only unique values are collected in the set. Use `make_set` when duplicates aren’t relevant.
* [**count**](/apl/aggregation-function/count): Returns the count of rows in each group. Use this instead of `make_list` when you’re interested in row totals rather than individual values.
* [**max**](/apl/aggregation-function/max): Aggregates values by returning the maximum value from each group. Useful for numeric comparison across rows.
* [**dcount**](/apl/aggregation-function/dcount): Returns the distinct count of values for each group. Use this when you need unique value counts instead of listing them.
## Other query languages [#other-query-languages]
In Splunk SPL, the `make_list` equivalent is `values` or `mvlist`, which gathers multiple values into a multivalue field. In APL, `make_list` behaves similarly by collecting values from rows into a dynamic array.
```sql Splunk example
index=logs | stats values(uri) by user
```
```kusto APL equivalent
['sample-http-logs']
| summarize uris=make_list(uri) by id
```
In ANSI SQL, the `make_list` function is similar to `ARRAY_AGG`, which aggregates column values into an array for each group. In APL, `make_list` performs the same role, grouping the column values into a dynamic array.
```sql SQL example
SELECT ARRAY_AGG(uri) AS uris FROM sample_http_logs GROUP BY id;
```
```kusto APL equivalent
['sample-http-logs']
| summarize uris=make_list(uri) by id
```
---
# make_set_if
Source: https://axiom.co/docs/apl/aggregation-function/make-set-if
The `make_set_if` aggregation function in APL allows you to create a set of distinct values from a column based on a condition. You can use this function to aggregate values that meet specific criteria, helping you filter and reduce data to unique entries while applying a conditional filter. This is especially useful when analyzing large datasets to extract relevant, distinct information without duplicates.
You can use `make_set_if` in scenarios where you need to aggregate conditional data points, such as log analysis, tracing information, or security logs, to summarize distinct occurrences based on particular conditions.
## Usage [#usage]
### Syntax [#syntax]
```kusto
make_set_if(column, predicate, [max_size])
```
### Parameters [#parameters]
* `column`: The column from which distinct values will be aggregated.
* `predicate`: A condition that filters the values to be aggregated.
* `[max_size]`: (Optional) Specifies the maximum number of elements in the resulting set. If omitted, the default is 1048576.
### Returns [#returns]
The `make_set_if` function returns a dynamic array of distinct values from the specified column that satisfy the given condition.
## Use case examples [#use-case-examples]
In this use case, you’re analyzing HTTP logs and want to get the distinct cities from which requests originated, but only for requests that took longer than 500 ms.
**Query**
```kusto
['sample-http-logs']
| summarize make_set_if(['geo.city'], req_duration_ms > 500) by ['method']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20make_set_if%28%5B%27geo.city%27%5D%2C%20req_duration_ms%20%3E%20500%29%20by%20%5B%27method%27%5D%22%7D)
**Output**
| method | make\_set\_if\_geo.city |
| ------ | ------------------------------ |
| GET | \[‘New York’, ‘San Francisco’] |
| POST | \[‘Berlin’, ‘Tokyo’] |
This query returns the distinct cities from which requests took more than 500 ms, grouped by HTTP request method.
Here, you’re analyzing OpenTelemetry traces and want to identify the distinct services that processed spans with a duration greater than 1 second, grouped by trace ID.
**Query**
```kusto
['otel-demo-traces']
| summarize make_set_if(['service.name'], duration > 1s) by ['trace_id']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20summarize%20make_set_if%28%5B%27service.name%27%5D%2C%20duration%20%3E%201s%29%20by%20%5B%27trace_id%27%5D%22%7D)
**Output**
| trace\_id | make\_set\_if\_service.name |
| --------- | ------------------------------------- |
| abc123 | \[‘frontend’, ‘cartservice’] |
| def456 | \[‘checkoutservice’, ‘loadgenerator’] |
This query extracts distinct services that have processed spans longer than 1 second for each trace.
In security log analysis, you may want to find out which HTTP status codes were encountered for each city, but only for POST requests.
**Query**
```kusto
['sample-http-logs']
| summarize make_set_if(status, method == 'POST') by ['geo.city']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20make_set_if%28status%2C%20method%20%3D%3D%20%27POST%27%29%20by%20%5B%27geo.city%27%5D%22%7D)
**Output**
| geo.city | make\_set\_if\_status |
| -------- | --------------------- |
| Berlin | \[‘200’, ‘404’] |
| Tokyo | \[‘500’, ‘403’] |
This query identifies the distinct HTTP status codes for POST requests grouped by the originating city.
## List of related aggregations [#list-of-related-aggregations]
* [**make\_list\_if**](/apl/aggregation-function/make-list-if): Similar to `make_set_if`, but returns a list that can include duplicates instead of a distinct set.
* [**make\_set**](/apl/aggregation-function/make-set): Aggregates distinct values without a conditional filter.
* [**countif**](/apl/aggregation-function/countif): Counts rows that satisfy a specific condition, useful for when you need to count rather than aggregate distinct values.
## Other query languages [#other-query-languages]
In Splunk SPL, you may use `values` with a `where` condition to achieve similar functionality to `make_set_if`. However, in APL, the `make_set_if` function is explicitly designed to create a distinct set of values based on a conditional filter within the aggregation step itself.
```sql Splunk example
| stats values(field) by another_field where condition
```
```kusto APL equivalent
summarize make_set_if(field, condition) by another_field
```
In ANSI SQL, you would typically use `GROUP BY` in combination with conditional aggregation, such as using `CASE WHEN` inside aggregate functions. In APL, the `make_set_if` function directly aggregates distinct values conditionally without requiring a `CASE WHEN`.
```sql SQL example
SELECT DISTINCT CASE WHEN condition THEN field END
FROM table
GROUP BY another_field
```
```kusto APL equivalent
summarize make_set_if(field, condition) by another_field
```
---
# make_set
Source: https://axiom.co/docs/apl/aggregation-function/make-set
The `make_set` aggregation in APL (Axiom Processing Language) is used to collect unique values from a specific column into an array. It’s useful when you want to reduce your data by grouping it and then retrieving all unique values for each group. This aggregation is valuable for tasks such as grouping logs, traces, or events by a common attribute and retrieving the unique values of a specific field for further analysis.
You can use `make_set` when you need to collect non-repeating values across rows within a group, such as finding all the unique HTTP methods in web server logs or unique trace IDs in telemetry data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
make_set(column, [limit])
```
### Parameters [#parameters]
* `column`: The column from which unique values are aggregated.
* `limit`: (Optional) The maximum number of unique values to return. Defaults to 128 if not specified.
### Returns [#returns]
An array of unique values from the specified column.
## Use case examples [#use-case-examples]
In this use case, you want to collect all unique HTTP methods used by each user in the log data.
**Query**
```kusto
['sample-http-logs']
| summarize make_set(method) by id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D+%7C+summarize+make_set%28method%29+by+id%22%7D)
**Output**
| id | make\_set\_method |
| ------- | ----------------- |
| user123 | \['GET', 'POST'] |
| user456 | \['GET'] |
This query groups the log entries by `id` and returns all unique HTTP methods used by each user.
In this use case, you want to gather the unique service names involved in a trace.
**Query**
```kusto
['otel-demo-traces']
| summarize make_set(['service.name']) by trace_id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D+%7C+summarize+make_set%28%5B%27service.name%27%5D%29+by+trace_id%22%7D)
**Output**
| trace\_id | make\_set\_service.name |
| --------- | -------------------------------- |
| traceA | \['frontend', 'checkoutservice'] |
| traceB | \['cartservice'] |
This query groups the telemetry data by `trace_id` and collects the unique services involved in each trace.
In this use case, you want to collect all unique HTTP status codes for each country where the requests originated.
**Query**
```kusto
['sample-http-logs']
| summarize make_set(status) by ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D+%7C+summarize+make_set%28status%29+by+%5B%27geo.country%27%5D%22%7D)
**Output**
| geo.country | make\_set\_status |
| ----------- | ----------------- |
| USA | \['200', '404'] |
| UK | \['200'] |
This query collects all unique HTTP status codes returned for each country from which requests were made.
## List of related aggregations [#list-of-related-aggregations]
* [**make\_list**](/apl/aggregation-function/make-list): Similar to `make_set`, but returns all values, including duplicates, in a list. Use `make_list` if you want to preserve duplicates.
* [**count**](/apl/aggregation-function/count): Counts the number of records in each group. Use `count` when you need the total count rather than the unique values.
* [**dcount**](/apl/aggregation-function/dcount): Returns the distinct count of values in a column. Use `dcount` when you need the number of unique values, rather than an array of them.
* [**max**](/apl/aggregation-function/max): Finds the maximum value in a group. Use `max` when you are interested in the largest value rather than collecting values.
## Other query languages [#other-query-languages]
In Splunk SPL, the `values` function is similar to `make_set` in APL. The main difference is that while `values` returns all non-null values, `make_set` specifically returns only unique values and stores them in an array.
```sql Splunk example
| stats values(method) by id
```
```kusto APL equivalent
['sample-http-logs']
| summarize make_set(method) by id
```
In ANSI SQL, the `GROUP_CONCAT` or `ARRAY_AGG(DISTINCT)` functions are commonly used to aggregate unique values in a column. `make_set` in APL works similarly by aggregating distinct values from a specific column into an array, but it offers better performance for large datasets.
```sql SQL example
SELECT id, ARRAY_AGG(DISTINCT method)
FROM sample_http_logs
GROUP BY id;
```
```kusto APL equivalent
['sample-http-logs']
| summarize make_set(method) by id
```
---
# max
Source: https://axiom.co/docs/apl/aggregation-function/max
The `max` aggregation in APL allows you to find the highest value in a specific column of your dataset. This is useful when you need to identify the maximum value of numerical data, such as the longest request duration, highest sales figures, or the latest timestamp in logs. The `max` function is ideal when you are working with large datasets and need to quickly retrieve the largest value, ensuring you’re focusing on the most critical or recent data point.
## Usage [#usage]
### Syntax [#syntax]
```kusto
summarize max(ColumnName)
```
### Parameters [#parameters]
* `ColumnName`: The column or field from which you want to retrieve the maximum value. The column should contain numerical data, timespans, or dates.
### Returns [#returns]
The maximum value from the specified column.
## Use case examples [#use-case-examples]
In log analysis, you might want to find the longest request duration to diagnose performance issues.
**Query**
```kusto
['sample-http-logs']
| summarize max(req_duration_ms)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20max\(req_duration_ms\)%22%7D)
**Output**
| max\_req\_duration\_ms |
| ---------------------- |
| 5400 |
This query returns the highest request duration from the `req_duration_ms` field, which helps you identify the slowest requests.
When analyzing OpenTelemetry traces, you can find the longest span duration to determine performance bottlenecks in distributed services.
**Query**
```kusto
['otel-demo-traces']
| summarize max(duration)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20max\(duration\)%22%7D)
**Output**
| max\_duration |
| ------------- |
| 00:00:07.234 |
This query returns the longest trace span from the `duration` field, helping you pinpoint the most time-consuming operations.
In security log analysis, you may want to identify the most recent event for monitoring threats or auditing activities.
**Query**
```kusto
['sample-http-logs']
| summarize max(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20max\(_time\)%22%7D)
**Output**
| max\_time |
| ------------------- |
| 2024-09-25 12:45:01 |
This query returns the most recent timestamp from your logs, allowing you to monitor the latest security events.
## List of related aggregations [#list-of-related-aggregations]
* [**min**](/apl/aggregation-function/min): Retrieves the minimum value from a column, which is useful when you need to find the smallest or earliest value, such as the lowest request duration or first event in a log.
* [**avg**](/apl/aggregation-function/avg): Calculates the average value of a column. This function helps when you want to understand the central tendency, such as the average response time for requests.
* [**sum**](/apl/aggregation-function/sum): Sums all values in a column, making it useful when calculating totals, such as total sales or total number of requests over a period.
* [**count**](/apl/aggregation-function/count): Counts the number of records or non-null values in a column. It’s useful for finding the total number of log entries or transactions.
* [**percentile**](/apl/aggregation-function/percentile): Finds a value below which a specified percentage of data falls. This aggregation is helpful when you need to analyze performance metrics like latency at the 95th percentile.
## Other query languages [#other-query-languages]
In Splunk SPL, the `max` function works similarly, used to find the maximum value in a given field. The syntax in APL, however, requires you to specify the column to aggregate within a query and make use of APL's structured flow.
```sql Splunk example
| stats max(req_duration_ms)
```
```kusto APL equivalent
['sample-http-logs']
| summarize max(req_duration_ms)
```
In ANSI SQL, `MAX` works similarly to APL’s `max`. In SQL, you aggregate over a column using the `MAX` function in a `SELECT` statement. In APL, you achieve the same result using the `summarize` operator followed by the `max` function.
```sql SQL example
SELECT MAX(req_duration_ms) FROM sample_http_logs;
```
```kusto APL equivalent
['sample-http-logs']
| summarize max(req_duration_ms)
```
---
# maxif
Source: https://axiom.co/docs/apl/aggregation-function/maxif
# maxif aggregation in APL [#maxif-aggregation-in-apl]
## Introduction [#introduction]
The `maxif` aggregation function in APL is useful when you want to return the maximum value from a dataset based on a conditional expression. This allows you to filter the dataset dynamically and only return the maximum for rows that satisfy the given condition. It’s particularly helpful for scenarios where you want to find the highest value of a specific metric, like response time or duration, but only for a subset of the data (for example, successful responses, specific users, or requests from a particular geographic location).
You can use the `maxif` function when analyzing logs, monitoring system traces, or inspecting security-related data to get insights into the maximum value under certain conditions.
## Usage [#usage]
### Syntax [#syntax]
```kusto
summarize maxif(column, condition)
```
### Parameters [#parameters]
* `column`: The column containing the values to aggregate.
* `condition`: The condition that must be true for the values to be considered in the aggregation.
### Returns [#returns]
The maximum value from `column` for rows that meet the `condition`. If no rows match the condition, it returns `null`.
## Use case examples [#use-case-examples]
In log analysis, you might want to find the maximum request duration, but only for successful requests.
**Query**
```kusto
['sample-http-logs']
| summarize maxif(req_duration_ms, status == "200")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20maxif\(req_duration_ms,%20status%20%3D%3D%20'200'\)%22%7D)
**Output**
| max\_req\_duration |
| ------------------ |
| 1250 |
This query returns the maximum request duration (`req_duration_ms`) for HTTP requests with a `200` status.
In OpenTelemetry traces, you might want to find the longest span duration for a specific service type.
**Query**
```kusto
['otel-demo-traces']
| summarize maxif(duration, ['service.name'] == "checkoutservice" and kind == "server")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20maxif\(duration,%20%5B'service.name'%5D%20%3D%3D%20'checkoutservice'%20and%20kind%20%3D%3D%20'server'\)%22%7D)
**Output**
| max\_duration |
| ------------- |
| 2.05s |
This query returns the maximum span duration (`duration`) for server spans in the `checkoutservice`.
For security logs, you might want to identify the longest request duration for any requests originating from a specific country, such as the United States.
**Query**
```kusto
['sample-http-logs']
| summarize maxif(req_duration_ms, ['geo.country'] == "United States")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20maxif\(req_duration_ms,%20%5B'geo.country'%5D%20%3D%3D%20'United%20States'\)%22%7D)
**Output**
| max\_req\_duration |
| ------------------ |
| 980 |
This query returns the maximum request duration for requests coming from the United States (`geo.country`).
## List of related aggregations [#list-of-related-aggregations]
* [**minif**](/apl/aggregation-function/minif): Returns the minimum value from a column for rows that satisfy a condition. Use `minif` when you’re interested in the lowest value under specific conditions.
* [**max**](/apl/aggregation-function/max): Returns the maximum value from a column without filtering. Use `max` when you want the highest value across the entire dataset without conditions.
* [**sumif**](/apl/aggregation-function/sumif): Returns the sum of values for rows that satisfy a condition. Use `sumif` when you want the total value of a column under specific conditions.
* [**avgif**](/apl/aggregation-function/avgif): Returns the average of values for rows that satisfy a condition. Use `avgif` when you want to calculate the mean value based on a filter.
* [**countif**](/apl/aggregation-function/countif): Returns the count of rows that satisfy a condition. Use `countif` when you want to count occurrences that meet certain criteria.
## Other query languages [#other-query-languages]
In Splunk SPL, you might use the `stats max()` function alongside a conditional filtering step to achieve a similar result. APL’s `maxif` function combines both operations into one, streamlining the query.
```splunk
| stats max(req_duration_ms) as max_duration where status="200"
```
```kusto
['sample-http-logs']
| summarize maxif(req_duration_ms, status == "200")
```
In ANSI SQL, you typically use the `MAX` function in conjunction with a `WHERE` clause. APL’s `maxif` allows you to perform the same operation with a single aggregation function.
```sql
SELECT MAX(req_duration_ms)
FROM logs
WHERE status = '200';
```
```kusto
['sample-http-logs']
| summarize maxif(req_duration_ms, status == "200")
```
---
# min
Source: https://axiom.co/docs/apl/aggregation-function/min
The `min` aggregation function in APL returns the minimum value from a set of input values. You can use this function to identify the smallest numeric or comparable value in a column of data. This is useful when you want to find the quickest response time, the lowest transaction amount, or the earliest date in log data. It’s ideal for analyzing performance metrics, filtering out abnormal low points in your data, or discovering outliers.
## Usage [#usage]
### Syntax [#syntax]
```kusto
summarize min(Expression)
```
### Parameters [#parameters]
* `Expression`: The expression from which to calculate the minimum value. Typically, this is a numeric or date/time field.
### Returns [#returns]
The function returns the smallest value found in the specified column or expression.
## Use case examples [#use-case-examples]
In this use case, you analyze HTTP logs to find the minimum request duration for each unique user.
**Query**
```kusto
['sample-http-logs']
| summarize min(req_duration_ms) by id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20min\(req_duration_ms\)%20by%20id%22%7D)
**Output**
| id | min\_req\_duration\_ms |
| --------- | ---------------------- |
| user\_123 | 32 |
| user\_456 | 45 |
This query returns the minimum request duration for each user, helping you identify the fastest responses.
Here, you analyze OpenTelemetry trace data to find the minimum span duration per service.
**Query**
```kusto
['otel-demo-traces']
| summarize min(duration) by ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20min\(duration\)%20by%20%5B'service.name'%5D%22%7D)
**Output**
| service.name | min\_duration |
| --------------- | ------------- |
| frontend | 2ms |
| checkoutservice | 5ms |
This query returns the minimum span duration for each service in the trace logs.
In this example, you analyze security logs to find the minimum request duration for each HTTP status code.
**Query**
```kusto
['sample-http-logs']
| summarize min(req_duration_ms) by status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20min\(req_duration_ms\)%20by%20status%22%7D)
**Output**
| status | min\_req\_duration\_ms |
| ------ | ---------------------- |
| 200 | 10 |
| 404 | 40 |
This query returns the minimum request duration for each HTTP status code, helping you identify if certain statuses are associated with faster or slower response times.
## List of related aggregations [#list-of-related-aggregations]
* [**max**](/apl/aggregation-function/max): Returns the maximum value from a set of values. Use `max` when you need to find the highest value instead of the lowest.
* [**avg**](/apl/aggregation-function/avg): Calculates the average of a set of values. Use `avg` to find the mean value instead of the minimum.
* [**count**](/apl/aggregation-function/count): Counts the number of records or distinct values. Use `count` when you need to know how many records or unique values exist, rather than calculating the minimum.
* [**sum**](/apl/aggregation-function/sum): Adds all values together. Use `sum` when you need the total of a set of values rather than the minimum.
* [**percentile**](/apl/aggregation-function/percentile): Returns the value at a specified percentile. Use `percentile` if you need a value that falls at a certain point in the distribution of your data, rather than the minimum.
## Other query languages [#other-query-languages]
In Splunk, the `min` function works similarly to APL's `min` aggregation, allowing you to find the minimum value in a field across your dataset. The main difference is in the query structure and syntax between the two.
```sql Splunk example
| stats min(duration) by id
```
```kusto APL equivalent
['sample-http-logs']
| summarize min(req_duration_ms) by id
```
In ANSI SQL, the `MIN` function works almost identically to the APL `min` aggregation. You use it to return the smallest value in a column of data, grouped by one or more fields.
```sql SQL example
SELECT MIN(duration), id FROM sample_http_logs GROUP BY id;
```
```kusto APL equivalent
['sample-http-logs']
| summarize min(req_duration_ms) by id
```
---
# minif
Source: https://axiom.co/docs/apl/aggregation-function/minif
## Introduction [#introduction]
The `minif` aggregation in Axiom Processing Language (APL) allows you to calculate the minimum value of a numeric expression, but only for records that meet a specific condition. This aggregation is useful when you want to find the smallest value in a subset of data that satisfies a given predicate. For example, you can use `minif` to find the shortest request duration for successful HTTP requests, or the minimum span duration for a specific service in your OpenTelemetry traces.
The `minif` aggregation is especially useful in scenarios where you need conditional aggregations, such as log analysis, monitoring distributed systems, or examining security-related events.
## Usage [#usage]
### Syntax [#syntax]
```kusto
summarize minif(Expression, Predicate)
```
### Parameters [#parameters]
| Parameter | Description |
| ------------ | ------------------------------------------------------------ |
| `Expression` | The numeric expression whose minimum value you want to find. |
| `Predicate` | The condition that determines which records to include. |
### Returns [#returns]
The `minif` aggregation returns the minimum value of the specified `Expression` for the records that satisfy the `Predicate`.
## Use case examples [#use-case-examples]
In log analysis, you might want to find the minimum request duration for successful HTTP requests.
**Query**
```kusto
['sample-http-logs']
| summarize minif(req_duration_ms, status == '200') by ['geo.city']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20minif\(req_duration_ms,%20status%20%3D%3D%20'200'\)%20by%20%5B'geo.city'%5D%22%7D)
**Output**
| geo.city | min\_duration |
| --------- | ------------- |
| San Diego | 120 |
| New York | 95 |
This query finds the minimum request duration for HTTP requests with a `200` status code, grouped by city.
For distributed tracing, you can use `minif` to find the minimum span duration for a specific service.
**Query**
```kusto
['otel-demo-traces']
| summarize minif(duration, ['service.name'] == 'frontend') by trace_id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20minif\(duration,%20%5B'service.name'%5D%20%3D%3D%20'frontend'\)%20by%20trace_id%22%7D)
**Output**
| trace\_id | min\_duration |
| --------- | ------------- |
| abc123 | 50ms |
| def456 | 40ms |
This query returns the minimum span duration for traces from the `frontend` service, grouped by `trace_id`.
In security logs, you can use `minif` to find the minimum request duration for HTTP requests from a specific country.
**Query**
```kusto
['sample-http-logs']
| summarize minif(req_duration_ms, ['geo.country'] == 'US') by status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20minif\(req_duration_ms,%20%5B'geo.country'%5D%20%3D%3D%20'US'\)%20by%20status%22%7D)
**Output**
| status | min\_duration |
| ------ | ------------- |
| 200 | 95 |
| 404 | 120 |
This query returns the minimum request duration for HTTP requests originating from the United States, grouped by HTTP status code.
## List of related aggregations [#list-of-related-aggregations]
* [**maxif**](/apl/aggregation-function/maxif): Finds the maximum value of an expression that satisfies a condition. Use `maxif` when you need the maximum value under a condition, rather than the minimum.
* [**avgif**](/apl/aggregation-function/avgif): Calculates the average value of an expression that meets a specified condition. Useful when you want an average instead of a minimum.
* [**countif**](/apl/aggregation-function/countif): Counts the number of records that satisfy a given condition. Use this for counting records rather than calculating a minimum.
* [**sumif**](/apl/aggregation-function/sumif): Sums the values of an expression for records that meet a condition. Helpful when you’re interested in the total rather than the minimum.
## Other query languages [#other-query-languages]
In Splunk, you might use the `min` function in combination with `where` to filter results. In APL, the `minif` function combines both the filtering condition and the minimum calculation into one step.
```sql Splunk example
| stats min(req_duration_ms) as min_duration where status="200"
```
```kusto APL equivalent
['sample-http-logs']
| summarize minif(req_duration_ms, status == "200") by id
```
In ANSI SQL, you would typically use a `CASE` statement with `MIN` to apply conditional logic for aggregation. In APL, the `minif` function simplifies this by combining both the condition and the aggregation.
```sql SQL example
SELECT MIN(CASE WHEN status = '200' THEN req_duration_ms ELSE NULL END) as min_duration
FROM sample_http_logs
GROUP BY id;
```
```kusto APL equivalent
['sample-http-logs']
| summarize minif(req_duration_ms, status == "200") by id
```
---
# percentile
Source: https://axiom.co/docs/apl/aggregation-function/percentile
The `percentile` aggregation function in Axiom Processing Language (APL) allows you to calculate the value below which a given percentage of data points fall. It’s particularly useful when you need to analyze distributions and want to summarize the data using specific thresholds, such as the 90th or 95th percentile. This function can be valuable in performance analysis, trend detection, or identifying outliers across large datasets.
You can apply the `percentile` function to various use cases, such as analyzing log data for request durations, OpenTelemetry traces for service latencies, or security logs to assess risk patterns.
The `percentile` aggregation in APL is a statistical aggregation that returns estimated results. The estimation comes with the benefit of speed at the expense of accuracy. This means that `percentile` is fast and light on resources even on a large or high-cardinality dataset, but it doesn’t provide precise results.
## Usage [#usage]
### Syntax [#syntax]
```kusto
percentile(column, percentile)
```
### Parameters [#parameters]
* **column**: The name of the column to calculate the percentile on. This must be a numeric field.
* **percentile**: The target percentile value (between 0 and 100).
### Returns [#returns]
The function returns the value from the specified column that corresponds to the given percentile.
## Use case examples [#use-case-examples]
In log analysis, you can use the `percentile` function to identify the 95th percentile of request durations, which gives you an idea of the tail-end latencies of requests in your system.
**Query**
```kusto
['sample-http-logs']
| summarize percentile(req_duration_ms, 95)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20percentile%28req_duration_ms%2C%2095%29%22%7D)
**Output**
| percentile\_req\_duration\_ms |
| ----------------------------- |
| 1200 |
This query calculates the 95th percentile of request durations, showing that 95% of requests take less than or equal to 1200ms.
For OpenTelemetry traces, you can use the `percentile` function to identify the 90th percentile of span durations for specific services, which helps to understand the performance of different services.
**Query**
```kusto
['otel-demo-traces']
| where ['service.name'] == 'checkoutservice'
| summarize percentile(duration, 90)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20where%20%5B%27service.name%27%5D%20%3D%3D%20%27checkoutservice%27%20%7C%20summarize%20percentile%28duration%2C%2090%29%22%7D)
**Output**
| percentile\_duration |
| -------------------- |
| 300ms |
This query calculates the 90th percentile of span durations for the `checkoutservice`, helping to assess high-latency spans.
In security logs, you can use the `percentile` function to calculate the 99th percentile of response times for a specific set of status codes, helping you focus on outliers.
**Query**
```kusto
['sample-http-logs']
| where status == '500'
| summarize percentile(req_duration_ms, 99)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20where%20status%20%3D%3D%20%27500%27%20%7C%20summarize%20percentile%28req_duration_ms%2C%2099%29%22%7D)
**Output**
| percentile\_req\_duration\_ms |
| ----------------------------- |
| 2500 |
This query identifies that 99% of requests resulting in HTTP 500 errors take less than or equal to 2500ms.
## List of related aggregations [#list-of-related-aggregations]
* [**avg**](/apl/aggregation-function/avg): Use `avg` to calculate the average of a column, which gives you the central tendency of your data. In contrast, `percentile` provides more insight into the distribution and tail values.
* [**min**](/apl/aggregation-function/min): The `min` function returns the smallest value in a column. Use this when you need the absolute lowest value instead of a specific percentile.
* [**max**](/apl/aggregation-function/max): The `max` function returns the highest value in a column. It’s useful for finding the upper bound, while `percentile` allows you to focus on a specific point in the data distribution.
* [**stdev**](/apl/aggregation-function/stdev): `stdev` calculates the standard deviation of a column, which helps measure data variability. While `stdev` provides insight into overall data spread, `percentile` focuses on specific distribution points.
## Other query languages [#other-query-languages]
In Splunk SPL, the `percentile` function is referred to as `perc` or `percentile`. APL's `percentile` function works similarly, but the syntax is different. The main difference is that APL requires you to explicitly define the column on which you want to apply the percentile and the target percentile value.
```sql Splunk example
| stats perc95(req_duration_ms)
```
```kusto APL equivalent
['sample-http-logs']
| summarize percentile(req_duration_ms, 95)
```
In ANSI SQL, you might use the `PERCENTILE_CONT` or `PERCENTILE_DISC` functions to compute percentiles. In APL, the `percentile` function provides a simpler syntax while offering similar functionality.
```sql SQL example
SELECT PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY req_duration_ms) FROM sample_http_logs;
```
```kusto APL equivalent
['sample-http-logs']
| summarize percentile(req_duration_ms, 95)
```
---
# percentileif
Source: https://axiom.co/docs/apl/aggregation-function/percentileif
The `percentileif` aggregation function calculates the percentile of a numeric column, conditional on a specified boolean predicate. This function is useful for filtering data dynamically and determining percentile values based only on relevant subsets of data.
You can use `percentileif` to gain insights in various scenarios, such as:
* Identifying response time percentiles for HTTP requests from specific regions.
* Calculating percentiles of span durations for specific service types in OpenTelemetry traces.
* Analyzing security events by percentile within defined risk categories.
## Usage [#usage]
### Syntax [#syntax]
```kusto
summarize percentileif(Field, Percentile, Predicate)
```
### Parameters [#parameters]
| Parameter | Description |
| ------------ | ---------------------------------------------------------------------- |
| `Field` | The numeric field from which to calculate the percentile. |
| `Percentile` | A number between 0 and 100 that specifies the percentile to calculate. |
| `Predicate` | A Boolean expression that filters rows to include in the calculation. |
### Returns [#returns]
The function returns a single numeric value representing the specified percentile of the `Field` for rows where the `Predicate` evaluates to `true`.
## Use case examples [#use-case-examples]
You can use `percentileif` to analyze request durations for specific HTTP methods.
**Query**
```kusto
['sample-http-logs']
| summarize post_p90 = percentileif(req_duration_ms, 90, method == "POST"), get_p90 = percentileif(req_duration_ms, 90, method == "GET") by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20post_p90%20%3D%20percentileif\(req_duration_ms%2C%2090%2C%20method%20%3D%3D%20'POST'\)%2C%20get_p90%20%3D%20percentileif\(req_duration_ms%2C%2090%2C%20method%20%3D%3D%20'GET'\)%20by%20bin_auto\(_time\)%22%7D)
**Output**
| post\_p90 | get\_p90 |
| --------- | -------- |
| 1.691 ms | 1.453 ms |
This query calculates the 90th percentile of request durations for HTTP POST and GET methods.
You can use `percentileif` to measure span durations for specific services and operation kinds.
**Query**
```kusto
['otel-demo-traces']
| summarize percentileif(duration, 95, ['service.name'] == 'frontend' and kind == 'server')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20summarize%20percentileif%28duration%2C%2095%2C%20%5B%27service.name%27%5D%20%3D%3D%20%27frontend%27%20and%20kind%20%3D%3D%20%27server%27%29%22%7D)
**Output**
| Percentile95 |
| ------------ |
| 1.2s |
This query calculates the 95th percentile of span durations for server spans in the `frontend` service.
You can use `percentileif` to calculate response time percentiles for specific HTTP status codes.
**Query**
```kusto
['sample-http-logs']
| summarize percentileif(req_duration_ms, 75, status == '404')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20percentileif%28req_duration_ms%2C%2075%2C%20status%20%3D%3D%20%27404%27%29%22%7D)
**Output**
| Percentile75 |
| ------------ |
| 350 |
This query calculates the 75th percentile of request durations for HTTP 404 errors.
## List of related aggregations [#list-of-related-aggregations]
* [percentile](/apl/aggregation-function/percentile): Calculates the percentile for all rows without any filtering. Use `percentile` when you don’t need conditional filtering.
* [avgif](/apl/aggregation-function/avgif): Calculates the average of a numeric column based on a condition. Use `avgif` for mean calculations instead of percentiles.
* [minif](/apl/aggregation-function/minif): Returns the minimum value of a numeric column where a condition is true. Use `minif` for identifying the lowest values within subsets.
* [maxif](/apl/aggregation-function/maxif): Returns the maximum value of a numeric column where a condition is true. Use `maxif` for identifying the highest values within subsets.
* [sumif](/apl/aggregation-function/sumif): Sums a numeric column based on a condition. Use `sumif` for conditional total calculations.
## Other query languages [#other-query-languages]
The `percentileif` aggregation in APL works similarly to `percentile` combined with conditional filtering in SPL. However, APL integrates the condition directly into the aggregation for simplicity.
```sql Splunk example
stats perc95(req_duration_ms) as p95 where geo.country="US"
```
```kusto APL equivalent
['sample-http-logs']
| summarize percentileif(req_duration_ms, 95, geo.country == 'US')
```
In SQL, you typically calculate percentiles using window functions or aggregate functions combined with a `WHERE` clause. APL simplifies this by embedding the condition directly in the `percentileif` aggregation.
```sql SQL example
SELECT PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY req_duration_ms)
FROM sample_http_logs
WHERE geo_country = 'US'
```
```kusto APL equivalent
['sample-http-logs']
| summarize percentileif(req_duration_ms, 95, geo.country == 'US')
```
---
# percentiles_array
Source: https://axiom.co/docs/apl/aggregation-function/percentiles-array
Use the `percentiles_array` aggregation function in APL to calculate multiple percentile values over a numeric expression in one pass. This function is useful when you want to understand the distribution of numeric data points, such as response times or durations, by summarizing them at several key percentiles like the 25th, 50th, and 95th.
You can use `percentiles_array` to:
* Analyze latency or duration metrics across requests or operations.
* Identify performance outliers.
* Visualize percentile distributions in dashboards.
## Usage [#usage]
### Syntax [#syntax]
```kusto
percentiles_array(Field, Percentile1, Percentile2, ...)
```
### Parameters [#parameters]
* `Field` is the name of the field for which you want to compute percentile values.
* `Percentile1`, `Percentile2`, `...` are numeric percentile values between 0 and 100.
### Returns [#returns]
An array of numbers where each element is the value at the corresponding percentile.
## Use case examples [#use-case-examples]
Use `percentiles_array` to understand the spread of request durations per HTTP method, highlighting performance variability.
**Query**
```kusto
['sample-http-logs']
| summarize percentiles_array(req_duration_ms, 25, 50, 95) by method
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20percentiles_array\(req_duration_ms%2C%2025%2C%2050%2C%2095\)%20by%20method%22%7D)
**Output**
| method | P25 | P50 | P95 |
| ------ | --------- | --------- | -------- |
| GET | 0.3981 ms | 0.7352 ms | 1.981 ms |
| POST | 0.3261 ms | 0.7162 ms | 2.341 ms |
| PUT | 0.3324 ms | 0.7772 ms | 1.341 ms |
| DELETE | 0.2332 ms | 0.4652 ms | 1.121 ms |
This query calculates the 25th, 50th, and 95th percentiles of request durations for each HTTP method. It helps identify performance differences between different methods.
Use `percentiles_array` to analyze the distribution of span durations by service to detect potential bottlenecks.
**Query**
```kusto
['otel-demo-traces']
| summarize percentiles_array(duration, 50, 90, 99) by ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20percentiles_array\(duration%2C%2050%2C%2090%2C%2099\)%20by%20%5B'service.name'%5D%22%7D)
**Output**
| service.name | P50 | P90 | P99 | P99 |
| --------------------- | -------- | --------- | --------- | --------- |
| recommendationservice | 1.96 ms | 2.965 ms | 3.477 ms | 3.477 ms |
| frontendproxy | 3.767 ms | 13.101 ms | 39.735 ms | 39.735 ms |
| shippingservice | 2.119 ms | 3.085 ms | 9.739 ms | 9.739 ms |
| checkoutservice | 1.454 ms | 12.342 ms | 29.542 ms | 29.542 ms |
This query shows latency patterns across services by computing the median, 90th, and 99th percentile of span durations.
Use `percentiles_array` to assess outlier response times per status code, which can reveal abnormal activity or service issues.
**Query**
```kusto
['sample-http-logs']
| summarize percentiles_array(req_duration_ms, 50, 95, 99) by status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20percentiles_array\(req_duration_ms%2C%2050%2C%2095%2C%2099\)%20by%20status%22%7D)
**Output**
| status | P50 | P95 | P99 |
| ------ | --------- | -------- | -------- |
| 200 | 0.7352 ms | 1.981 ms | 2.612 ms |
| 201 | 0.7856 ms | 1.356 ms | 2.234 ms |
| 301 | 0.8956 ms | 1.547 ms | 2.546 ms |
| 500 | 0.6587 ms | 1.856 ms | 2.856 ms |
This query helps identify whether requests resulting in errors (like 500) are significantly slower than successful ones.
## List of related functions [#list-of-related-functions]
* [avg](/apl/aggregation-function/avg): Returns the average value. Use it when a single central tendency is sufficient.
* [percentile](/apl/aggregation-function/percentile): Returns a single percentile value. Use it when you only need one percentile.
* [percentile\_if](/apl/aggregation-function/percentileif): Returns a single percentile value for the records that satisfy a condition.
* [percentiles\_arrayif](/apl/aggregation-function/percentiles-arrayif): Returns an array of percentile values for the records that satisfy a condition.
* [sum](/apl/aggregation-function/sum): Returns the sum of a numeric column.
## Other query languages [#other-query-languages]
In Splunk, you typically calculate percentiles one at a time using the `perc` function. To get multiple percentiles, you repeat the function with different percentile values. In APL, `percentiles_array` lets you specify multiple percentiles in a single function call and returns them as an array.
```sql Splunk example
... | stats perc95(duration), perc50(duration), perc25(duration) by service
```
```kusto APL equivalent
['otel-demo-traces']
| summarize percentiles_array(duration, 25, 50, 95) by ['service.name']
```
Standard SQL typically lacks a built-in function to calculate multiple percentiles in a single operation. Instead, you use `PERCENTILE_CONT` or `PERCENTILE_DISC` with `WITHIN GROUP`, repeated for each desired percentile. In APL, `percentiles_array` simplifies this with a single function call that returns all requested percentiles as an array.
```sql SQL example
SELECT
service,
PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY duration) AS p25,
PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY duration) AS p50,
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY duration) AS p95
FROM traces
GROUP BY service
```
```kusto APL equivalent
['otel-demo-traces']
| summarize percentiles_array(duration, 25, 50, 95) by ['service.name']
```
---
# percentiles_arrayif
Source: https://axiom.co/docs/apl/aggregation-function/percentiles-arrayif
Use `percentiles_arrayif` to calculate approximate percentile values for a numeric expression when a certain condition evaluates to true. This function is useful when you want an array of percentiles instead of a single percentile. You can use it to understand data distributions in scenarios such as request durations, event processing times, or security alert severities, while filtering on specific criteria.
## Syntax [#syntax]
```kusto
percentiles_arrayif(Field, Array, Condition)
```
## Parameters [#parameters]
* `Field` is the name of the field for which you want to compute percentile values.
* `Array` is a dynamic array of one or more numeric percentile values (between 0 and 100).
* `Condition` is a Boolean expression that indicates which records to include in the calculation.
## Returns [#returns]
The function returns an array of percentile values for the records that satisfy the condition. The position of each returned percentile in the array matches the order in which it appears in the function call.
## Use case examples [#use-case-examples]
You can use `percentiles_arrayif` to analyze request durations in HTTP logs while filtering for specific criteria, such as certain HTTP statuses or geographic locations.
**Query**
```kusto
['sample-http-logs']
| summarize percentiles_arrayif(req_duration_ms, dynamic([50, 90, 95, 99]), status == '200') by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20percentiles_arrayif\(req_duration_ms%2C%20dynamic\(%5B50%2C%2090%2C%2095%2C%2099%5D\)%2C%20status%20%3D%3D%20'200'\)%20by%20bin_auto\(_time\)%22%7D)
**Output**
| percentiles\_req\_duration\_ms |
| ------------------------------ |
| 0.7352 ms |
| 1.691 ms |
| 1.981 ms |
| 2.612 ms |
This query filters records to those with a status of 200 and returns the percentile values for the request durations.
Use `percentiles_arrayif` to track performance of spans and filter on a specific service operation. This lets you quickly gauge how request durations differ for incoming traffic.
**Query**
```kusto
['otel-demo-traces']
| summarize percentiles_arrayif(duration, dynamic([50, 90, 99, 99]), ['method'] == "POST") by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20percentiles_arrayif\(duration%2C%20dynamic\(%5B50%2C%2090%2C%2099%2C%2099%5D\)%2C%20%5B'method'%5D%20%3D%3D%20'POST'\)%20by%20bin_auto\(_time\)%22%7D)
**Output**
| percentiles\_duration |
| --------------------- |
| 5.166 ms |
| 25.18 ms |
| 71.996 ms |
This query returns the percentile values for span durations for requests with the POST method.
You can focus on server issues by filtering for specific status codes, then see how request durations are distributed in those scenarios.
**Query**
```kusto
['sample-http-logs']
| summarize percentiles_arrayif(req_duration_ms, dynamic([50, 90, 95, 99]), status startswith '5') by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20percentiles_arrayif\(req_duration_ms%2C%20dynamic\(%5B50%2C%2090%2C%2095%2C%2099%5D\)%2C%20status%20startswith%20'5'\)%20by%20bin_auto\(_time\)%22%7D)
**Output**
| percentiles\_req\_duration\_ms |
| ------------------------------ |
| 0.7352 ms |
| 1.691 ms |
| 1.981 ms |
| 2.612 ms |
This query calculates percentile values for request durations that return a status code starting with 5 which means server error.
## List of related functions [#list-of-related-functions]
* [avg](/apl/aggregation-function/avg): Returns the average of a numeric column.
* [percentile](/apl/aggregation-function/percentile): Returns a single percentile value.
* [percentile\_if](/apl/aggregation-function/percentileif): Returns a single percentile value for the records that satisfy a condition.
* [percentiles\_array](/apl/aggregation-function/percentiles-array): Returns an array of percentile values for all rows.
* [sum](/apl/aggregation-function/sum): Returns the sum of a numeric column.
## Other query languages [#other-query-languages]
In Splunk SPL, you often use statistical functions such as `perc` or `percN()` to compute percentile estimates. In APL, you use `percentiles_arrayif` and provide a predicate to define which records to include in the computation.
```sql Splunk example
index=main sourcetype=access_combined
| stats perc90(req_duration_ms) AS p90, perc99(req_duration_ms) AS p99
```
```kusto APL equivalent
['sample-http-logs']
| summarize Dist=percentiles_arrayif(req_duration_ms, dynamic([90, 99]), status == '200')
```
In ANSI SQL, you often use window functions like `PERCENTILE_DISC` or `PERCENTILE_CONT` or write multiple `CASE` expressions for conditional aggregation. In APL, you can achieve similar functionality with `percentiles_arrayif` by passing the numeric field and condition to the function.
```sql SQL example
SELECT
PERCENTILE_DISC(0.90) WITHIN GROUP (ORDER BY req_duration_ms) AS p90,
PERCENTILE_DISC(0.99) WITHIN GROUP (ORDER BY req_duration_ms) AS p99
FROM sample_http_logs
WHERE status = '200';
```
```kusto APL equivalent
['sample-http-logs']
| summarize Dist=percentiles_arrayif(req_duration_ms, dynamic([90, 99]), status == '200')
```
# Usage [#usage]
---
# phrases
Source: https://axiom.co/docs/apl/aggregation-function/phrases
The `phrases` aggregation extracts and counts common phrases or word sequences from text fields across a dataset. It analyzes text content to identify frequently occurring phrases, helping you discover patterns, trends, and common topics in your data.
You can use this aggregation to identify common user queries, discover trending topics, extract key phrases from logs, or analyze conversation patterns in AI applications.
## Usage [#usage]
### Syntax [#syntax]
```kusto
summarize phrases(column, max_phrases)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------------- | ------ | -------- | -------------------------------------------------------------- |
| `column` | string | Yes | The column containing text data from which to extract phrases. |
| `max_phrases` | long | Yes | The maximum number of top phrases to return. |
### Returns [#returns]
Returns a dynamic array containing the most common phrases found in the specified column, ordered by frequency.
## Example [#example]
Extract common phrases from GenAI conversation text to identify trending topics and patterns.
**Query**
```kusto
['otel-demo-genai']
| extend conversation_text = genai_concat_contents(['attributes.gen_ai.input.messages'], ' | ')
| summarize common_phrases = phrases(conversation_text, 20)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20conversation_text%20%3D%20genai_concat_contents\(%5B%27attributes.gen_ai.input.messages%27%5D%2C%20%27%20%7C%20%27\)%20%7C%20summarize%20common_phrases%20%3D%20phrases\(conversation_text%2C%2020\)%22%7D)
**Output**
| Count | common\_phrases |
| ----- | ---------------------------- |
| 3 | first query |
| 3 | cover related future queries |
| 3 | use title case |
This query identifies the most common phrases in GenAI conversations, helping you discover trending topics and user needs.
## List of related functions [#list-of-related-functions]
* [make\_list](/apl/aggregation-function/make-list): Creates an array of all values. Use this when you need all occurrences rather than common phrases.
* [make\_set](/apl/aggregation-function/make-set): Creates an array of unique values. Use this for distinct values without frequency analysis.
* [topk](/apl/aggregation-function/topk): Returns top K values by a specific aggregation. Use this for numerical top values rather than phrase extraction.
* [count](/apl/aggregation-function/count): Counts occurrences. Combine with group by for manual phrase counting if you need more control.
* [dcount](/apl/aggregation-function/dcount): Counts distinct values. Use this to understand the variety of phrases before extracting top ones.
## Other query languages [#other-query-languages]
In Splunk SPL, there’s no built-in phrases function, but you might use the `rare` or `top` commands on tokenized text.
```sql Splunk example
| rex field=message "(?\w+)"
| top words
```
```kusto APL equivalent
['sample-http-logs']
| summarize phrases(uri, 10)
```
In ANSI SQL, you would need complex string manipulation and grouping to extract common phrases.
```sql SQL example
SELECT
phrase,
COUNT(*) as frequency
FROM (
SELECT UNNEST(SPLIT(message, ' ')) as phrase
FROM logs
)
GROUP BY phrase
ORDER BY frequency DESC
LIMIT 10
```
```kusto APL equivalent
['sample-http-logs']
| summarize phrases(uri, 10)
```
---
# rate
Source: https://axiom.co/docs/apl/aggregation-function/rate
The `rate` aggregation function in APL (Axiom Processing Language) helps you calculate the rate of change over a specific time interval. This is especially useful for scenarios where you need to monitor how frequently an event occurs or how a value changes over time. For example, you can use the `rate` function to track request rates in web logs or changes in metrics like CPU usage or memory consumption.
The `rate` function is useful for analyzing trends in time series data and identifying unusual spikes or drops in activity. It can help you understand patterns in logs, metrics, and traces over specific intervals, such as per minute, per second, or per hour.
## Usage [#usage]
### Syntax [#syntax]
```kusto
rate(field)
```
### Parameters [#parameters]
* `field`: The numeric field for which you want to calculate the rate.
### Returns [#returns]
Returns the rate of change or occurrence of the specified `field` over the time interval specified in the query.
Specify the time interval in the query in the following way:
* `| summarize rate(field)` calculates the rate value of the field over the entire query window.
* `| summarize rate(field) by bin(_time, 1h)` calculates the rate value of the field over a one-hour time window.
* `| summarize rate(field) by bin_auto(_time)` calculates the rate value of the field bucketed by an automatic time window computed by `bin_auto()`.
Use two `summarize` statements to visualize the average rate over one minute per hour. For example:
```kusto
['sample-http-logs']
| summarize respBodyRate = rate(resp_body_size_bytes) by bin(_time, 1m)
| summarize avg(respBodyRate) by bin(_time, 1h)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20respBodyRate%20%3D%20rate\(resp_body_size_bytes\)%20by%20bin\(_time%2C%201m\)%20%7C%20summarize%20avg\(respBodyRate\)%20by%20bin\(_time%2C%201h\)%22%2C%20%22queryOptions%22%3A%7B%22quickRange%22%3A%226h%22%7D%7D)
## Use case examples [#use-case-examples]
In this example, the `rate` aggregation calculates the rate of HTTP response sizes per second.
**Query**
```kusto
['sample-http-logs']
| summarize rate(resp_body_size_bytes) by bin(_time, 1s)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20rate\(resp_body_size_bytes\)%20by%20bin\(_time%2C%201s\)%22%7D)
**Output**
| rate | \_time |
| ------ | ------------------- |
| 854 kB | 2024-01-01 12:00:00 |
| 635 kB | 2024-01-01 12:00:01 |
This query calculates the rate of HTTP response sizes per second.
This example calculates the rate of span duration per second.
**Query**
```kusto
['otel-demo-traces']
| summarize rate(toint(duration)) by bin(_time, 1s)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20rate\(toint\(duration\)\)%20by%20bin\(_time%2C%201s\)%22%7D)
**Output**
| rate | \_time |
| ---------- | ------------------- |
| 26,393,768 | 2024-01-01 12:00:00 |
| 19,303,456 | 2024-01-01 12:00:01 |
This query calculates the rate of span duration per second.
In this example, the `rate` aggregation calculates the rate of HTTP request duration per second which can be useful to detect an increate in malicious requests.
**Query**
```kusto
['sample-http-logs']
| summarize rate(req_duration_ms) by bin(_time, 1s)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20rate\(req_duration_ms\)%20by%20bin\(_time%2C%201s\)%22%7D)
**Output**
| rate | \_time |
| ---------- | ------------------- |
| 240.668 ms | 2024-01-01 12:00:00 |
| 264.17 ms | 2024-01-01 12:00:01 |
This query calculates the rate of HTTP request duration per second.
## List of related aggregations [#list-of-related-aggregations]
* [**count**](/apl/aggregation-function/count): Returns the total number of records. Use `count` when you want an absolute total instead of a rate over time.
* [**sum**](/apl/aggregation-function/sum): Returns the sum of values in a field. Use `sum` when you want to aggregate the total value, not its rate of change.
* [**avg**](/apl/aggregation-function/avg): Returns the average value of a field. Use `avg` when you want to know the mean value rather than how it changes over time.
* [**max**](/apl/aggregation-function/max): Returns the maximum value of a field. Use `max` when you need to find the peak value instead of how often or quickly something occurs.
* [**min**](/apl/aggregation-function/min): Returns the minimum value of a field. Use `min` when you’re looking for the lowest value rather than a rate.
## Other query languages [#other-query-languages]
In Splunk SPL, the equivalent of the `rate` function can be achieved using the `timechart` command with a `per_second` option or by calculating the difference between successive values over time. In APL, the `rate` function simplifies this process by directly calculating the rate over a specified time interval.
```splunk Splunk example
| timechart per_second count by resp_body_size_bytes
```
```kusto APL equivalent
['sample-http-logs']
| summarize rate(resp_body_size_bytes) by bin(_time, 1s)
```
In ANSI SQL, calculating rates typically involves using window functions like `LAG` or `LEAD` to calculate the difference between successive rows in a time series. In APL, the `rate` function abstracts this complexity by allowing you to directly compute the rate over time without needing window functions.
```sql SQL example
SELECT resp_body_size_bytes, COUNT(*) / TIMESTAMPDIFF(SECOND, MIN(_time), MAX(_time)) AS rate
FROM http_logs;
```
```kusto APL equivalent
['sample-http-logs']
| summarize rate(resp_body_size_bytes) by bin(_time, 1s)
```
---
# spotlight
Source: https://axiom.co/docs/apl/aggregation-function/spotlight
Spotlight lets you set up an analysis inside a query. You define a comparison set of events and compare it to the implicit baseline (the rest of the events in scope). Spotlight evaluates every field you pass in, scores differences, and returns the most informative contrasts. You use it when you want fast root-cause analysis, anomaly investigation, or pattern discovery without hand-rolling many ad-hoc aggregations.
Spotlight is useful when you:
* Investigate spikes or dips in a time series and want to know what changed
* Explain why a subset of traces is slow or error-prone
* Find which attributes distinguish suspicious requests from normal traffic
This page explains the Spotlight APL function. For more information about how Spotlight works in the Axiom Console, see [Spotlight](/console/intelligence/spotlight).
## Usage [#usage]
### Syntax [#syntax]
```kusto
summarize spotlight(SelectionPredicate, Field1, Field2, ..., FieldN)
```
You use `spotlight` inside `summarize`. The first argument defines the comparison set. The remaining arguments list the fields to analyze.
### Parameters [#parameters]
| Name | Type | Description |
| -------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SelectionPredicate` | Boolean expression | Defines the comparison set (selected cohort). Spotlight compares events where the predicate evaluates to `true` against the baseline (events where it evaluates to `false`) within the current query scope. |
| `Field1 ... FieldN` | field references | One or more fields to analyze. Include string or categorical fields (for proportions) and numeric or timespan fields (for distributional differences). Use `*` as a wildcard to analyze all fields. |
The wildcard `*` is useful to analyze all fields in the current row, but it increases query complexity and decreases performance. Specify only relevant fields when possible.
### Returns [#returns]
* Bar charts for categorical fields (strings, Booleans)
* Boxplots for numeric fields (integers, floats, timespans) with many distinct values
## Use case examples [#use-case-examples]
Find what distinguishes error responses from normal traffic in the last 15 minutes.
**Query**
```kusto
['sample-http-logs']
| where _time >= now(-15m)
| summarize spotlight(status startswith "5", ['geo.country'], ['geo.city'], method, uri, req_duration_ms)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20spotlight\(status%20startswith%20'5'%2C%20%5B'geo.country'%5D%2C%20%5B'geo.city'%5D%2C%20method%2C%20uri%2C%20req_duration_ms\)%22%7D)
This query keeps the last 15 minutes of traffic in scope and compares error responses to everything else. Spotlight ranks the strongest differences, pointing to endpoints, regions, and latency ranges associated with the errors.
Explain why some spans are slow or erroring in the last 30 minutes.
**Query**
```kusto
['otel-demo-traces']
| where _time >= now(-30m)
| summarize spotlight(duration > 500ms, ['service.name'], kind, status_code, duration)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20_time%20%3E%3D%20now\(-30m\)%20%7C%20summarize%20spotlight\(duration%20%3E%20500ms%2C%20%5B'service.name'%5D%2C%20kind%2C%20status_code%2C%20duration\)%22%7D)
The query compares spans that ran longer than 500 ms to all other spans in the time window. Spotlight highlights the service, kind, and duration range that most distinguish the selected spans.
## Best practices [#best-practices]
* Keep the `where` scope broad enough that the baseline remains meaningful. Over-filtering reduces contrast.
* Pass only fields that carry signal. Very high-cardinality identifiers can drown out more actionable attributes.
* Include numeric fields like `req_duration_ms` or `duration` to let Spotlight detect distribution shifts, not just categorical skews.
## List of related functions [#list-of-related-functions]
* [where](/apl/tabular-operators/where-operator): Filters events before Spotlight runs. Use it to scope the time window or dataset; use `spotlight` to compare selected vs baseline inside that scope.
* [summarize](/apl/tabular-operators/summarize-operator): Runs aggregations over events. `spotlight` is an aggregation you call within `summarize`.
* [top](/apl/tabular-operators/top-operator): Returns the most frequent values. Use `top` for simple frequency counts; use `spotlight` to contrast a cohort against its baseline with lift and significance.
* [lookup](/apl/tabular-operators/lookup-operator): Enriches events with reference attributes. Use `lookup` to add context before running `spotlight` across enriched fields.
## Other query languages [#other-query-languages]
In Splunk, there is no one operator that compares a selected cohort to the baseline across many fields at once. You often create a flag with `eval`, run separate `stats`/`eventstats` for each field, and then `appendpipe` or `join` to compare rates. In APL, `spotlight` is an aggregation you call once inside `summarize`. You pass a Boolean predicate to define the cohort and a list of fields to inspect, and APL returns a scored table of differences.
```sql Splunk example
index=web earliest=-15m
| eval is_error = if(status IN ("500","502","503"), 1, 0)
| stats count by method uri status geo_country geo_city
| eventstats sum(eval(is_error)) AS sel, sum(eval(1-is_error)) AS base
| ... (manual rate/lift calculations and sorting) ...
```
```kusto APL equivalent
['sample-http-logs']
| where _time >= now(-15m)
| summarize spotlight(status in ('500','502','503'),
['geo.country'], ['geo.city'], method, uri, status)
```
Standard SQL does not include a built-in cohort-vs-baseline comparator. You typically `CASE` a selection flag, aggregate twice (selected vs baseline), compute proportions, deltas, and significance, then union and sort. In APL, you express the selection as a predicate and let `spotlight` compute proportions, lift, and scores for each field/value.
```sql SQL example
WITH scoped AS (
SELECT *,
CASE WHEN status IN ('500','502','503') THEN 1 ELSE 0 END AS is_sel
FROM sample_http_logs
WHERE _time >= NOW() - INTERVAL '15' MINUTE
),
per_field AS (
SELECT 'status' AS field, status AS value,
AVG(is_sel) AS sel_rate,
AVG(1 - is_sel) AS base_rate
FROM scoped
GROUP BY status
)
SELECT field, value, sel_rate, base_rate,
sel_rate / NULLIF(base_rate,0) AS lift
FROM per_field
ORDER BY lift DESC
```
```kusto APL equivalent
['sample-http-logs']
| where _time >= now(-15m)
| summarize spotlight(status in ('500','502','503'),
status, method, uri, ['geo.country'], ['geo.city'])
```
---
# Aggregation functions
Source: https://axiom.co/docs/apl/aggregation-function/statistical-functions
The table summarizes the aggregation functions available in APL. Use all these aggregation functions in the context of the [summarize operator](/apl/tabular-operators/summarize-operator).
| Function | Description |
| --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| [arg\_min](/apl/aggregation-function/arg-min) | Returns the row where an expression evaluates to the minimum value. |
| [arg\_max](/apl/aggregation-function/arg-max) | Returns the row where an expression evaluates to the maximum value. |
| [avg](/apl/aggregation-function/avg) | Returns an average value across the group. |
| [avgif](/apl/aggregation-function/avgif) | Calculates the average value of an expression in records for which the predicate evaluates to true. |
| [count](/apl/aggregation-function/count) | Returns a count of the group without/with a predicate. |
| [countif](/apl/aggregation-function/countif) | Returns a count of rows for which the predicate evaluates to true. |
| [dcount](/apl/aggregation-function/dcount) | Returns an estimate for the number of distinct values that are taken by a scalar an expression in the summary group. |
| [dcountif](/apl/aggregation-function/dcountif) | Returns an estimate of the number of distinct values of an expression of rows for which the predicate evaluates to true. |
| [histogram](/apl/aggregation-function/histogram) | Returns a timeseries heatmap chart across the group. |
| [make\_list](/apl/aggregation-function/make-list) | Creates a dynamic JSON object (array) of all the values of an expression in the group. |
| [make\_list\_if](/apl/aggregation-function/make-list-if) | Creates a dynamic JSON object (array) of an expression values in the group for which the predicate evaluates to true. |
| [make\_set](/apl/aggregation-function/make-set) | Creates a dynamic JSON array of the set of distinct values that an expression takes in the group. |
| [make\_set\_if](/apl/aggregation-function/make-set-if) | Creates a dynamic JSON object (array) of the set of distinct values that an expression takes in records for which the predicate evaluates to true. |
| [max](/apl/aggregation-function/max) | Returns the maximum value across the group. |
| [maxif](/apl/aggregation-function/maxif) | Calculates the maximum value of an expression in records for which the predicate evaluates to true. |
| [min](/apl/aggregation-function/min) | Returns the minimum value across the group. |
| [minif](/apl/aggregation-function/minif) | Returns the minimum of an expression in records for which the predicate evaluates to true. |
| [percentile](/apl/aggregation-function/percentile) | Calculates the requested percentiles of the group and produces a timeseries chart. |
| [percentileif](/apl/aggregation-function/percentileif) | Calculates the requested percentiles of the field for the rows where the predicate evaluates to true. |
| [percentiles\_array](/apl/aggregation-function/percentiles-array) | Returns an array of numbers where each element is the value at the corresponding percentile. |
| [percentiles\_arrayif](/apl/aggregation-function/percentiles-arrayif) | Returns an array of percentile values for the records that satisfy the condition. |
| [phrases](/apl/aggregation-function/phrases) | Extracts and counts common phrases or word sequences from text fields. |
| [rate](/apl/aggregation-function/rate) | Calculates the rate of values in a group per second. |
| [spotlight](/apl/aggregation-function/spotlight) | Compares a selected set of events against a baseline and surface the most significant differences. |
| [stdev](/apl/aggregation-function/stdev) | Calculates the standard deviation of an expression across the group. |
| [stdevif](/apl/aggregation-function/stdevif) | Calculates the standard deviation of an expression in records for which the predicate evaluates to true. |
| [sum](/apl/aggregation-function/sum) | Calculates the sum of an expression across the group. |
| [sumif](/apl/aggregation-function/sumif) | Calculates the sum of an expression in records for which the predicate evaluates to true. |
| [topk](/apl/aggregation-function/topk) | Calculates the top values of an expression across the group in a dataset. |
| [topkif](/apl/aggregation-function/topkif) | Calculates the top values of an expression in records for which the predicate evaluates to true. |
| [variance](/apl/aggregation-function/variance) | Calculates the variance of an expression across the group. |
| [varianceif](/apl/aggregation-function/varianceif) | Calculates the variance of an expression in records for which the predicate evaluates to true. |
---
# stdev
Source: https://axiom.co/docs/apl/aggregation-function/stdev
The `stdev` aggregation in APL computes the standard deviation of a numeric field within a dataset. This is useful for understanding the variability or dispersion of data points around the mean. You can apply this aggregation to various use cases, such as performance monitoring, anomaly detection, and statistical analysis of logs and traces.
Use the `stdev` function to determine how spread out values like request duration, span duration, or response times are. This is particularly helpful when analyzing data trends and identifying inconsistencies, outliers, or abnormal behavior.
## Usage [#usage]
### Syntax [#syntax]
```kusto
stdev(numeric_field)
```
### Parameters [#parameters]
* **`numeric_field`**: The field containing numeric values for which the standard deviation is calculated.
### Returns [#returns]
The `stdev` aggregation returns a single numeric value representing the standard deviation of the specified numeric field in the dataset.
## Use case examples [#use-case-examples]
You can use the `stdev` aggregation to analyze HTTP request durations and identify performance variations across different requests. For instance, you can calculate the standard deviation of request durations to identify potential anomalies.
**Query**
```kusto
['sample-http-logs']
| summarize req_duration_std = stdev(req_duration_ms)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20req_duration_std%20%3D%20stdev\(req_duration_ms\)%22%7D)
**Output**
| req\_duration\_std |
| ------------------ |
| 345.67 |
This query calculates the standard deviation of the `req_duration_ms` field in the `sample-http-logs` dataset, helping to understand how much variability there is in request durations.
In distributed tracing, calculating the standard deviation of span durations can help identify inconsistent spans that might indicate performance issues or bottlenecks.
**Query**
```kusto
['otel-demo-traces']
| summarize span_duration_std = stdev(duration)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20span_duration_std%20%3D%20stdev\(duration\)%22%7D)
**Output**
| span\_duration\_std |
| ------------------- |
| 0:00:02.456 |
This query computes the standard deviation of span durations in the `otel-demo-traces` dataset, providing insight into how much variation exists between trace spans.
In security logs, the `stdev` function can help analyze the response times of various HTTP requests, potentially identifying patterns that might be related to security incidents or abnormal behavior.
**Query**
```kusto
['sample-http-logs']
| summarize resp_time_std = stdev(req_duration_ms) by status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20resp_time_std%20%3D%20stdev\(req_duration_ms\)%20by%20status%22%7D)
**Output**
| status | resp\_time\_std |
| ------ | --------------- |
| 200 | 123.45 |
| 500 | 567.89 |
This query calculates the standard deviation of request durations grouped by the HTTP status code, providing insight into the performance of different status codes.
## List of related aggregations [#list-of-related-aggregations]
* [**avg**](/apl/aggregation-function/avg): Calculates the average value of a numeric field. Use `avg` to understand the central tendency of the data.
* [**min**](/apl/aggregation-function/min): Returns the smallest value in a numeric field. Use `min` when you need to find the minimum value.
* [**max**](/apl/aggregation-function/max): Returns the largest value in a numeric field. Use `max` to identify the peak value in a dataset.
* [**sum**](/apl/aggregation-function/sum): Adds up all the values in a numeric field. Use `sum` to get a total across records.
* [**count**](/apl/aggregation-function/count): Returns the number of records in a dataset. Use `count` when you need the number of occurrences or entries.
## Other query languages [#other-query-languages]
In Splunk SPL, the `stdev` aggregation function works similarly but has a different syntax. While SPL uses the `stdev` command within the `stats` function, APL users find the aggregation works similarly in APL with just minor differences in syntax.
```sql Splunk example
| stats stdev(duration) as duration_std
```
```kusto APL equivalent
['dataset']
| summarize duration_std = stdev(duration)
```
In ANSI SQL, the standard deviation is computed using the `STDDEV` function. APL's `stdev` function is the direct equivalent of SQL’s `STDDEV`, although APL uses pipes (`|`) for chaining operations and different keyword formatting.
```sql SQL example
SELECT STDDEV(duration) AS duration_std FROM dataset;
```
```kusto APL equivalent
['dataset']
| summarize duration_std = stdev(duration)
```
---
# stdevif
Source: https://axiom.co/docs/apl/aggregation-function/stdevif
The `stdevif` aggregation function in APL computes the standard deviation of values in a group based on a specified condition. This is useful when you want to calculate variability in data, but only for rows that meet a particular condition. For example, you can use `stdevif` to find the standard deviation of response times in an HTTP log, but only for requests that resulted in a 200 status code.
The `stdevif` function is useful when you want to analyze the spread of data values filtered by specific criteria, such as analyzing request durations in successful transactions or monitoring trace durations of specific services in OpenTelemetry data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
summarize stdevif(column, condition)
```
### Parameters [#parameters]
* **column**: The column that contains the numeric values for which you want to calculate the standard deviation.
* **condition**: The condition that must be true for the values to be included in the standard deviation calculation.
### Returns [#returns]
The `stdevif` function returns a floating-point number representing the standard deviation of the specified column for the rows that satisfy the condition.
## Use case examples [#use-case-examples]
In this example, you calculate the standard deviation of request durations (`req_duration_ms`), but only for successful HTTP requests (status code 200).
**Query**
```kusto
['sample-http-logs']
| summarize stdevif(req_duration_ms, status == '200') by ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20stdevif%28req_duration_ms%2C%20status%20%3D%3D%20%27200%27%29%20by%20%5B%27geo.country%27%5D%22%7D)
**Output**
| geo.country | stdev\_req\_duration\_ms |
| ----------- | ------------------------ |
| US | 120.45 |
| Canada | 98.77 |
| Germany | 134.92 |
This query calculates the standard deviation of request durations for HTTP 200 responses, grouped by country.
In this example, you calculate the standard deviation of span durations, but only for traces from the `frontend` service.
**Query**
```kusto
['otel-demo-traces']
| summarize stdevif(duration, ['service.name'] == "frontend") by kind
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20summarize%20stdevif%28duration%2C%20%5B%27service.name%27%5D%20%3D%3D%20%27frontend%27%29%20by%20kind%22%7D)
**Output**
| kind | stdev\_duration |
| ------ | --------------- |
| server | 45.78 |
| client | 23.54 |
This query computes the standard deviation of span durations for the `frontend` service, grouped by span type (`kind`).
In this example, you calculate the standard deviation of request durations for security events from specific HTTP methods, filtered by `POST` requests.
**Query**
```kusto
['sample-http-logs']
| summarize stdevif(req_duration_ms, method == "POST") by ['geo.city']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20stdevif%28req_duration_ms%2C%20method%20%3D%3D%20%27POST%27%29%20by%20%5B%27geo.city%27%5D%22%7D)
**Output**
| geo.city | stdev\_req\_duration\_ms |
| -------- | ------------------------ |
| New York | 150.12 |
| Berlin | 130.33 |
This query calculates the standard deviation of request durations for `POST` HTTP requests, grouped by the originating city.
## List of related aggregations [#list-of-related-aggregations]
* [**avgif**](/apl/aggregation-function/avgif): Similar to `stdevif`, but instead of calculating the standard deviation, `avgif` computes the average of values that meet the condition.
* [**sumif**](/apl/aggregation-function/sumif): Computes the sum of values that meet the condition. Use `sumif` when you want to aggregate total values instead of analyzing data spread.
* [**varianceif**](/apl/aggregation-function/varianceif): Returns the variance of values that meet the condition, which is a measure of how spread out the data points are.
* [**countif**](/apl/aggregation-function/countif): Counts the number of rows that satisfy the specified condition.
* [**minif**](/apl/aggregation-function/minif): Retrieves the minimum value that satisfies the given condition, useful when finding the smallest value in filtered data.
## Other query languages [#other-query-languages]
In Splunk SPL, the `stdev` function is used to calculate the standard deviation, but you need to use an `if` function or a `where` clause to filter data. APL simplifies this by combining both operations in `stdevif`.
```sql Splunk example
| stats stdev(req_duration_ms) as stdev_req where status="200"
```
```kusto APL equivalent
['sample-http-logs']
| summarize stdevif(req_duration_ms, status == "200") by geo.country
```
In ANSI SQL, the `STDDEV` function is used to compute the standard deviation, but it requires the use of a `CASE WHEN` expression to apply a conditional filter. APL integrates the condition directly into the `stdevif` function.
```sql SQL example
SELECT STDDEV(CASE WHEN status = '200' THEN req_duration_ms END)
FROM sample_http_logs
GROUP BY geo.country;
```
```kusto APL equivalent
['sample-http-logs']
| summarize stdevif(req_duration_ms, status == "200") by geo.country
```
---
# sum
Source: https://axiom.co/docs/apl/aggregation-function/sum
The `sum` aggregation in APL is used to compute the total sum of a specific numeric field in a dataset. This aggregation is useful when you want to find the cumulative value for a certain metric, such as the total duration of requests, total sales revenue, or any other numeric field that can be summed.
You can use the `sum` aggregation in a wide range of scenarios, such as analyzing log data, monitoring traces, or examining security logs. It’s particularly helpful when you want to get a quick overview of your data in terms of totals or cumulative statistics.
## Usage [#usage]
### Syntax [#syntax]
```kusto
summarize [ =] sum()
```
### Parameters [#parameters]
* ``: (Optional) The name you want to assign to the resulting column that contains the sum.
* ``: The field in your dataset that contains the numeric values you want to sum.
### Returns [#returns]
The `sum` aggregation returns a single row with the sum of the specified numeric field. If used with a `by` clause, it returns multiple rows with the sum per group.
## Use case examples [#use-case-examples]
The `sum` aggregation can be used to calculate the total request duration in an HTTP log dataset.
**Query**
```kusto
['sample-http-logs']
| summarize total_duration = sum(req_duration_ms)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20total_duration%20%3D%20sum\(req_duration_ms\)%22%7D)
**Output**
| total\_duration |
| --------------- |
| 123456 |
This query calculates the total request duration across all HTTP requests in the dataset.
The `sum` aggregation can be applied to OpenTelemetry traces to calculate the total span duration.
**Query**
```kusto
['otel-demo-traces']
| summarize total_duration = sum(duration)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20total_duration%20%3D%20sum\(duration\)%22%7D)
**Output**
| total\_duration |
| --------------- |
| 7890 |
This query calculates the total duration of all spans in the dataset.
You can use the `sum` aggregation to calculate the total number of requests based on a specific HTTP status in security logs.
**Query**
```kusto
['sample-http-logs']
| where status == '200'
| summarize request_count = sum(1)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20%3D%3D%20'200'%20%7C%20summarize%20request_count%20%3D%20sum\(1\)%22%7D)
**Output**
| request\_count |
| -------------- |
| 500 |
This query counts the total number of successful requests (status 200) in the dataset.
## List of related aggregations [#list-of-related-aggregations]
* [**count**](/apl/aggregation-function/count): Counts the number of records in a dataset. Use `count` when you want to count the number of rows, not aggregate numeric values.
* [**avg**](/apl/aggregation-function/avg): Computes the average value of a numeric field. Use `avg` when you need to find the mean instead of the total sum.
* [**min**](/apl/aggregation-function/min): Returns the minimum value of a numeric field. Use `min` when you’re interested in the lowest value.
* [**max**](/apl/aggregation-function/max): Returns the maximum value of a numeric field. Use `max` when you’re interested in the highest value.
* [**sumif**](/apl/aggregation-function/sumif): Sums a numeric field conditionally. Use `sumif` when you only want to sum values that meet a specific condition.
## Other query languages [#other-query-languages]
In Splunk, you use the `sum` function in combination with the `stats` command to aggregate data. In APL, the `sum` aggregation works similarly but is structured differently in terms of syntax.
```splunk Splunk example
| stats sum(req_duration_ms) as total_duration
```
```kusto APL equivalent
['sample-http-logs']
| summarize total_duration = sum(req_duration_ms)
```
In ANSI SQL, the `SUM` function is commonly used with the `GROUP BY` clause to aggregate data by a specific field. In APL, the `sum` function works similarly but can be used without requiring a `GROUP BY` clause for simple summations.
```sql SQL example
SELECT SUM(req_duration_ms) AS total_duration
FROM sample_http_logs
```
```kusto APL equivalent
['sample-http-logs']
| summarize total_duration = sum(req_duration_ms)
```
---
# sumif
Source: https://axiom.co/docs/apl/aggregation-function/sumif
The `sumif` aggregation function in Axiom Processing Language (APL) computes the sum of a numeric expression for records that meet a specified condition. This function is useful when you want to filter data based on specific criteria and aggregate the numeric values that match the condition. Use `sumif` when you need to apply conditional logic to sums, such as calculating the total request duration for successful HTTP requests or summing the span durations in OpenTelemetry traces for a specific service.
## Usage [#usage]
### Syntax [#syntax]
```kusto
sumif(numeric_expression, condition)
```
### Parameters [#parameters]
* `numeric_expression`: The numeric field or expression you want to sum.
* `condition`: A boolean expression that determines which records contribute to the sum. Only the records that satisfy the condition are considered.
### Returns [#returns]
`sumif` returns the sum of the values in `numeric_expression` for records where the `condition` is true. If no records meet the condition, the result is 0.
## Use case examples [#use-case-examples]
In this use case, we calculate the total request duration for HTTP requests that returned a `200` status code.
**Query**
```kusto
['sample-http-logs']
| summarize total_req_duration = sumif(req_duration_ms, status == '200')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20total_req_duration%20%3D%20sumif%28req_duration_ms%2C%20status%20%3D%3D%20%27200%27%29%22%7D)
**Output**
| total\_req\_duration |
| -------------------- |
| 145000 |
This query computes the total request duration (in milliseconds) for all successful HTTP requests (those with a status code of `200`).
In this example, we sum the span durations for the `frontend` service in OpenTelemetry traces.
**Query**
```kusto
['otel-demo-traces']
| summarize total_duration = sumif(duration, ['service.name'] == 'frontend')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20summarize%20total_duration%20%3D%20sumif%28duration%2C%20%5B%27service.name%27%5D%20%3D%3D%20%27frontend%27%29%22%7D)
**Output**
| total\_duration |
| --------------- |
| 32000 |
This query sums the span durations for traces related to the `frontend` service, providing insight into how long this service has been running over time.
Here, we calculate the total request duration for failed HTTP requests (those with status codes other than `200`).
**Query**
```kusto
['sample-http-logs']
| summarize total_req_duration_failed = sumif(req_duration_ms, status != '200')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20total_req_duration_failed%20%3D%20sumif%28req_duration_ms%2C%20status%20%21%3D%20%27200%27%29%22%7D)
**Output**
| total\_req\_duration\_failed |
| ---------------------------- |
| 64000 |
This query computes the total request duration for all failed HTTP requests (where the status code isn’t `200`), which can be useful for security log analysis.
## List of related aggregations [#list-of-related-aggregations]
* [**avgif**](/apl/aggregation-function/avgif): Computes the average of a numeric expression for records that meet a specified condition. Use `avgif` when you’re interested in the average value, not the total sum.
* [**countif**](/apl/aggregation-function/countif): Counts the number of records that satisfy a condition. Use `countif` when you need to know how many records match a specific criterion.
* [**minif**](/apl/aggregation-function/minif): Returns the minimum value of a numeric expression for records that meet a condition. Useful when you need the smallest value under certain criteria.
* [**maxif**](/apl/aggregation-function/maxif): Returns the maximum value of a numeric expression for records that meet a condition. Use `maxif` to identify the highest values under certain conditions.
## Other query languages [#other-query-languages]
In Splunk SPL, the `sumif` equivalent functionality requires using a `stats` command with a `where` clause to filter the data. In APL, you can use `sumif` to simplify this operation by combining both the condition and the summing logic into one function.
```sql Splunk example
| stats sum(duration) as total_duration where status="200"
```
```kusto APL equivalent
summarize total_duration = sumif(duration, status == '200')
```
In ANSI SQL, achieving a similar result typically involves using a `CASE` statement inside the `SUM` function to conditionally sum values based on a specified condition. In APL, `sumif` provides a more concise approach by allowing you to filter and sum in a single function.
```sql SQL example
SELECT SUM(CASE WHEN status = '200' THEN duration ELSE 0 END) AS total_duration
FROM http_logs
```
```kusto APL equivalent
summarize total_duration = sumif(duration, status == '200')
```
---
# topk
Source: https://axiom.co/docs/apl/aggregation-function/topk
The `topk` aggregation in Axiom Processing Language (APL) allows you to identify the top `k` results based on a specified field. This is especially useful when you want to quickly analyze large datasets and extract the most significant values, such as the top-performing queries, most frequent errors, or highest latency requests.
Use `topk` to find the most common or relevant entries in datasets, especially in log analysis, telemetry data, and monitoring systems. This aggregation helps you focus on the most important data points, filtering out the noise.
The `topk` aggregation in APL is a statistical aggregation that returns estimated results. The estimation comes with the benefit of speed at the expense of accuracy. This means that `topk` is fast and light on resources even on a large or high-cardinality dataset, but it doesn’t provide precise results.
For completely accurate results, use the [`top` operator](/apl/tabular-operators/top-operator).
## Usage [#usage]
### Syntax [#syntax]
```kusto
topk(Field, k)
```
### Parameters [#parameters]
* `Field`: The field or expression to rank the results by.
* `k`: The number of top results to return.
### Returns [#returns]
A subset of the original dataset with the top `k` values based on the specified field.
## Use case examples [#use-case-examples]
When analyzing HTTP logs, you can use the `topk` function to find the top 5 most frequent HTTP status codes.
**Query**
```kusto
['sample-http-logs']
| summarize topk(status, 5)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%20%7C%20summarize%20topk\(status%2C%205\)%22%7D)
**Output**
| status | count\_ |
| ------ | ------- |
| 200 | 1500 |
| 404 | 400 |
| 500 | 200 |
| 301 | 150 |
| 302 | 100 |
This query groups the logs by HTTP status and returns the 5 most frequent statuses.
In OpenTelemetry traces, you can use `topk` to find the top five status codes by service.
**Query**
```kusto
['otel-demo-traces']
| summarize topk(['attributes.http.status_code'], 5) by ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20topk\(%5B'attributes.http.status_code'%5D%2C%205\)%20by%20%5B'service.name'%5D%22%7D)
**Output**
| service.name | attributes.http.status\_code | \_count |
| ------------- | ---------------------------- | ---------- |
| frontendproxy | 200 | 34,862,088 |
| | 203 | 3,095,223 |
| | 404 | 154,417 |
| | 500 | 153,823 |
| | 504 | 3,497 |
This query shows the top five status codes by service.
You can use `topk` in security log analysis to find the top 5 cities generating the most HTTP requests.
**Query**
```kusto
['sample-http-logs']
| summarize topk(['geo.city'], 5)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%20%7C%20summarize%20topk\(%5B'geo.city'%5D%2C%205\)%22%7D)
**Output**
| geo.city | count\_ |
| -------- | ------- |
| New York | 500 |
| London | 400 |
| Paris | 350 |
| Tokyo | 300 |
| Berlin | 250 |
This query returns the top 5 cities based on the number of HTTP requests.
## List of related aggregations [#list-of-related-aggregations]
* [top](/apl/tabular-operators/top-operator): Returns the top values based on a field without requiring a specific number of results (`k`), making it useful when you’re unsure how many top values to retrieve.
* [topkif](/apl/aggregation-function/topkif): Returns the top `k` results without filtering. Use topk when you don’t need to restrict your analysis to a subset.
* [sort](/apl/tabular-operators/sort-operator): Orders the dataset based on one or more fields, which is useful if you need a complete ordered list rather than the top `k` values.
* [extend](/apl/tabular-operators/extend-operator): Adds calculated fields to your dataset, which can be useful in combination with `topk` to create custom rankings.
* [count](/apl/aggregation-function/count): Aggregates the dataset by counting occurrences, often used in conjunction with `topk` to find the most common values.
## Other query languages [#other-query-languages]
Splunk SPL doesn’t have the equivalent of the `topk` function. You can achieve similar results with SPL’s `top` command which is equivalent to APL’s `top` operator. The `topk` function in APL behaves similarly by returning the top `k` values of a specified field, but its syntax is unique to APL.
The main difference between `top` (supported by both SPL and APL) and `topk` (supported only by APL) is that `topk` is estimated. This means that APL’s `topk` is faster, less resource intenstive, but less accurate than SPL’s `top`.
```sql Splunk example
| top limit=5 status by method
```
```kusto APL equivalent
['sample-http-logs']
| summarize topk(status, 5) by method
```
In ANSI SQL, identifying the top `k` rows often involves using the `ORDER BY` and `LIMIT` clauses. While the logic remains similar, APL’s `topk` simplifies this process by directly returning the top `k` values of a field in an aggregation.
The main difference between SQL’s solution and APL’s `topk` is that `topk` is estimated. This means that APL’s `topk` is faster, less resource intenstive, but less accurate than SQL’s combination of `ORDER BY` and `LIMIT` clauses.
```sql SQL example
SELECT status, COUNT(*)
FROM sample_http_logs
GROUP BY status
ORDER BY COUNT(*) DESC
LIMIT 5;
```
```kusto APL equivalent
['sample-http-logs']
| summarize topk(status, 5)
```
---
# topkif
Source: https://axiom.co/docs/apl/aggregation-function/topkif
The `topkif` aggregation in Axiom Processing Language (APL) allows you to identify the top `k` values based on a specified field, while also applying a filter on another field. Use `topkif` when you want to find the most significant entries that meet specific criteria, such as the top-performing queries from a particular service, the most frequent errors for a specific HTTP method, or the highest latency requests from a specific country.
Use `topkif` when you need to focus on the most important filtered subsets of data, especially in log analysis, telemetry data, and monitoring systems. This aggregation helps you quickly zoom in on significant values without scanning the entire dataset.
The `topkif` aggregation in APL is a statistical aggregation that returns estimated results. The estimation provides the benefit of speed at the expense of precision. This means that `topkif` is fast and light on resources even on large or high-cardinality datasets but doesn’t provide completely accurate results.
For completely accurate results, use the [top operator](/apl/tabular-operators/top-operator) together with a filter.
## Syntax [#syntax]
```kusto
topkif(Field, k, Condition)
```
## Parameters [#parameters]
* `Field`: The field or expression to rank the results by.
* `k`: The number of top results to return.
* `Condition`: A logical expression that specifies the filtering condition.
## Returns [#returns]
A subset of the original dataset containing the top `k` values based on the specified field, after applying the filter condition.
# Use case examples [#use-case-examples]
Use `topkif` when analyzing HTTP logs to find the top 5 most frequent HTTP status codes for GET requests.
**Query**
```kusto
['sample-http-logs']
| summarize topkif(status, 5, method == 'GET')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20topkif\(status%2C%205%2C%20method%20%3D%3D%20'GET'\)%22%7D)
**Output**
| status | count\_ |
| ------ | ------- |
| 200 | 900 |
| 404 | 250 |
| 500 | 100 |
| 301 | 90 |
| 302 | 60 |
This query groups GET requests by HTTP status and returns the 5 most frequent statuses.
Use `topkif` in OpenTelemetry traces to find the top five services for server.
**Query**
```kusto
['otel-demo-traces']
| summarize topkif(['service.name'], 5, kind == 'server')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20topkif\(%5B'service.name'%5D%2C%205%2C%20kind%20%3D%3D%20'server'\)%22%7D)
**Output**
| service.name | count\_ |
| --------------- | ------- |
| frontend-proxy | 99,573 |
| frontend | 91,800 |
| product-catalog | 29,696 |
| image-provider | 25,223 |
| flagd | 10,336 |
This query shows the top five services filtered to server.
Use `topkif` in security log analysis to find the top 5 cities generating GET HTTP requests.
**Query**
```kusto
['sample-http-logs']
| summarize topkif(['geo.city'], 5, method == 'GET')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20topkif\(%5B'geo.city'%5D%2C%205%2C%20method%20%3D%3D%20'GET'\)%22%7D)
**Output**
| geo.city | count\_ |
| -------- | ------- |
| New York | 300 |
| London | 250 |
| Paris | 200 |
| Tokyo | 180 |
| Berlin | 160 |
This query returns the top 5 cities generating the most GET HTTP requests.
# List of related aggregations [#list-of-related-aggregations]
* [topk](/apl/aggregation-function/topk): Returns the top `k` results without filtering. Use topk when you don’t need to restrict your analysis to a subset.
* [top](/apl/tabular-operators/top-operator): Returns the top results based on a field with accurate results. Use top when precision is important.
* [sort](/apl/tabular-operators/sort-operator): Sorts the dataset based on one or more fields. Use sort if you need full ordered results.
* [extend](/apl/tabular-operators/extend-operator): Adds calculated fields to your dataset, useful before applying topkif to create new fields to rank.
* [count](/apl/aggregation-function/count): Counts occurrences in the dataset. Use count when you only need counts without focusing on the top entries.\`
## Other query languages [#other-query-languages]
Splunk SPL doesn’t have a direct equivalent to the `topkif` function. You can achieve similar results by using the top command combined with a where clause, which is closer to using APL’s top operator with a filter. However, APL’s `topkif` provides a more optimized, estimated solution when you want speed and efficiency.
```sql Splunk example
| where method="GET" | top limit=5 status
```
```kusto APL equivalent
['sample-http-logs']
| summarize topkif(status, 5, method == 'GET')
```
In ANSI SQL, identifying the top `k` rows filtered by a condition often involves a WHERE clause followed by ORDER BY and LIMIT. APL’s `topkif` simplifies this by combining the filtering and top-k selection in one function.
```sql SQL example
SELECT status, COUNT(*)
FROM sample_http_logs
WHERE method = 'GET'
GROUP BY status
ORDER BY COUNT(*) DESC
LIMIT 5;
```
```kusto APL equivalent
['sample-http-logs']
| summarize topkif(status, 5, method == 'GET')
```
# Usage [#usage]
---
# variance
Source: https://axiom.co/docs/apl/aggregation-function/variance
The `variance` aggregation function in APL calculates the variance of a numeric expression across a set of records. Variance is a statistical measurement that represents the spread of data points in a dataset. It’s useful for understanding how much variation exists in your data. In scenarios such as performance analysis, network traffic monitoring, or anomaly detection, `variance` helps identify outliers and patterns by showing how data points deviate from the mean.
## Usage [#usage]
### Syntax [#syntax]
```kusto
summarize variance(Expression)
```
### Parameters [#parameters]
* `Expression`: A numeric expression or field for which you want to compute the variance. The expression should evaluate to a numeric data type.
### Returns [#returns]
The function returns the variance (a numeric value) of the specified expression across the records.
## Use case examples [#use-case-examples]
You can use the `variance` function to measure the variability of request durations, which helps in identifying performance bottlenecks or anomalies in web services.
**Query**
```kusto
['sample-http-logs']
| summarize variance(req_duration_ms)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20variance\(req_duration_ms\)%22%7D)
**Output**
| variance\_req\_duration\_ms |
| --------------------------- |
| 1024.5 |
This query calculates the variance of request durations from a dataset of HTTP logs. A high variance indicates greater variability in request durations, potentially signaling performance issues.
For OpenTelemetry traces, `variance` can be used to measure how much span durations differ across service invocations, helping in performance optimization and anomaly detection.
**Query**
```kusto
['otel-demo-traces']
| summarize variance(duration)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20variance\(duration\)%22%7D)
**Output**
| variance\_duration |
| ------------------ |
| 1287.3 |
This query computes the variance of span durations across traces, which helps in understanding how consistent the service performance is. A higher variance might indicate unstable or inconsistent performance.
You can use the `variance` function on security logs to detect abnormal patterns in request behavior, such as unusual fluctuations in response times, which may point to potential security threats.
**Query**
```kusto
['sample-http-logs']
| summarize variance(req_duration_ms) by status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20variance\(req_duration_ms\)%20by%20status%22%7D)
**Output**
| status | variance\_req\_duration\_ms |
| ------ | --------------------------- |
| 200 | 1534.8 |
| 404 | 2103.4 |
This query calculates the variance of request durations grouped by HTTP status codes. High variance in certain status codes (e.g., 404 errors) can indicate network or application issues.
## List of related aggregations [#list-of-related-aggregations]
* [**stdev**](/apl/aggregation-function/stdev): Computes the standard deviation, which is the square root of the variance. Use `stdev` when you need the spread of data in the same units as the original dataset.
* [**avg**](/apl/aggregation-function/avg): Computes the average of a numeric field. Combine `avg` with `variance` to analyze both the central tendency and the spread of data.
* [**count**](/apl/aggregation-function/count): Counts the number of records. Use `count` alongside `variance` to get a sense of data size relative to variance.
* [**percentile**](/apl/aggregation-function/percentile): Returns a value below which a given percentage of observations fall. Use `percentile` for a more detailed distribution analysis.
* [**max**](/apl/aggregation-function/max): Returns the maximum value. Use `max` when you are looking for extreme values in addition to variance to detect anomalies.
## Other query languages [#other-query-languages]
In SPL, variance is computed using the `stats` command with the `var` function, whereas in APL, you can use `variance` for the same functionality.
```sql Splunk example
| stats var(req_duration_ms) as variance
```
```kusto APL equivalent
['sample-http-logs']
| summarize variance(req_duration_ms)
```
In ANSI SQL, variance is typically calculated using `VAR_POP` or `VAR_SAMP`. APL provides a simpler approach using the `variance` function without needing to specify population or sample.
```sql SQL example
SELECT VAR_POP(req_duration_ms) FROM sample_http_logs;
```
```kusto APL equivalent
['sample-http-logs']
| summarize variance(req_duration_ms)
```
---
# varianceif
Source: https://axiom.co/docs/apl/aggregation-function/varianceif
The `varianceif` aggregation in APL calculates the variance of values that meet a specified condition. This is useful when you want to understand the variability of a subset of data without considering all data points. For example, you can use `varianceif` to compute the variance of request durations for HTTP requests that resulted in a specific status code or to track anomalies in trace durations for a particular service.
You can use the `varianceif` aggregation when analyzing logs, telemetry data, or security events where conditions on subsets of the data are critical to your analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
summarize varianceif(Expr, Predicate)
```
### Parameters [#parameters]
* `Expr`: The expression (numeric) for which you want to calculate the variance.
* `Predicate`: A boolean condition that determines which records to include in the calculation.
### Returns [#returns]
Returns the variance of `Expr` for the records where the `Predicate` is true. If no records match the condition, it returns `null`.
## Use case examples [#use-case-examples]
You can use the `varianceif` function to calculate the variance of HTTP request durations for requests that succeeded (`status == '200'`).
**Query**
```kusto
['sample-http-logs']
| summarize varianceif(req_duration_ms, status == '200')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20varianceif%28req_duration_ms%2C%20status%20%3D%3D%20'200'%29%22%7D)
**Output**
| varianceif\_req\_duration\_ms |
| ----------------------------- |
| 15.6 |
This query calculates the variance of request durations for all HTTP requests that returned a status code of 200 (successful requests).
You can use the `varianceif` function to monitor the variance in span durations for a specific service, such as the `frontend` service.
**Query**
```kusto
['otel-demo-traces']
| summarize varianceif(duration, ['service.name'] == 'frontend')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20varianceif%28duration%2C%20%5B'service.name'%5D%20%3D%3D%20'frontend'%29%22%7D)
**Output**
| varianceif\_duration |
| -------------------- |
| 32.7 |
This query calculates the variance in the duration of spans generated by the `frontend` service.
The `varianceif` function can also be used to track the variance in request durations for requests from a specific geographic region, such as requests from `geo.country == 'United States'`.
**Query**
```kusto
['sample-http-logs']
| summarize varianceif(req_duration_ms, ['geo.country'] == 'United States')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20varianceif%28req_duration_ms%2C%20%5B'geo.country'%5D%20%3D%3D%20'United%20States'%29%22%7D)
**Output**
| varianceif\_req\_duration\_ms |
| ----------------------------- |
| 22.9 |
This query calculates the variance in request durations for requests originating from the United States.
## List of related aggregations [#list-of-related-aggregations]
* [**avgif**](/apl/aggregation-function/avgif): Computes the average value of an expression for records that match a given condition. Use `avgif` when you want the average instead of variance.
* [**sumif**](/apl/aggregation-function/sumif): Returns the sum of values that meet a specified condition. Use `sumif` when you’re interested in totals, not variance.
* [**stdevif**](/apl/aggregation-function/stdevif): Returns the standard deviation of values based on a condition. Use `stdevif` when you want to measure dispersion using standard deviation instead of variance.
## Other query languages [#other-query-languages]
In Splunk, you would use the `eval` function to filter data and calculate variance for specific conditions. In APL, `varianceif` combines the filtering and aggregation into a single function, making your queries more concise.
```sql Splunk example
| eval filtered_var=if(status=="200",req_duration_ms,null())
| stats var(filtered_var)
```
```kusto APL equivalent
['sample-http-logs']
| summarize varianceif(req_duration_ms, status == '200')
```
In ANSI SQL, you typically use a `CASE` statement to apply conditional logic and then compute the variance. In APL, `varianceif` simplifies this by combining both the condition and the aggregation.
```sql SQL example
SELECT VARIANCE(CASE WHEN status = '200' THEN req_duration_ms END)
FROM sample_http_logs;
```
```kusto APL equivalent
['sample-http-logs']
| summarize varianceif(req_duration_ms, status == '200')
```
---
# Map fields
Source: https://axiom.co/docs/apl/data-types/map-fields
Map fields are a special type of field that can hold a collection of nested key-value pairs within a single field. You can think of the content of a map field as a JSON object.
Axiom automatically creates map fields in datasets that use [OpenTelemetry](/send-data/opentelemetry) and you can create map fields yourself in any dataset.
## Benefits and drawbacks of map fields [#benefits-and-drawbacks-of-map-fields]
Map fields help you manage high-dimensionality data by storing multiple key-value pairs within a single field. One of the benefits of map fields is that you can store additional attributes without adding more fields. This is particularly useful when the shape of your data is unpredictable (for example, additional attributes added by OpenTelemetry instrumentation). Using map fields means that you can avoid reaching the field limit of a dataset.
Use map fields in the following cases:
* You approach the dataset field limit.
* The shape of your data is unpredictable. For example, an OpenTelemetry instrumentation or another SDK creates objects with many keys.
* You work with feature flags or custom attributes that generate many fields.
Map fields reduce impact on field limits, but involve trade-offs in query efficiency and compression. The drawbacks of map fields are the following:
* Querying map fields uses more query-hours than querying conventional fields.
* In some cases, map fields don’t compress as well as conventional fields. For example, if there is little shared structure between map values. This means datasets with map fields can use more storage.
* You don’t have visibility into map fields from the schema. For example, autocomplete doesn’t know the properties inside the map field.
## Custom attributes in tracing datasets [#custom-attributes-in-tracing-datasets]
If you use [OpenTelemetry](/send-data/opentelemetry) to send data to Axiom, you find some attributes in the `attributes.custom` map field. The reason is that instrumentation libraries can add hundreds or even thousands of arbitrary attributes to spans. Storing each custom attribute in a separate field would significantly increase the number of fields in your dataset. To keep the number of fields in your dataset under control, Axiom places all custom attributes in the single `attributes.custom` map field.
## Use map fields in queries [#use-map-fields-in-queries]
Map fields are particularly useful for handling nested data structures, such as data from `logfmt` format or other key-value formats with unpredictable structure.
The example query below uses the `http.protocol` property inside the `attributes.custom` map field to filter results:
```kusto
['otel-demo-traces']
| where ['attributes.custom']['http.protocol'] == 'HTTP/1.1'
```
[Run in playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7b%22apl%22%3a%22%5b%27otel-demo-traces%27%5d%5cn%7c%20where%20%5b%27attributes.custom%27%5d%5b%27http.protocol%27%5d%20%3d%3d%20%27HTTP%2f1.1%27%22%2c%22queryOptions%22%3a%7b%22quickRange%22%3a%2230d%22%7d%7d)
## Create map fields using UI [#create-map-fields-using-ui]
To create a map field using the UI:
1. Go to the Datasets tab.
2. Select the dataset where you want to create the map field.
3. In the top right of the fields list, click **More > Create map field**.
4. In **Field name**, enter the full name of the field, including parent fields, if any. For example, `map_field_name`. For more information on syntax, see [Access properties of nested maps](#access-properties-of-nested-maps)
5. Click **Create map field**.
## Create map fields using API [#create-map-fields-using-api]
To create a map field using the Axiom API, send a request to the [Create map field](/restapi/endpoints/createMapField) endpoint. For example:
```bash
curl --request POST \
--url https://api.axiom.co/v2/datasets/DATASET_NAME/mapfields \
--header 'Authorization: Bearer API_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"name": "MAP_FIELD"
}'
```
Replace `MAP_FIELD` with the name of the field that you want to change to a map field.
Creating a map field affects the schema of the dataset. If the dataset’s schema is locked, Axiom automatically regenerates the locked schema when you create a map field so that the locked schema reflects the change. For more information, see [Lock dataset schema](/reference/datasets#lock-dataset-schema).
## View map fields [#view-map-fields]
To view map fields:
1. Go to the Datasets tab.
2. Select a dataset where you want to view map fields.
3. Map fields are labelled in the following way:
* **MAPPED** means that the field was previously an ordinary field but at some point its parent was changed to a map field. Axiom adds new events to the field as an attribute of the parent map field. Events you ingested before the change retain the ordinary structure.
* **UNUSED** means that the field is configured as a map field but you haven’t yet ingested data into it. Once ingested, data within this field won’t count toward your field limit.
* **REMOVED** means that the field was configured as a map field but at some point it was changed to an ordinary field. Axiom adds new events to the field as usual. Events you ingested before the change retain the map structure. To fully remove this field, first [trim your dataset](/reference/datasets#trim-dataset) to remove the time period when map data was ingested, and then [vacuum the fields](/reference/datasets#vacuum-fields).
## Access properties of nested maps [#access-properties-of-nested-maps]
To access the properties of nested maps, use index notation, dot notation, or a mix of the two. If you use index notation for an entity, enclose the entity name in quotation marks (`'` or `"`) and square brackets (`[]`). For example:
* `where ['map_field']['property1']['property2'] == 14`
* `where map_field.property1.property2 == 14`
* `where ['map_field'].property1.property2 == 14`
If an entity name has spaces (` `), dots (`.`), or dashes (`-`), you can only use index notation for that entity. You can use dot notation for the other entities. For example:
* `where ['map.field']['property.name1']['property.name2'] == 14`
* `where ['map.field'].property1.property2 == 14`
In OTel traces, custom attributes are located in the `attributes.custom` map field. You can access them as `['attributes.custom']['header.Accept']`, for example. In this case, you don’t access the `Accept` field nested within the `header` field. What actually happens is that you access the field named `header.Accept` within the `attributes.custom` map field.
For more information on quoting field names, see [Entity names](/apl/entities/entity-names#quote-identifiers).
## Map fields and flattened fields [#map-fields-and-flattened-fields]
Within a dataset, the same fields can exist as flattened fields and as subfields of a map field.
For example, consider the following:
1. `geo` is initially not a map field.
2. You ingest the following:
```json
{
"geo": {
"city": "Paris",
"country": "France"
}
}
```
This adds two flattened fields to the dataset that you can access as `['geo.city']` or `['geo.country']`.
3. You change `geo` to a map field through the UI or the API.
4. You ingest the following:
```json
{
"geo": {
"city": "Paris",
"country": "France"
}
}
```
You use the same ingest JSON as before, but this adds the new subfields to the `geo` parent map field. You can access the subfields as `['geo']['city']` and `['geo']['country']`.
Axiom treats the flattened fields (`['geo.city']` and `['geo.country']`) and the subfields of the map field (`['geo']['city']` and `['geo']['country']`) as separate fields and doesn’t maintain a relationship between them.
Queries using `['geo.city']` access a field literally named `geo.city`, while `['geo']['city']` accesses the `city` key inside a `geo` map. These references aren’t equivalent.
To avoid confusion:
* Choose either a flattened or map-based structure when designing your schema.
* Be explicit in queries about which fields to include or exclude.
---
# Null values
Source: https://axiom.co/docs/apl/data-types/null-values
All scalar data types in APL have a special value that represents a missing value. This value is called the null value, or null.
## Null literals [#null-literals]
The null value of a scalar type D is represented in the query language by the null literal D(null). The following query returns a single row full of null values:
```kusto
print bool(null), datetime(null), dynamic(null), int(null), long(null), real(null), double(null), time(null)
```
## Predicates on null values [#predicates-on-null-values]
The scalar function [isnull()](/apl/scalar-functions/string-functions#isnull\(\)) can be used to determine if a scalar value is the null value. The corresponding function [isnotnull()](/apl/scalar-functions/string-functions#isnotnull\(\)) can be used to determine if a scalar value isn’t the null value.
## Equality and inequality of null values [#equality-and-inequality-of-null-values]
* Equality (`==`): Applying the equality operator to two null values yields `bool(null)`. Applying the equality operator to a null value and a non-null value yields `bool(false)`.
* inequality(`!=`): Applying the inequality operator to two null values yields `bool(null)`. Applying the inequality operator to a null value and a non-null value yields `bool(true)`.
---
# Scalar data types
Source: https://axiom.co/docs/apl/data-types/scalar-data-types
Axiom Processing Language supplies a set of system data types that define all the types of data that can be used with APL.
The following table lists the data types supported by APL, alongside additional aliases you can use to refer to them.
| **Type** | **Additional names** | **gettype()** |
| ------------------------------------- | ----------------------------- | ------------------------------------------------------------ |
| [bool()](#the-bool-data-type) | **boolean** | **int8** |
| [datetime()](#the-datetime-data-type) | **date** | **datetime** |
| [dynamic()](#the-dynamic-data-type) | | **array** or **dictionary** or any other of the other values |
| [int()](#the-int-data-type) | **int** has an alias **long** | **int** |
| [long()](#the-long-data-type) | | **long** |
| [real()](#the-real-data-type) | **double** | **real** |
| [string()](#the-string-data-type) | | **string** |
| [timespan()](#the-timespan-data-type) | **time** | **timespan** |
## The bool data type [#the-bool-data-type]
The bool (boolean) data type can have one of two states: `true` or `false` (internally encoded as 1 and 0, respectively), as well as the null value.
### bool literals [#bool-literals]
The bool data type has the following literals:
* true and bool(true): Representing trueness
* false and bool(false): Representing falsehood
* null and bool(null): Representing the null value
### bool operators [#bool-operators]
The `bool` data type supports the following operators: equality (`==`), inequality (`!=`), logical-and (`and`), and logical-or (`or`).
## The datetime data type [#the-datetime-data-type]
The datetime (date) data type represents an instant in time, typically expressed as a date and time of day. Values range from 00:00:00 (midnight), January 1, 0001 Anno Domini (Common Era) through 11:59:59 PM, December 31, 9999 AD (CE) in the Gregorian calendar.
### datetime literals [#datetime-literals]
Literals of type **datetime** have the syntax **datetime** (`value`), where a number of formats are supported for value, as indicated by the following table:
| **Example** | **Value** |
| ----------------------------------------------------------------- | -------------------------------------------------------------- |
| **datetime(2019-11-30 23:59:59.9)** **datetime(2015-12-31)** | Times are always in UTC. Omitting the date gives a time today. |
| **datetime(null)** | Check out [null values](/apl/data-types/null-values) |
| **now()** | The current time. |
| **now(-timespan)** | now()-timespan |
| **ago(timespan)** | now()-timespan |
**now()** and **ago()** indicate a `datetime` value compared with the moment in time when APL started to execute the query.
### Supported formats [#supported-formats]
Axiom supports the [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format which is the standard format for representing dates and times in the Gregorian calendar.
| **Format** | **Example** |
| ------------------- | --------------------------- |
| %Y-%m-%dT%H:%M:%s%z | 2016-06-26T08:20:03.123456Z |
| %Y-%m-%dT%H:%M:%s | 2016-06-26T08:20:03.123456 |
| %Y-%m-%dT%H:%M | 2016-06-26T08:20 |
| %Y-%m-%d %H:%M:%s%z | 2016-10-06 15:55:55.123456Z |
| %Y-%m-%d %H:%M:%s | 2016-10-06 15:55:55 |
| %Y-%m-%d %H:%M | 2016-10-06 15:55 |
| %Y-%m-%d | 2014-11-08 |
## The dynamic data type [#the-dynamic-data-type]
The **dynamic** scalar data type is special in that it can take on any value of other scalar data types from the list below, as well as arrays and property bags. Specifically, a **dynamic** value can be:
* null
* A value of any of the primitive scalar data types: **bool**, **datetime**, **int**, **long**, **real**, **string**, and **timespan**.
* An array of **dynamic** values, holding zero or more values with zero-based indexing.
* A property bag, holding zero or more key-value pairs.
### Dynamic literals [#dynamic-literals]
A literal of type dynamic looks like this:
dynamic (`Value`)
Value can be:
* null, in which case the literal represents the null dynamic value: **dynamic(null)**.
* Another scalar data type literal, in which case the literal represents the **dynamic** literal of the "inner" type. For example, **dynamic(6)** is a dynamic value holding the value 6 of the long scalar data type.
* An array of dynamic or other literals: \[`ListOfValues`]. For example, dynamic(\[3, 4, "bye"]) is a dynamic array of three elements, two **long** values and one **string** value.
* A property bag: \{`Name`=`Value ...`}. For example, `dynamic(\{"a":1, "b":\{"a":2\}\})` is a property bag with two slots, a, and b, with the second slot being another property bag.
## The int data type [#the-int-data-type]
The **int** data type represents a signed, 64-bit wide, integer.
The special form **int(null)** represents the [null value.](/apl/data-types/null-values)
**int** has an alias **[long](/apl/data-types/scalar-data-types#the-long-data-type)**
## The long data type [#the-long-data-type]
The **long** data type represents a signed, 64-bit wide, integer.
### long literals [#long-literals]
Literals of the long data type can be specified in the following syntax:
long(`Value`)
Where Value can take the following forms:
* One more or digits, in which case the literal value is the decimal representation of these digits. For example, **long(11)** is the number eleven of type long.
* A minus (`-`) sign followed by one or more digits. For example, **long(-3)** is the number minus three of type **long**.
* null, in which case this is the [null value](/apl/data-types/null-values) of the **long** data type. Thus, the null value of type **long** is **long(null)**.
## The real data type [#the-real-data-type]
The **real** data type represents a 64-bit wide, double-precision, floating-point number.
## The string data type [#the-string-data-type]
The **string** data type represents a sequence of zero or more [Unicode](https://home.unicode.org/) characters.
### String literals [#string-literals]
There are several ways to encode literals of the **string** data type in a query text:
* Enclose the string in double-quotes(`"`): "This is a string literal. Single quote characters (') don’t require escaping. Double quote characters (") are escaped by a backslash (\\)"
* Enclose the string in single-quotes (`'`): Another string literal. Single quote characters (') require escaping by a backslash (\\). Double quote characters (") don’t require escaping.
In the two representations above, the backslash (`\`) character indicates escaping. The backslash is used to escape the enclosing quote characters, tab characters (`\t`), newline characters (`\n`), and itself (`\\`).
### Raw string literals [#raw-string-literals]
Raw string literals are also supported. In this form, the backslash character (`\`) stands for itself, and doesn’t denote an escape sequence.
* Enclosed in double-quotes (`""`): `@"This is a raw string literal"`
* Enclose in single-quotes (`'`): `@'This is a raw string literal'`
Raw strings are particularly useful for regexes where you can use `@"^[\d]+$"` instead of `"^[\\d]+$"`.
## The timespan data type [#the-timespan-data-type]
The **timespan** `(time)` data type represents a time interval.
## timespan literals [#timespan-literals]
Literals of type **timespan** have the syntax **timespan(value)**, where a number of formats are supported for value, as indicated by the following table:
| **Value** | **length of time** |
| --------------- | ------------------ |
| '`2d` | 2 days |
| `1.5h` | 1.5 hour |
| `30m` | 30 minutes |
| `10s` | 10 seconds |
| `timespan(15s)` | 15 seconds |
| `0.1s` | 0.1 second |
| `timespan(2d)` | 2 days |
## Type conversions [#type-conversions]
APL provides a set of functions to convert values between different scalar data types. These conversion functions allow you to convert a value from one type to another.
Some of the commonly used conversion functions include:
* `tobool()`: Converts input to boolean representation.
* `todatetime()`: Converts input to datetime scalar.
* `todouble()` or `toreal()`: Converts input to a value of type real.
* `tostring()`: Converts input to a string representation.
* `totimespan()`: Converts input to timespan scalar.
* `tolong()`: Converts input to long (signed 64-bit) number representation.
* `toint()`: Converts input to an integer value (signed 64-bit) number representation.
For a complete list of conversion functions and their detailed descriptions and examples, refer to the [Conversion functions](/apl/scalar-functions/conversion-functions) documentation.
---
# Entity names
Source: https://axiom.co/docs/apl/entities/entity-names
APL entities (datasets, tables, columns, and operators) are named. For example, two fields or columns in the same dataset can have the same name if the casing is different, and a table and a dataset may have the same name because they aren’t in the same scope.
## Columns [#columns]
* Column names are case-sensitive for resolving purposes and they have a specific position in the dataset’s collection of columns.
* Column names are unique within a dataset and table.
* In queries, columns are generally referenced by name only. They can only appear in expressions, and the query operator under which the expression appears determines the table or tabular data stream.
## Identifier naming rules [#identifier-naming-rules]
Axiom uses identifiers to name various entities. Valid identifier names follow these rules:
* Between 1 and 1024 characters long.
* Allowed characters:
* Alphanumeric characters (letters and digits)
* Underscore (`_`)
* Space (` `)
* Dot (`.`)
* Dash (`-`)
Identifier names are case-sensitive.
## Quote identifiers [#quote-identifiers]
Quote an identifier in your APL query if any of the following is true:
* The identifier name contains at least one of the following special characters:
* Space (` `)
* Dot (`.`)
* Dash (`-`)
* The identifier name is identical to one of the reserved keywords of the APL query language. For example, `project` or `where`.
If any of the above is true, you must quote the identifier by enclosing it in quotation marks (`'` or `"`) and square brackets (`[]`). For example, `['my-field']`.
If none of the above is true, you don’t need to quote the identifier in your APL query. For example, `myfield`. In this case, quoting the identifier name is optional.
---
# Migrate from SQL to APL
Source: https://axiom.co/docs/apl/guides/migrating-from-sql-to-apl
## Introduction [#introduction]
As data grows exponentially, organizations are continuously seeking more efficient and powerful tools to manage and analyze their data. The Query tab, which utilizes the Axiom Processing Language (APL), is one such service that offers fast, scalable, and interactive data exploration capabilities.
This tutorial helps you migrate SQL to APL, helping you understand key differences and providing you with query examples.
## Introduction to Axiom Processing Language (APL) [#introduction-to-axiom-processing-language-apl]
Axiom Processing Language (APL) is the language used by the Query tab, a fast and highly scalable data exploration service. APL is optimized for real-time and historical data analytics, making it a suitable choice for various data analysis tasks.
**Tabular operators**: In APL, there are several tabular operators that help you manipulate and filter data, similar to SQL’s SELECT, FROM, WHERE, GROUP BY, and ORDER BY clauses. Some of the commonly used tabular operators are:
* `extend`: Adds new columns to the result set.
* `project`: Selects specific columns from the result set.
* `where`: Filters rows based on a condition.
* `summarize`: Groups and aggregates data similar to the GROUP BY clause in SQL.
* `sort`: Sorts the result set based on one or more columns, similar to ORDER BY in SQL.
## Key differences between SQL and APL [#key-differences-between-sql-and-apl]
While SQL and APL are query languages, there are some key differences to consider:
* APL is designed for querying large volumes of structured, semi-structured, and unstructured data.
* APL is a pipe-based language, meaning you can chain multiple operations using the pipe operator (`|`) to create a data transformation flow.
* APL doesn’t use SELECT, and FROM clauses like SQL. Instead, it uses keywords such as summarize, extend, where, and project.
* APL is case-sensitive, whereas SQL isn’t.
## Benefits of migrating from SQL to APL: [#benefits-of-migrating-from-sql-to-apl]
* **Time Series Analysis:** APL is particularly strong when it comes to analyzing time-series data (logs, telemetry data, etc.). It has a rich set of operators designed specifically for such scenarios, making it much easier to handle time-based analysis.
* **Pipelining:** APL uses a pipelining model, much like the UNIX command line. You can chain commands together using the pipe (`|`) symbol, with each command operating on the results of the previous command. This makes it very easy to write complex queries.
* **Easy to Learn:** APL is designed to be simple and easy to learn, especially for those already familiar with SQL. It doesn’t require any knowledge of database schemas or the need to specify joins.
* **Scalability:** APL is a more scalable platform than SQL. This means that it can handle larger amounts of data.
* **Flexibility:** APL is a more flexible platform than SQL. This means that it can be used to analyze different types of data.
* **Features:** APL offers more features and capabilities than SQL. This includes features such as real-time analytics, and time-based analysis.
## Basic APL Syntax [#basic-apl-syntax]
A basic APL query follows this structure:
```kusto
|
|
|
|
```
## Query Examples [#query-examples]
Let’s see some examples of how to convert SQL queries to APL.
## SELECT with a simple filter [#select-with-a-simple-filter]
**SQL:**
```sql
SELECT *
FROM [Sample-http-logs]
WHERE method = 'GET';
```
**APL:**
```kusto
['sample-http-logs']
| where method == 'GET'
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20where%20method%20==%20%27GET%27%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## COUNT with GROUP BY [#count-with-group-by]
**SQL:**
```sql
SELECT Country, COUNT(*)
FROM [Sample-http-logs]
GROUP BY method;
```
**APL:**
```kusto
['sample-http-logs']
| summarize count() by method
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20summarize%20count\(\)%20by%20method%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Top N results [#top-n-results]
**SQL:**
```sql
SELECT TOP 10 Status, Method
FROM [Sample-http-logs]
ORDER BY Method DESC;
```
**APL:**
```kusto
['sample-http-logs']
| top 10 by method desc
| project status, method
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|top%2010%20by%20method%20desc%20\n|%20project%20status,%20method%22,%22queryOptions%22:\{%22quickRange%22:%2215d%22}})
## Simple filtering and projection [#simple-filtering-and-projection]
**SQL:**
```sql
SELECT method, status, geo.country
FROM [Sample-http-logs]
WHERE resp_header_size_bytes >= 18;
```
**APL:**
```kusto
['sample-http-logs']
| where resp_header_size_bytes >= 18
| project method, status, ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|where%20resp_header_size_bytes%20%3E=18%20\n|%20project%20method,%20status,%20\[%27geo.country%27]%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## COUNT with a HAVING clause [#count-with-a-having-clause]
**SQL:**
```sql
SELECT geo.country
FROM [Sample-http-logs]
GROUP BY geo.country
HAVING COUNT(*) > 100;
```
**APL:**
```kusto
['sample-http-logs']
| summarize count() by ['geo.country']
| where count_ > 100
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20summarize%20count\(\)%20by%20\[%27geo.country%27]\n|%20where%20count_%20%3E%20100%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Multiple Aggregations [#multiple-aggregations]
**SQL:**
```sql
SELECT geo.country,
COUNT(*) AS TotalRequests,
AVG(req_duration_ms) AS AverageRequest,
MIN(req_duration_ms) AS MinRequest,
MAX(req_duration_ms) AS MaxRequest
FROM [Sample-http-logs]
GROUP BY geo.country;
```
**APL:**
```kusto
Users
| summarize TotalRequests = count(),
AverageRequest = avg(req_duration_ms),
MinRequest = min(req_duration_ms),
MaxRequest = max(req_duration_ms) by ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20summarize%20totalRequests%20=%20count\(\),%20Averagerequest%20=%20avg\(req_duration_ms\),%20MinRequest%20=%20min\(req_duration_ms\),%20MaxRequest%20=%20max\(req_duration_ms\)%20by%20\[%27geo.country%27]%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
### Sum of a column [#sum-of-a-column]
**SQL:**
```sql
SELECT SUM(resp_body_size_bytes) AS TotalBytes
FROM [Sample-http-logs];
```
**APL:**
```kusto
[‘sample-http-logs’]
| summarize TotalBytes = sum(resp_body_size_bytes)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20summarize%20TotalBytes%20=%20sum\(resp_body_size_bytes\)%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
### Average of a column [#average-of-a-column]
**SQL:**
```sql
SELECT AVG(req_duration_ms) AS AverageRequest
FROM [Sample-http-logs];
```
**APL:**
```kusto
['sample-http-logs']
| summarize AverageRequest = avg(req_duration_ms)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20summarize%20AverageRequest%20=%20avg\(req_duration_ms\)%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Minimum and Maximum Values of a column [#minimum-and-maximum-values-of-a-column]
**SQL:**
```sql
SELECT MIN(req_duration_ms) AS MinRequest, MAX(req_duration_ms) AS MaxRequest
FROM [Sample-http-logs];
```
**APL:**
```kusto
['sample-http-logs']
| summarize MinRequest = min(req_duration_ms), MaxRequest = max(req_duration_ms)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20summarize%20MinRequest%20=%20min\(req_duration_ms\),%20MaxRequest%20=%20max\(req_duration_ms\)%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Count distinct values [#count-distinct-values]
**SQL:**
```sql
SELECT COUNT(DISTINCT method) AS UniqueMethods
FROM [Sample-http-logs];
```
**APL:**
```kusto
['sample-http-logs']
| summarize UniqueMethods = dcount(method)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|summarize%20UniqueMethods%20=%20dcount\(method\)%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Standard deviation of a data [#standard-deviation-of-a-data]
**SQL:**
```sql
SELECT STDDEV(req_duration_ms) AS StdDevRequest
FROM [Sample-http-logs];
```
**APL:**
```kusto
['sample-http-logs']
| summarize StdDevRequest = stdev(req_duration_ms)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20summarize%20stdDEVRequest%20=%20stdev\(req_duration_ms\)%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Variance of a data [#variance-of-a-data]
**SQL:**
```sql
SELECT VAR(req_duration_ms) AS VarRequest
FROM [Sample-http-logs];
```
**APL:**
```kusto
['sample-http-logs']
| summarize VarRequest = variance(req_duration_ms)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20summarize%20VarRequest%20=%20variance\(req_duration_ms\)%22,%22queryOptions%22:\{%22quickRange%22:%2215d%22}})
## Multiple aggregation functions [#multiple-aggregation-functions]
**SQL:**
```sql
SELECT COUNT(*) AS TotalDuration, SUM(req_duration_ms) AS TotalDuration, AVG(Price) AS AverageDuration
FROM [Sample-http-logs];
```
**APL:**
```kusto
['sample-http-logs']
| summarize TotalOrders = count(), TotalDuration = sum( req_duration_ms), AverageDuration = avg(req_duration_ms)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20summarize%20TotalOrders%20=%20count\(\),%20TotalDuration%20=%20sum\(req_duration_ms\),%20AverageDuration%20=%20avg\(req_duration_ms\)%20%22,%22queryOptions%22:\{%22quickRange%22:%2215d%22}})
## Aggregation with GROUP BY and ORDER BY [#aggregation-with-group-by-and-order-by]
**SQL:**
```sql
SELECT status, COUNT(*) AS TotalStatus, SUM(resp_header_size_bytes) AS TotalRequest
FROM [Sample-http-logs];
GROUP BY status
ORDER BY TotalSpent DESC;
```
**APL:**
```kusto
['sample-http-logs']
| summarize TotalStatus = count(), TotalRequest = sum(resp_header_size_bytes) by status
| order by TotalRequest desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20summarize%20TotalStatus%20=%20count\(\),%20TotalRequest%20=%20sum\(resp_header_size_bytes\)%20by%20status\n|%20order%20by%20TotalRequest%20desc%20%22,%22queryOptions%22:\{%22quickRange%22:%2215d%22}})
## Count with a condition [#count-with-a-condition]
**SQL:**
```sql
SELECT COUNT(*) AS HighContentStatus
FROM [Sample-http-logs];
WHERE resp_header_size_bytes > 1;
```
**APL:**
```kusto
['sample-http-logs']
| where resp_header_size_bytes > 1
| summarize HighContentStatus = count()
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20where%20resp_header_size_bytes%20%3E%201\n|%20summarize%20HighContentStatus%20=%20count\(\)%20%20%20%22,%22queryOptions%22:\{%22quickRange%22:%2215d%22}})
## Aggregation with HAVING [#aggregation-with-having]
**SQL:**
```sql
SELECT Status
FROM [Sample-http-logs];
GROUP BY Status
HAVING COUNT(*) > 10;
```
**APL:**
```kusto
['sample-http-logs']
| summarize OrderCount = count() by status
| where OrderCount > 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20summarize%20OrderCount%20=%20count\(\)%20by%20status\n|%20where%20OrderCount%20%3E%2010%20%20%20%22,%22queryOptions%22:\{%22quickRange%22:%2215d%22}})
## Count occurrences of a value in a field [#count-occurrences-of-a-value-in-a-field]
**SQL:**
```sql
SELECT content_type, COUNT(*) AS RequestCount
FROM [Sample-http-logs];
WHERE content_type = ‘text/csv’;
```
**APL:**
```kusto
['sample-http-logs'];
| where content_type == 'text/csv'
| summarize RequestCount = count()
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20where%20content_type%20==%20%27text/csv%27%20\n|%20summarize%20RequestCount%20=%20count\(\)%20%20%20%22,%22queryOptions%22:\{%22quickRange%22:%2215d%22}})
## String Functions: [#string-functions]
## Length of a string [#length-of-a-string]
**SQL:**
```sql
SELECT LEN(Status) AS NameLength
FROM [Sample-http-logs];
```
**APL:**
```kusto
['sample-http-logs']
| extend NameLength = strlen(status)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20NameLength%20=%20strlen\(status\)%20%22,%22queryOptions%22:\{%22quickRange%22:%2215d%22}})
## Concatentation [#concatentation]
**SQL:**
```sql
SELECT CONCAT(content_type, ' ', method) AS FullLength
FROM [Sample-http-logs];
```
**APL:**
```kusto
['sample-http-logs']
| extend FullLength = strcat(content_type, ' ', method)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20FullLength%20=%20strcat\(content_type,%20%27%20%27,%20method\)%20%20%22,%22queryOptions%22:\{%22quickRange%22:%2215d%22}})
## Substring [#substring]
**SQL:**
```sql
SELECT SUBSTRING(content_type, 1, 10) AS ShortDescription
FROM [Sample-http-logs];
```
**APL:**
```kusto
['sample-http-logs']
| extend ShortDescription = substring(content_type, 0, 10)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20ShortDescription%20=%20substring\(content_type,%200,%2010\)%20%22,%22queryOptions%22:\{%22quickRange%22:%2215d%22}})
## Left and Right [#left-and-right]
**SQL:**
```sql
SELECT LEFT(content_type, 3) AS LeftTitle, RIGHT(content_type, 3) AS RightTitle
FROM [Sample-http-logs];
```
**APL:**
```kusto
['sample-http-logs']
| extend LeftTitle = substring(content_type, 0, 3), RightTitle = substring(content_type, strlen(content_type) - 3, 3)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20LeftTitle%20=%20substring\(content_type,%200,%203\),%20RightTitle%20=%20substring\(content_type,%20strlen\(content_type\)%20-%203,%203\)%20%20%22,%22queryOptions%22:\{%22quickRange%22:%2215d%22}})
## Replace [#replace]
**SQL:**
```sql
SELECT REPLACE(StaTUS, 'old', 'new') AS UpdatedStatus
FROM [Sample-http-logs];
```
**APL:**
```kusto
['sample-http-logs']
| extend UpdatedStatus = replace('old', 'new', status)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20UpdatedStatus%20=%20replace\(%27old%27,%20%27new%27,%20status\)%20%20%22,%22queryOptions%22:\{%22quickRange%22:%2215d%22}})
## Upper and Lower [#upper-and-lower]
**SQL:**
```sql
SELECT UPPER(FirstName) AS UpperFirstName, LOWER(LastName) AS LowerLastName
FROM [Sample-http-logs];
```
**APL:**
```kusto
['sample-http-logs']
| project upperFirstName = toupper(content_type), LowerLastNmae = tolower(status)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20project%20upperFirstName%20=%20toupper\(content_type\),%20LowerLastNmae%20=%20tolower\(status\)%20%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## LTrim and RTrim [#ltrim-and-rtrim]
**SQL:**
```sql
SELECT LTRIM(content_type) AS LeftTrimmedFirstName, RTRIM(content_type) AS RightTrimmedLastName
FROM [Sample-http-logs];
```
**APL:**
```kusto
['sample-http-logs']
| extend LeftTrimmedFirstName = trim_start(' ', content_type), RightTrimmedLastName = trim_end(' ', content_type)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20project%20LeftTrimmedFirstName%20=%20trim_start\(%27%27,%20content_type\),%20RightTrimmedLastName%20=%20trim_end\(%27%27,%20content_type\)%20%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Trim [#trim]
**SQL:**
```sql
SELECT TRIM(content_type) AS TrimmedFirstName
FROM [Sample-http-logs];
```
**APL:**
```kusto
['sample-http-logs']
| extend TrimmedFirstName = trim(' ', content_type)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20TrimmedFirstName%20=%20trim\(%27%20%27,%20content_type\)%20%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Reverse [#reverse]
**SQL:**
```sql
SELECT REVERSE(Method) AS ReversedFirstName
FROM [Sample-http-logs];
```
**APL:**
```kusto
['sample-http-logs']
| extend ReversedFirstName = reverse(method)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20project%20ReservedFirstnName%20=%20reverse\(method\)%20%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Case-insensitive search [#case-insensitive-search]
**SQL:**
```sql
SELECT Status, Method
FROM “Sample-http-logs”
WHERE LOWER(Method) LIKE 'get’';
```
**APL:**
```kusto
['sample-http-logs']
| where tolower(method) contains 'GET'
| project status, method
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20where%20tolower\(method\)%20contains%20%27GET%27\n|%20project%20status,%20method%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Take the First Step Today: Dive into APL [#take-the-first-step-today-dive-into-apl]
The journey from SQL to APL might seem daunting at first, but with the right approach, it can become an empowering transition. It’s about expanding your data query capabilities to leverage the advanced, versatile, and fast querying infrastructure that APL provides. In the end, the goal is to enable you to draw more value from your data, make faster decisions, and ultimately propel your business forward.
Try converting some of your existing SQL queries to APL and observe the performance difference. Explore the Axiom Processing Language and start experimenting with its unique features.
**Happy querying.**
---
# Migrate from Sumo Logic Query Language to APL
Source: https://axiom.co/docs/apl/guides/migrating-from-sumologic-to-apl
## Introduction [#introduction]
In the sphere of data analytics and log management, being able to query data efficiently and effectively is of paramount importance.
This guide dives into why APL could be a superior choice for your data needs, the differences between Sumo Logic and APL, and the potential benefits you could reap from migrating from Sumo Logic to APL. Let’s explore the compelling case for APL as a robust, powerful tool for handling your complex data querying requirements.
APL is powerful and flexible and uses a pipe (`|`) operator for chaining commands, and it provides a richer set of functions and operators for more complex queries.
## Benefits of Migrating from SumoLogic to APL [#benefits-of-migrating-from-sumologic-to-apl]
* **Scalability and Performance:** APL was built with scalability in mind. It handles very large volumes of data more efficiently and provides quicker query execution compared to Sumo Logic, making it a suitable choice for organizations with extensive data requirements. APL is designed for high-speed data ingestion, real-time analytics, and providing insights across structured, semi-structured data. It’s also optimized for time-series data analysis, making it highly efficient for log and telemetry data.
* **Advanced Analytics Capabilities:** With APL’s support for aggregation and conversion functions and more advanced statistical visualization, organizations can derive more sophisticated insights from their data.
## Query Examples [#query-examples]
Let’s see some examples of how to convert SumoLogic queries to APL.
## Parse, and Extract Operators [#parse-and-extract-operators]
Extract `from` and `to` fields. For example, if a raw event contains `From: Jane To: John,` then `from=Jane and to=John.`
**Sumo Logic:**
```bash
* | parse "From: * To: *" as (from, to)
```
**APL:**
```kusto
['sample-http-logs']
| extend (method) == extract("From: (.*?) To: (.*)", 1, method)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20\(method\)%20==%20extract\(%22From:%20\(.*?\)%20To:%20\(.*\)%22,%201,%20method\)%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Extract Source IP with Regex [#extract-source-ip-with-regex]
In this section, a regular expression identifies the four octets of an IP address. This helps you efficiently extract the source IP addresses from the data.
**Sumo Logic:**
```bash
*| parse regex "(\\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"
```
**APL:**
```kusto
['sample-http-logs']
| extend ip = extract("(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})", 1, "23.45.67.90")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20ip%20=%20extract\(%22\(\\\d\{1,3}\\\\.\\\d\{1,3}\\\\.\\\d\{1,3}\\\\.\\\d\{1,3}\)%22,%201,%20%2223.45.67.90%22\)%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Extract Visited URLs [#extract-visited-urls]
This section focuses on identifying all URL addresses visited and extracting them to populate the "url" field. This method provides an organized way to track user activity using APL.
**Sumo Logic:**
```bash
_sourceCategory=apache
| parse "GET * " as url
```
**APL:**
```kusto
['sample-http-logs']
| where method == "GET"
| project url = extract(@"(\w+)", 1, method)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%5Cn%7C%20where%20method%20%3D%3D%20%5C%22GET%5C%22%5Cn%7C%20project%20url%20%3D%20extract\(%40%5C%22\(%5C%5Cw%2B\)%5C%22%2C%201%2C%20method\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
## Extract Data from Source Category Traffic [#extract-data-from-source-category-traffic]
This section aims to identify and analyze traffic originating from the Source Category. It extracts critical information including the source addresses, the sizes of messages transmitted, and the URLs visited, providing valuable insights into the nature of the traffic using APL.
**Sumo Logic:**
```bash
_sourceCategory=apache
| parse "* " as src_IP
| parse " 200 * " as size
| parse "GET * " as url
```
**APL:**
```kusto
['sample-http-logs']
| extend src_IP = extract("^(\\S+)", 0, uri)
| extend size = extract("^(\\S+)", 1, status)
| extend url = extract("^(\\S+)", 1, method)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20src_IP%20%3D%20extract\(%5C%22%5E\(%40S%2B\)%5C%22%2C%200%2C%20uri\)%5Cn%7C%20extend%20size%20%3D%20extract\(%5C%22%5E\(%40S%2B\)%5C%22%2C%201%2C%20status\)%5Cn%7C%20extend%20url%20%3D%20extract\(%5C%22%5E\(%40S%2B\)%5C%22%2C%201%2C%20method\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
## Calculate Bytes Transferred per Source IP [#calculate-bytes-transferred-per-source-ip]
In this part, compute the total number of bytes transferred to each source IP address. This allows you to gauge the data volume associated with each source using APL.
**Sumo Logic:**
```bash
_sourceCategory=apache
| parse "* " as src_IP
| parse " 200 * " as size
| count, sum(size) by src_IP
```
**APL:**
```kusto
['sample-http-logs']
| extend src_IP = extract("^(\\S+)", 1, uri)
| extend size = toint(extract("200", 0, status))
| summarize count(), sum(size) by src_IP
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20size%20=%20toint\(extract\(%22200%22,%200,%20status\)\)\n|%20summarize%20count\(\),%20sum\(size\)%20by%20status%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Compute Average HTTP Response Size [#compute-average-http-response-size]
In this section, calculate the average size of all successful HTTP responses. This metric helps you understand the typical data load associated with successful server responses.
**Sumo Logic:**
```bash
_sourceCategory=apache
| parse " 200 * " as size
| avg(size)
```
**APL:**
Get the average value from a string:
```kusto
['sample-http-logs']
| extend number = todouble(extract("\\d+(\\.\\d+)?", 0, status))
| summarize Average = avg(number)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20number%20=%20todouble\(status\)\n|%20summarize%20Average%20=%20avg\(number\)%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Extract Data with Missing Size Field (NoDrop) [#extract-data-with-missing-size-field-nodrop]
This section focuses on extracting key parameters like `src`, `size`, and `URL`, even when the `size` field may be absent from the log message.
**Sumo Logic:**
```bash
_sourceCategory=apache
| parse "* " as src_IP
| parse " 200 * " as size nodrop
| parse "GET * " as url
```
**APL:**
```kusto
['sample-http-logs']
| where content_type == "text/css"
| extend src_IP = extract("^(\\S+)", 1, ['id'])
| extend size = toint(extract("(\\w+)", 1, status))
| extend url = extract("GET", 0, method)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20where%20content_type%20%3D%3D%20%5C%22text%2Fcss%5C%22%20%7C%20extend%20src_IP%20%3D%20extract\(%5C%22%5E\(%5C%5CS%2B\)%5C%22%2C%201%2C%20%5B%27id%27%5D\)%20%7C%20extend%20size%20%3D%20toint\(extract\(%5C%22\(%5C%5Cw%2B\)%5C%22%2C%201%2C%20status\)\)%20%7C%20extend%20url%20%3D%20extract\(%5C%22GET%5C%22%2C%200%2C%20method\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
## Count URL Visits [#count-url-visits]
This section is dedicated to identifying the frequency of visits to a specific URL. By counting these occurrences, you can gain insights into website popularity and user behavior.
**Sumo Logic:**
```bash
_sourceCategory=apache
| parse "GET * " as url
| count by url
```
**APL:**
```kusto
['sample-http-logs']
| extend url = extract("^(\\S+)", 1, method)
| summarize Count = count() by url
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?qid=RsnK4jahgNC-rviz3s)
## Page Count by Source IP [#page-count-by-source-ip]
In this section, identify the total number of pages associated with each source IP address. This analysis allows you to understand the volume of content generated or hosted by each source.
**Sumo Logic:**
```bash
_sourceCategory=apache
| parse "* -" as src_ip
| count by src_ip
```
**APL:**
```kusto
['sample-http-logs']
| extend src_ip = extract(".*", 0, ['id'])
| summarize Count = count() by src_ip
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20src_ip%20=%20extract\(%22.*%22,%200,%20%20\[%27id%27]\)\n|%20summarize%20Count%20=%20count\(\)%20by%20src_ip%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Reorder Pages by Load Frequency [#reorder-pages-by-load-frequency]
This section aims to identify the total number of pages per source IP address. Following this, the pages will be reordered based on the frequency of loads, which will provide insights into the most accessed content.
**Sumo Logic:**
```bash
_sourceCategory=apache
| parse "* " as src_ip
| parse "GET * " as url
| count by url
| sort by _count
```
**APL:**
```kusto
['sample-http-logs']
| extend src_ip = extract(".*", 0, ['id'])
| extend url = extract("(GET)", 1, method)
| where isnotnull(url)
| summarize _count = count() by url, src_ip
| order by _count desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20src_ip%20=%20extract\(%22.*%22,%200,%20\[%27id%27]\)\n|%20extend%20url%20=%20extract\(%22\(GET\)%22,%201,%20method\)\n|%20where%20isnotnull\(url\)\n|%20summarize%20_count%20=%20count\(\)%20by%20url,%20src_ip\n|%20order%20by%20_count%20desc%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Identify the top 10 requested pages [#identify-the-top-10-requested-pages]
**Sumo Logic:**
```bash
* | parse "GET * " as url
| count by url
| top 10 url by _count
```
**APL:**
```kusto
['sample-http-logs']
| where method == "GET"
| top 10 by method desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20where%20method%20==%20%22GET%22\n|%20top%2010%20by%20method%20desc%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Top 10 IPs by Bandwidth Usage [#top-10-ips-by-bandwidth-usage]
This section aims to identify the top 10 source IP addresses based on their bandwidth consumption.
**Sumo Logic:**
```bash
_sourceCategory=apache
| parse " 200 * " as size
| parse "* -" as src_ip
| sum(size) as total_bytes by src_ip
| top 10 src_ip by total_bytes
```
**APL:**
```kusto
['sample-http-logs']
| extend size = req_duration_ms
| summarize total_bytes = sum(size) by ['id']
| top 10 by total_bytes desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20size%20=%20req_duration_ms\n|%20summarize%20total_bytes%20=%20sum\(size\)%20by%20\[%27id%27]\n|%20top%2010%20by%20total_bytes%20desc%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Top 6 IPs by Number of Hits [#top-6-ips-by-number-of-hits]
This section focuses on identifying the top six source IP addresses according to the number of hits they generate. This will provide insight into the most frequently accessed or active sources in the network.
**Sumo Logic**
```bash
_sourceCategory=apache
| parse "* -" as src_ip
| count by src_ip
| top 100 src_ip by _count
```
**APL:**
```kusto
['sample-http-logs']
| extend src_ip = extract("^(\\S+)", 1, user_agent)
| summarize _count = count() by src_ip
| top 6 by _count desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20summarize%20_count%20=%20count\(\)%20by%20user_agent\n|%20order%20by%20_count%20desc\n|%20limit%206%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Timeslice and Transpose [#timeslice-and-transpose]
For the Source Category `"apache"`, count by status\_code and timeslice of 1 hour.
**Sumo Logic:**
```bash
_sourceCategory=apache*
| parse "HTTP/1.1\" * * \"" as (status_code, size)
| timeslice 1h
| count by _timeslice, status_code
```
**APL:**
```kusto
['sample-http-logs']
| extend status_code = extract("^(\\S+)", 1, method)
| where status_code == "POST"
| summarize count() by status_code, bin(_time, 1h)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20where%20method%20==%20%22POST%22\n|%20summarize%20count\(\)%20by%20method,%20bin\(_time,%201h\)%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Hourly Status Code Count for "Text" Source [#hourly-status-code-count-for-text-source]
This section aims to count instances by `status_code`, grouped into one-hour timeslices, and then transpose `status_code` to column format. This helps you understand the frequency and timing of different status codes.
**Sumo Logic:**
```bash
_sourceCategory=text*
| parse "HTTP/1.1\" * * \"" as (status_code, size)
| timeslice 1h
| count by _timeslice, status_code
| transpose row _timeslice column status_code
```
**APL:**
```
['sample-http-logs']
| where content_type startswith 'text/css'
| extend status_code= status
| summarize count() by bin(_time, 1h), content_type, status_code
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20where%20content_type%20startswith%20%27text/css%27\n|%20extend%20status_code%20=%20status\n|%20summarize%20count\(\)%20by%20bin\(_time,%201h\),%20content_type,%20status_code%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Status Code Count in 5 Time Buckets [#status-code-count-in-5-time-buckets]
This example performs a count by 'status\_code', sliced into five time buckets across the search results. This will help analyze the distribution and frequency of status codes over specific time intervals.
**Sumo Logic:**
```bash
_sourceCategory=apache*
| parse "HTTP/1.1\" * * \"" as (status_code, size)
| timeslice 5 buckets
| count by _timeslice, status_code
```
**APL:**
```kusto
['sample-http-logs']
| where content_type startswith 'text/css'
| extend p=("HTTP/1.1\" * * \""), tostring( is_tls)
| extend status_code= status
| summarize count() by bin(_time, 12m), status_code
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20where%20content_type%20startswith%20%27text/css%27\n|%20extend%20p=\(%22HTTP/1.1\\%22%20*%20*%20\\%22%22\),%20tostring\(is_tls\)\n|%20extend%20status_code%20=%20status\n|%20summarize%20count\(\)%20by%20bin\(_time,%2012m\),%20status_code%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Grouped Status Code Count [#grouped-status-code-count]
This example counts messages by status code categories. It groups all messages with status codes in the `200s`, `300s`, `400s`, and `500s` together, and also groups the method requests with the `GET`, `POST`, `PUT`, `DELETE` attributes. This provides an overview of the response status distribution.
**Sumo Logic:**
```bash
_sourceCategory=Apache/Access
| timeslice 15m
| if (status_code matches "20*",1,0) as resp_200
| if (status_code matches "30*",1,0) as resp_300
| if (status_code matches "40*",1,0) as resp_400
| if (status_code matches "50*",1,0) as resp_500
| if (!(status_code matches "20*" or status_code matches "30*" or status_code matches "40*" or status_code matches "50*"),1,0) as resp_others
| count(*), sum(resp_200) as tot_200, sum(resp_300) as tot_300, sum(resp_400) as tot_400, sum(resp_500) as tot_500, sum(resp_others) as tot_others by _timeslice
```
**APL:**
```kusto
['sample-http-logs']
| extend MethodCategory = case(
method == "GET", "GET Requests",
method == "POST", "POST Requests",
method == "PUT", "PUT Requests",
method == "DELETE", "DELETE Requests",
"Other Methods")
| extend StatusCodeCategory = case(
status startswith "2", "Success",
status startswith "3", "Redirection",
status startswith "4", "Client Error",
status startswith "5", "Server Error",
"Unknown Status")
| extend ContentTypeCategory = case(
content_type == "text/csv", "CSV",
content_type == "application/json", "JSON",
content_type == "text/html", "HTML",
"Other Types")
| summarize Count=count() by bin_auto(_time), StatusCodeCategory, MethodCategory, ContentTypeCategory
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20MethodCategory%20=%20case\(\n%20%20%20method%20==%20%22GET%22,%20%22GET%20Requests%22,\n%20%20%20method%20==%20%22POST%22,%20%22POST%20Requests%22,\n%20%20%20method%20==%20%22PUT%22,%20%22PUT%20Requests%22,\n%20%20%20method%20==%20%22DELETE%22,%20%22DELETE%20Requests%22,\n%20%20%20%22Other%20Methods%22\)\n|%20extend%20StatusCodeCategory%20=%20case\(\n%20%20%20status%20startswith%20%222%22,%20%22Success%22,\n%20%20%20status%20startswith%20%223%22,%20%22Redirection%22,\n%20%20%20status%20startswith%20%224%22,%20%22Client%20Error%22,\n%20%20%20status%20startswith%20%225%22,%20%22Server%20Error%22,\n%20%20%20%22Unknown%20Status%22\)\n|%20extend%20ContentTypeCategory%20=%20case\(\n%20%20%20content_type%20==%20%22text/csv%22,%20%22CSV%22,\n%20%20%20content_type%20==%20%22application/json%22,%20%22JSON%22,\n%20%20%20content_type%20==%20%22text/html%22,%20%22HTML%22,\n%20%20%20%22Other%20Types%22\)\n|%20summarize%20Count=count\(\)%20by%20bin_auto\(_time\),%20StatusCodeCategory,%20MethodCategory,%20ContentTypeCategory%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Conditional Operators [#conditional-operators]
For the Source Category `"apache"`, find all messages with a client error status code (40\*):
**Sumo Logic:**
```bash
_sourceCategory=apache*
| parse "HTTP/1.1\" * * \"" as (status_code, size)
| where status_code matches "40*"
```
**APL:**
```kusto
['sample-http-logs']
| where content_type startswith 'text/css'
| extend p = ("HTTP/1.1\" * * \"")
| where status == "200"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20where%20content_type%20startswith%20%27text/css%27\n|%20extend%20p%20=%20\(%22HTTP/1.1\\%22%20*%20*%20\\%22%22\)\n|%20where%20status%20==%20%22200%22%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Browser-based Hit Count [#browser-based-hit-count]
This query example counts the number of hits by browser. This analysis provides insights into the different browsers used to access the source and their respective frequencies.
**Sumo Logic:**
```bash
_sourceCategory=Apache/Access
| extract "\"[A-Z]+ \S+ HTTP/[\d\.]+\" \S+ \S+ \S+ \"(?[^\"]+?)\""
| if (agent matches "*MSIE*",1,0) as ie
| if (agent matches "*Firefox*",1,0) as firefox
| if (agent matches "*Safari*",1,0) as safari
| if (agent matches "*Chrome*",1,0) as chrome
| sum(ie) as ie, sum(firefox) as firefox, sum(safari) as safari, sum(chrome) as chrome
```
**APL:**
```kusto
['sample-http-logs']
| extend ie = case(tolower(user_agent) contains "msie", 1, 0)
| extend firefox = case(tolower(user_agent) contains "firefox", 1, 0)
| extend safari = case(tolower(user_agent) contains "safari", 1, 0)
| extend chrome = case(tolower(user_agent) contains "chrome", 1, 0)
| summarize data = sum(ie), lima = sum(firefox), lo = sum(safari), ce = sum(chrome)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20ie%20=%20case\(tolower\(user_agent\)%20contains%20%22msie%22,%201,%200\)\n|%20extend%20firefox%20=%20case\(tolower\(user_agent\)%20contains%20%22firefox%22,%201,%200\)\n|%20extend%20safari%20=%20case\(tolower\(user_agent\)%20contains%20%22safari%22,%201,%200\)\n|%20extend%20chrome%20=%20case\(tolower\(user_agent\)%20contains%20%22chrome%22,%201,%200\)\n|%20summarize%20data%20=%20sum\(ie\),%20lima%20=%20sum\(firefox\),%20lo%20=%20sum\(safari\),%20ce%20=%20sum\(chrome\)%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Use the where operator to match only weekend days [#use-the-where-operator-to-match-only-weekend-days]
**Sumo Logic:**
```bash
* | parse "day=*:" as day_of_week
| where day_of_week in ("Saturday","Sunday")
```
**APL:**
```kusto
['sample-http-logs']
| extend day_of_week = dayofweek(_time)
| where day_of_week == 1 or day_of_week == 0
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20day_of_week%20=%20dayofweek\(_time\)\n|%20where%20day_of_week%20==%201%20or%20day_of_week%20==%200%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Extract Numeric Version Numbers [#extract-numeric-version-numbers]
In this section, identify version numbers that match numeric values 2, 3, or 1. Use the `num` operator to convert these strings into numerical format, facilitating easier analysis and comparison.
**Sumo Logic:**
```bash
* | parse "Version=*." as number | num(number)
| where number in (2,3,6)
```
**APL:**
```kusto
['sample-http-logs']
| extend p= (req_duration_ms)
| extend number=toint(p)
| where number in (2,3,6)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20p=%20\(req_duration_ms\)\n|%20extend%20number=toint\(p\)\n|%20where%20number%20in%20\(2,3,6\)%22,%22queryOptions%22:\{%22quickRange%22:%2290d%22}})
## Making the Leap: Transform Your Data Analytics with APL [#making-the-leap-transform-your-data-analytics-with-apl]
Now that you’ve navigated through the process of migrating from Sumo Logic to APL, you’ve hopefully found the insights valuable. The powerful capabilities of Axiom Processing Lnaguage are now within your reach, ready to empower your data analytics journey.
Ready to take the next step in your data analytics journey? Dive deeper into APL and discover how it can unlock even more potential in your data. Check out the APL [learning resources](/apl/guides/migrating-from-sql-to-apl) and [tutorials](/apl/tutorial) to become proficient in APL, and join the [community forums](http://axiom.co/discord) to engage with other APL users. Together, you can redefine what’s possible in data analytics. Remember, the migration to APL isn’t just a change, it’s an upgrade. Embrace the change, because better data analytics await you.
Begin your APL journey today.
---
# Migrate from Splunk SPL to APL
Source: https://axiom.co/docs/apl/guides/splunk-cheat-sheet
Splunk and Axiom are powerful tools for log analysis and data exploration. The Query tab uses Axiom Processing Language (APL). There are some differences between the query languages for Splunk and Axiom. When transitioning from Splunk to APL, you will need to understand how to convert your Splunk SPL queries into APL.
**This guide provides a high-level mapping from Splunk to APL.**
## Basic Searching [#basic-searching]
Splunk uses a `search` command for basic searching, while in APL, simply specify the dataset name followed by a filter.
**Splunk:**
```bash
search index="myIndex" error
```
**APL:**
```kusto
['myDatasaet']
| where FieldName contains “error”
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20where%20method%20contains%20%27GET%27%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Filtering [#filtering]
In Splunk, perform filtering using the `search` command, usually specifying field names and their desired values. In APL, perform filtering by using the `where` operator.
**Splunk:**
```bash
Search index=”myIndex” error
| stats count
```
**APL:**
```kusto
['myDataset']
| where fieldName contains “error”
| count
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20where%20content_type%20contains%20%27text%27\n|%20count\n|%20limit%2010%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Aggregation [#aggregation]
In Splunk, the `stats` command is used for aggregation. In APL, perform aggregation using the `summarize` operator.
**Splunk:**
```bash
search index="myIndex"
| stats count by status
```
**APL:**
```kusto
['myDataset']
| summarize count() by status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20summarize%20count\(\)%20by%20status%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Time Frames [#time-frames]
In Splunk, select a time range for a search in the time picker on the search page. In APL, filter by a time range using the where operator and the `timespan` field of the dataset.
**Splunk:**
```bash
search index="myIndex" earliest=-1d@d latest=now
```
**APL:**
```kusto
['myDataset']
| where _time >= ago(1d) and _time <= now()
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20where%20_time%20%3E=%20ago\(1d\)%20and%20_time%20%3C=%20now\(\)%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Sorting [#sorting]
In Splunk, the `sort` command is used to order the results of a search. In APL, perform sorting by using the `sort by` operator.
**Splunk:**
```bash
search index="myIndex"
| sort - content_type
```
**APL:**
```kusto
['myDataset']
| sort by countent_type desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20sort%20by%20content_type%20desc%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Selecting Fields [#selecting-fields]
In Splunk, use the fields command to specify which fields to include or exclude in the search results. In APL, use the `project` operator, `project-away` operator, or the `project-keep` operator to specify which fields to include in the query results.
**Splunk:**
```bash
index=main sourcetype=mySourceType
| fields status, responseTime
```
**APL:**
```kusto
['myDataset']
| extend newName = oldName
| project-away oldName
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20newStatus%20=%20status%20\n|%20project-away%20status%20%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Renaming Fields [#renaming-fields]
In Splunk, rename fields using the `rename` command, while in APL rename fields using the `extend,` and `project` operator. Here is the general syntax:
**Splunk:**
```bash
index="myIndex" sourcetype="mySourceType"
| rename oldFieldName AS newFieldName
```
**APL:**
```kusto
['myDataset']
| where method == "GET"
| extend new_field_name = content_type
| project-away content_type
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20where%20method%20==%20%27GET%27\n|%20extend%20new_field_name%20=%20content_type\n|%20project-away%20content_type%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Calculated Fields [#calculated-fields]
In Splunk, use the `eval` command to create calculated fields based on the values of other fields, while in APL use the `extend` operator to create calculated fields based on the values of other fields.
**Splunk**
```bash
search index="myIndex"
| eval newField=field1+field2
```
**APL:**
```kusto
['myDataset']
| extend newField = field1 + field2
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=\{%22apl%22:%22\[%27sample-http-logs%27]\n|%20extend%20calculatedFields%20=%20req_duration_ms%20%2b%20resp_body_size_bytes%22,%22queryOptions%22:\{%22quickRange%22:%2230d%22}})
## Structure and Concepts [#structure-and-concepts]
The following table compares concepts and data structures between Splunk and APL logs.
| Concept | Splunk | APL | Comment |
| ------------------------- | -------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| data caches | buckets | caching and retention policies | Controls the period and caching level for the data. This setting directly affects the performance of queries. |
| logical partition of data | index | dataset | Allows logical separation of the data. |
| structured event metadata | N/A | dataset | Splunk doesn’t expose the concept of metadata to the search language. APL logs have the concept of a dataset, which has fields and columns. Each event instance is mapped to a row. |
| data record | event | row | Terminology change only. |
| types | datatype | datatype | APL data types are more explicit because they’re set on the fields. Both have the ability to work dynamically with data types and roughly equivalent sets of data types. |
| query and search | search | query | Concepts essentially are the same between APL and Splunk |
## Functions [#functions]
The following table specifies functions in APL that are equivalent to Splunk Functions.
| Splunk | APL |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| strcat | strcat() |
| split | split() |
| if | iff() |
| tonumber | todouble(), tolong(), toint() |
| upper, lower | toupper(), tolower() |
| replace | replace\_string() or replace\_regex() |
| substr | substring() |
| tolower | tolower() |
| toupper | toupper() |
| match | matches regex |
| regex | matches regex **(in splunk, regex is an operator. In APL, it’s a relational operator.)** |
| searchmatch | == **(In splunk, `searchmatch` allows searching the exact string.)** |
| random | rand(), rand(n) **(Splunk’s function returns a number between zero to 231 -1. APL returns a number between 0.0 and 1.0, or if a parameter is provided, between 0 and n-1.)** |
| now | now() |
In Splunk, the function is invoked by using the `eval` operator. In APL, it’s used as part of the `extend` or `project`.
In Splunk, the function is invoked by using the `eval` operator. In APL, it can be used with the `where` operator.
## Filter [#filter]
APL log queries start from a tabular result set in which a filter is applied. In Splunk, filtering is the default operation on the current index. You may also use the where operator in Splunk, but Axiom doesn’t recommend it.
| Product | Operator | Example |
| :------ | :--------- | :--------------------------------------------------------------------------- |
| Splunk | **search** | `Sample.Logs="330009.2" method="GET" _indextime>-24h` |
| APL | **where** | \['sample-http-logs'] \| where method == "GET" and \_time > `ago(24h)` |
## Get n events or rows for inspection [#get-n-events-or-rows-for-inspection]
APL log queries also support `take` as an alias to `limit`. In Splunk, if the results are ordered, `head` returns the first n results. In APL, `limit` isn’t ordered, but it returns the first n rows that are found.
| Product | Operator | Example |
| ------- | -------- | ---------------------------------------- |
| Splunk | head | `Sample.Logs=330009.2` \| head 100 |
| APL | limit | \['sample-htto-logs'] \| limit 100 |
## Get the first *n* events or rows ordered by a field or column [#get-the-first-n-events-or-rows-ordered-by-a-field-or-column]
For the bottom results, in Splunk, use `tail`. In APL, specify ordering direction by using `asc`.
| Product | Operator | Example |
| :------ | :------- | :---------------------------------------------------------------------- |
| Splunk | head | `Sample.Logs="33009.2"` \| sort `Event.Sequence` \| head 20 |
| APL | top | \['sample-http-logs'] \| top 20 by method |
## Extend the result set with new fields or columns [#extend-the-result-set-with-new-fields-or-columns]
Splunk has an `eval` function, but it’s not comparable to the `eval` operator in APL. Both the `eval` operator in Splunk and the `extend` operator in APL support only scalar functions and arithmetic operators.
| Product | Operator | Example |
| :------ | :------- | :---------------------------------------------------------------------------------------- |
| Splunk | eval | `Sample.Logs=330009.2` \| eval state= `if(Data.Exception = "0", "success", "error")` |
| APL | extend | \['sample-http-logs'] \| extend Grade = `iff(req_duration_ms >= 80, "A", "B")` |
## Rename [#rename]
APL uses the `project` operator to rename a field. In the `project` operator, a query can take advantage of any indexes that are prebuilt for a field. Splunk has a `rename` operator that does the same.
| Product | Operator | Example |
| :------ | :------- | :------------------------------------------------------------------ |
| Splunk | rename | `Sample.Logs=330009.2` \| rename `Date.Exception` as execption |
| APL | project | \['sample-http-logs'] \| project updated\_status = status |
## Format results and projection [#format-results-and-projection]
Splunk uses the `table` command to select which columns to include in the results. APL has a `project` operator that does the same and [more](/apl/tabular-operators/project-operator).
| Product | Operator | Example |
| :------ | :------- | :--------------------------------------------------- |
| Splunk | table | `Event.Rule=330009.2` \| table rule, state |
| APL | project | \['sample-http-logs'] \| project status, method |
Splunk uses the `field -` command to select which columns to exclude from the results. APL has a `project-away` operator that does the same.
| Product | Operator | Example |
| :------ | :--------------- | :-------------------------------------------------------------- |
| Splunk | **fields -** | `Sample.Logs=330009.2` \| fields - quota, hightest\_seller |
| APL | **project-away** | \['sample-http-logs'] \| project-away method, status |
## Aggregation [#aggregation-1]
See the [list of summarize aggregations functions](/apl/aggregation-function/statistical-functions) that are available.
| Splunk operator | Splunk example | APL operator | APL example |
| :-------------- | :------------------------------------------------------------- | :----------- | :----------------------------------------------------------------------- |
| **stats** | search (Rule=120502.\*) \| stats count by OSEnv, Audience | summarize | \['sample-http-logs'] \| summarize count() by content\_type, status |
## Sort [#sort]
In Splunk, to sort in ascending order, you must use the `reverse` operator. APL also supports defining where to put nulls, either at the beginning or at the end.
| Product | Operator | Example |
| :------ | :------- | :--------------------------------------------------------------- |
| Splunk | sort | Sample.logs=120103 \| sort `Data.Hresult` \| reverse |
| APL | order by | \['sample-http-logs'] \| order by status desc |
Whether you’re just starting your transition or you’re in the thick of it, this guide can serve as a helpful roadmap to assist you in your journey from Splunk to Axiom Processing Language.
Dive into the Axiom Processing Language, start converting your Splunk queries to APL, and explore the rich capabilities of the Query tab. Embrace the learning curve, and remember, every complex query you master is another step forward in your data analytics journey.
---
# Set statement
Source: https://axiom.co/docs/apl/query-statement/set-statement
The `set` statement is used to set a query option. Options enabled with the `set` statement only have effect for the duration of the query.
The `set` statement affects how your query is processed and the returned results.
## Syntax [#syntax]
```kusto
set OptionName=OptionValue
```
## Strict types [#strict-types]
The `stricttypes` query option lets you specify only the exact type of the data type declaration needed in your query. Otherwise, it throws a **QueryFailed** error.
## Example [#example]
```kusto
set stricttypes;
['Dataset']
| where number == 5
```
---
# Special field attributes
Source: https://axiom.co/docs/apl/reference/special-field-attributes
## Add link to table [#add-link-to-table]
* Name: `_row_url`
* Type: string
* Description: Define the URL to which the entire table links.
* APL query example: `extend _row_url = 'https://axiom.co/'`
* Expected behavior: Make rows clickable. When clicked, go to the specified URL.
If you specify a static string as the URL, all rows link to that page. To specify a different URL for each row, use an dynamic expression like `extend _row_url = strcat('https://axiom.co/', uri)` where `uri` is a field in your data.
## Add link to values in a field [#add-link-to-values-in-a-field]
* Name: `_FIELDNAME_url`
* Type: string
* Description: Define a URL to which values in a field link.
* APL query example: `extend _website_url = 'https://axiom.co/'`
* Expected behavior: Make values in the `website` field clickable. When clicked, go to the specified URL.
Replace `FIELDNAME` with the actual name of the field.
## Add tooltip to values in a field [#add-tooltip-to-values-in-a-field]
* Name: `_FIELDNAME_tooltip`
* Type: string
* Description: Define text to be displayed when hovering over values in a field.
* Example Usage: `extend _errors_tooltip = 'Number of errors'`
* Expected behavior: Display a tooltip with the specified text when the user hovers over values in a field.
Replace `FIELDNAME` with the actual name of the field.
## Add description to values in a field [#add-description-to-values-in-a-field]
* Name: `_FIELDNAME_description`
* Type: string
* Description: Define additional information to be displayed under the values in a field.
* Example Usage: `extend _diskusage_description = 'Current disk usage'`
* Expected behavior: Display additional text under the values in a field for more context.
Replace `FIELDNAME` with the actual name of the field.
## Add unit of measurement [#add-unit-of-measurement]
* Name: `_FIELDNAME_unit`
* Type: string
* Description: Specify the unit of measurement for another field’s value allowing for proper formatting and display.
* APL query example: `extend _size_unit = "gbytes"`
* Expected behavior: Format the value in the `size` field according to the unit specified in the `_size_unit` field.
Replace `FIELDNAME` with the actual name of the field you want to format. For example, for a field named `size`, use `_size_unit = "gbytes"` to display its values in gigabytes in the query results.
The supported units are the following:
**Percentage**
| Unit name | APL sytax |
| ----------------- | ---------- |
| percent (0-100) | percent100 |
| percent (0.0-1.0) | percent |
**Currency**
| Unit name | APL sytax |
| ----------- | --------- |
| Dollars ($) | curusd |
| Pounds (£) | curgbp |
| Euro (€) | cureur |
| Bitcoin (฿) | curbtc |
**Data (IEC)**
| Unit name | APL sytax |
| ---------- | --------- |
| bits(IEC) | bits |
| bytes(IEC) | bytes |
| kibibytes | kbytes |
| mebibytes | mbytes |
| gibibytes | gbytes |
| tebibytes | tbytes |
| pebibytes | pbytes |
**Data (metric)**
| Unit name | APL sytax |
| ------------- | --------- |
| bits(Metric) | decbits |
| bytes(Metric) | decbytes |
| kilobytes | deckbytes |
| megabytes | decmbytes |
| gigabytes | decgbytes |
| terabytes | dectbytes |
| petabytes | decpbytes |
**Data rate**
| Unit name | APL sytax |
| ------------- | --------- |
| packets/sec | pps |
| bits/sec | bps |
| bytes/sec | Bps |
| kilobytes/sec | KBs |
| kilobits/sec | Kbits |
| megabytes/sec | MBs |
| megabits/sec | Mbits |
| gigabytes/sec | GBs |
| gigabits/sec | Gbits |
| terabytes/sec | TBs |
| terabits/sec | Tbits |
| petabytes/sec | PBs |
| petabits/sec | Pbits |
**Datetime**
| Unit name | APL sytax |
| ----------------- | --------- |
| Hertz (1/s) | hertz |
| nanoseconds (ns) | ns |
| microseconds (µs) | µs |
| milliseconds (ms) | ms |
| seconds (s) | secs |
| minutes (m) | mins |
| hours (h) | hours |
| days (d) | days |
| ago | ago |
**Throughput**
| Unit name | APL sytax |
| ------------------ | --------- |
| counts/sec (cps) | cps |
| ops/sec (ops) | ops |
| requests/sec (rps) | reqps |
| reads/sec (rps) | rps |
| writes/sec (wps) | wps |
| I/O ops/sec (iops) | iops |
| counts/min (cpm) | cpm |
| ops/min (opm) | opm |
| requests/min (rps) | reqpm |
| reads/min (rpm) | rpm |
| writes/min (wpm) | wpm |
## Example [#example]
The example APL query below adds a tooltip and a description to the values of the `status` field. Clicking one of the values in this field leads to a page about status codes. The query adds the new field `resp_body_size_bits` that displays the size of the response body in the unit of bits.
```apl
['sample-http-logs']
| extend _status_tooltip = 'The status of the HTTP request is the response code from the server. It shows if an HTTP request has been successfully completed.'
| extend _status_description = 'This is the status of the HTTP request.'
| extend _status_url = 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Status'
| extend resp_body_size_bits = resp_body_size_bytes * 8
| extend _resp_body_size_bits_unit = 'bits'
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20_status_tooltip%20%3D%20'The%20status%20of%20the%20HTTP%20request%20is%20the%20response%20code%20from%20the%20server.%20It%20shows%20if%20an%20HTTP%20request%20has%20been%20successfully%20completed.'%20%7C%20extend%20_status_description%20%3D%20'This%20is%20the%20status%20of%20the%20HTTP%20request.'%20%7C%20extend%20_status_url%20%3D%20'https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FHTTP%2FStatus'%20%7C%20extend%20resp_body_size_bits%20%3D%20resp_body_size_bytes%20*%208%20%7C%20extend%20_resp_body_size_bits_unit%20%3D%20'bits'%22%7D)
---
# Array functions
Source: https://axiom.co/docs/apl/scalar-functions/array-functions
The table summarizes the array functions available in APL.
| Function | Description |
| -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| [array\_concat](/apl/scalar-functions/array-functions/array-concat) | Concatenates a number of dynamic arrays to a single array. |
| [array\_extract](/apl/scalar-functions/array-functions/array-extract) | Returns a dynamic array containing the extracted elements. |
| [array\_iff](/apl/scalar-functions/array-functions/array-iff) | Returns a new array containing elements from the input array that satisfy the condition. |
| [array\_index\_of](/apl/scalar-functions/array-functions/array-index-of) | Searches the array for the specified item, and returns its position. |
| [array\_length](/apl/scalar-functions/array-functions/array-length) | Calculates the number of elements in a dynamic array. |
| [array\_reverse](/apl/scalar-functions/array-functions/array-reverse) | Reverses the order of the elements in a dynamic array. |
| [array\_rotate\_left](/apl/scalar-functions/array-functions/array-rotate-left) | Rotates values inside a dynamic array to the left. |
| [array\_rotate\_right](/apl/scalar-functions/array-functions/array-rotate-right) | Rotates values inside a dynamic array to the right. |
| [array\_select\_dict](/apl/scalar-functions/array-functions/array-select-dict) | Selects a dictionary from an array of dictionaries. |
| [array\_shift\_left](/apl/scalar-functions/array-functions/array-shift-left) | Shifts the values inside a dynamic array to the left. |
| [array\_shift\_right](/apl/scalar-functions/array-functions/array-shift-right) | Shifts values inside an array to the right. |
| [array\_slice](/apl/scalar-functions/array-functions/array-slice) | Extracts a slice of a dynamic array. |
| [array\_sort\_asc](/apl/scalar-functions/array-functions/array-sort-asc) | Sorts an array in ascending order. |
| [array\_sort\_desc](/apl/scalar-functions/array-functions/array-sort-desc) | Sorts an array in descending order. |
| [array\_split](/apl/scalar-functions/array-functions/array-split) | Splits an array to multiple arrays according to the split indices and packs the generated array in a dynamic array. |
| [array\_sum](/apl/scalar-functions/array-functions/array-sum) | Calculates the sum of elements in a dynamic array. |
| [bag\_has\_key](/apl/scalar-functions/array-functions/bag-has-key) | Checks whether a dynamic property bag contains a specific key. |
| [bag\_keys](/apl/scalar-functions/array-functions/bag-keys) | Returns all keys in a dynamic property bag. |
| [bag\_pack](/apl/scalar-functions/array-functions/bag-pack) | Converts a list of key-value pairs to a dynamic property bag. |
| [isarray](/apl/scalar-functions/array-functions/isarray) | Checks whether a value is an array. |
| [len](/apl/scalar-functions/array-functions/len) | Returns the length of a string or the number of elements in an array. |
| [pack\_array](/apl/scalar-functions/array-functions/pack-array) | Packs all input values into a dynamic array. |
| [pack\_dictionary](/apl/scalar-functions/array-functions/pack-dictionary) | Returns a dynamic object that represents a dictionary where each key maps to its associated value. |
| [strcat\_array](/apl/scalar-functions/array-functions/strcat-array) | Takes an array and returns a single concatenated string with the array’s elements separated by the specified delimiter. |
## Dynamic arrays [#dynamic-arrays]
Most array functions accept a dynamic array as their parameter. Dynamic arrays allow you to add or remove elements. You can change a dynamic array with an array function.
A dynamic array expands as you add more elements. This means that you don’t need to determine the size in advance.
---
# Conditional functions
Source: https://axiom.co/docs/apl/scalar-functions/conditional-function
Use conditional functions to branch query logic based on evaluated predicates. They let you map values to labels, define alert tiers, or apply different transformations to different rows in a single expression.
## List of functions [#list-of-functions]
| Function | Description |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| [case](/apl/scalar-functions/conditional-function/case) | Evaluates a list of conditions and returns the value paired with the first condition that's true. |
| [iff](/apl/scalar-functions/conditional-function/iff) | Evaluates a single predicate and returns one of two values. `iif` is an alias. |
---
# Conversion functions
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions
The table summarizes the conversion functions available in APL.
| **Function Name** | **Description** |
| ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| [dynamic\_to\_json](/apl/scalar-functions/conversion-functions/dynamic-to-json) | Converts a scalar value of type dynamic to a canonical string representation. |
| [ensure\_field](/apl/scalar-functions/conversion-functions/ensure-field) | Ensures the existence of a field and returns its value or a typed nil if it doesn’t exist. |
| [isbool](/apl/scalar-functions/conversion-functions/isbool) | Returns a value of true or false if the expression value is passed. |
| [toarray](/apl/scalar-functions/conversion-functions/toarray) | Converts input to array. |
| [tobool](/apl/scalar-functions/conversion-functions/tobool) | Converts input to boolean (signed 8-bit) representation. |
| [todatetime](/apl/scalar-functions/conversion-functions/todatetime) | Converts input to datetime scalar. |
| [todouble](/apl/scalar-functions/conversion-functions/todouble) | Converts the input to a value of type `real`. `todouble` and `toreal` are synonyms. |
| [todynamic](/apl/scalar-functions/conversion-functions/todynamic) | Converts input to dynamic. |
| [tohex](/apl/scalar-functions/conversion-functions/tohex) | Converts input to a hexadecimal string. |
| [toint](/apl/scalar-functions/conversion-functions/toint) | Converts the input to an integer value (signed 64-bit) number representation. `toint` and `tolong` are synonyms. |
| [tolong](/apl/scalar-functions/conversion-functions/toint) | Converts input to long (signed 64-bit) number representation. `toint` and `tolong` are synonyms. |
| [toreal](/apl/scalar-functions/conversion-functions/todouble) | Converts the input to a value of type `real`. `todouble` and `toreal` are synonyms. |
| [tostring](/apl/scalar-functions/conversion-functions/tostring) | Converts input to a string representation. |
| [totimespan](/apl/scalar-functions/conversion-functions/totimespan) | Converts input to timespan scalar. |
---
# Datetime functions
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions
The table summarizes the datetime functions available in APL.
| Name | Description |
| --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| [ago](/apl/scalar-functions/datetime-functions/ago) | Subtracts the given timespan from the current UTC clock time. |
| [datetime\_add](/apl/scalar-functions/datetime-functions/datetime-add) | Calculates a new datetime from a specified datepart multiplied by a specified amount, added to a specified datetime. |
| [datetime\_diff](/apl/scalar-functions/datetime-functions/datetime-diff) | Calculates the calendarian difference between two datetime values. |
| [datetime\_part](/apl/scalar-functions/datetime-functions/datetime-part) | Extracts the requested date part as an integer value. |
| [dayofmonth](/apl/scalar-functions/datetime-functions/dayofmonth) | Returns the integer number representing the day number of the given month. |
| [dayofweek](/apl/scalar-functions/datetime-functions/dayofweek) | Returns the integer number of days since the preceding Sunday. |
| [dayofyear](/apl/scalar-functions/datetime-functions/dayofyear) | Returns the integer number representing the day number of the given year. |
| [endofday](/apl/scalar-functions/datetime-functions/endofday) | Returns the end of the day containing the date. |
| [endofmonth](/apl/scalar-functions/datetime-functions/endofmonth) | Returns the end of the month containing the date. |
| [endofweek](/apl/scalar-functions/datetime-functions/endofweek) | Returns the end of the week containing the date. |
| [endofyear](/apl/scalar-functions/datetime-functions/endofyear) | Returns the end of the year containing the date. |
| [getmonth](/apl/scalar-functions/datetime-functions/getmonth) | Returns the month number (1-12) from a datetime. |
| [getyear](/apl/scalar-functions/datetime-functions/getyear) | Returns the year part of the datetime argument. |
| [hourofday](/apl/scalar-functions/datetime-functions/hourofday) | Returns the integer number representing the hour number of the given date. |
| [monthofyear](/apl/scalar-functions/datetime-functions/monthofyear) | Returns the integer number representing the month number of the given year. |
| [now](/apl/scalar-functions/datetime-functions/now) | Returns the current UTC clock time, optionally offset by a given timespan. |
| [startofday](/apl/scalar-functions/datetime-functions/startofday) | Returns the start of the day containing the date. |
| [startofmonth](/apl/scalar-functions/datetime-functions/startofmonth) | Returns the start of the month containing the date. |
| [startofweek](/apl/scalar-functions/datetime-functions/startofweek) | Returns the start of the week containing the date. |
| [startofyear](/apl/scalar-functions/datetime-functions/startofyear) | Returns the start of the year containing the date. |
| [unixtime\_microseconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-microseconds-todatetime) | Converts a Unix timestamp expressed in whole microseconds to an APL `datetime` value. |
| [unixtime\_milliseconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-milliseconds-todatetime) | Converts a Unix timestamp expressed in whole milliseconds to an APL `datetime` value. |
| [unixtime\_nanoseconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-nanoseconds-todatetime) | Converts a Unix timestamp expressed in whole nanoseconds to an APL `datetime` value. |
| [unixtime\_seconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-seconds-todatetime) | Converts a Unix timestamp expressed in whole seconds to an APL `datetime` value. |
| [week\_of\_year](/apl/scalar-functions/datetime-functions/week-of-year) | Returns the ISO 8601 week number from a datetime expression. |
Axiom supports the ISO 8601 format which is the standard format for representing dates and times in the Gregorian calendar. For more information, see [Supported formats](/apl/data-types/scalar-data-types#supported-formats).
---
# GenAI functions
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions
GenAI functions in APL help you analyze and process GenAI conversation data, including messages, token usage, costs, and conversation metadata. These functions are useful when working with logs or data from large language models (LLMs) and AI systems.
## When to use GenAI functions [#when-to-use-genai-functions]
Use GenAI functions when you need to:
* Extract specific information from AI conversation logs, such as user prompts, assistant responses, or system prompts
* Calculate token costs and usage metrics for LLM API calls
* Analyze conversation structure and flow, including turn counts and message roles
* Process and filter conversation messages based on roles or content
* Determine pricing information for different AI models
* Detect truncation or tool calls in AI responses
## Available GenAI functions [#available-genai-functions]
| Function | Description |
| :------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------ |
| [genai\_concat\_contents](/apl/scalar-functions/genai-functions/genai-concat-contents) | Concatenates message contents from a conversation array |
| [genai\_conversation\_turns](/apl/scalar-functions/genai-functions/genai-conversation-turns) | Counts the number of conversation turns |
| [genai\_cost](/apl/scalar-functions/genai-functions/genai-cost) | Calculates the total cost for input and output tokens |
| [genai\_estimate\_tokens](/apl/scalar-functions/genai-functions/genai-estimate-tokens) | Estimates the number of tokens in a text string |
| [genai\_extract\_assistant\_response](/apl/scalar-functions/genai-functions/genai-extract-assistant-response) | Extracts the assistant’s response from a conversation |
| [genai\_extract\_function\_results](/apl/scalar-functions/genai-functions/genai-extract-function-results) | Extracts function call results from messages |
| [genai\_extract\_system\_prompt](/apl/scalar-functions/genai-functions/genai-extract-system-prompt) | Extracts the system prompt from a conversation |
| [genai\_extract\_tool\_calls](/apl/scalar-functions/genai-functions/genai-extract-tool-calls) | Extracts tool calls from messages |
| [genai\_extract\_user\_prompt](/apl/scalar-functions/genai-functions/genai-extract-user-prompt) | Extracts the user prompt from a conversation |
| [genai\_get\_content\_by\_index](/apl/scalar-functions/genai-functions/genai-get-content-by-index) | Gets message content by index position |
| [genai\_get\_content\_by\_role](/apl/scalar-functions/genai-functions/genai-get-content-by-role) | Gets message content by role |
| [genai\_get\_pricing](/apl/scalar-functions/genai-functions/genai-get-pricing) | Gets pricing information for a specific model |
| [genai\_get\_role](/apl/scalar-functions/genai-functions/genai-get-role) | Gets the role of a message at a specific index |
| [genai\_has\_tool\_calls](/apl/scalar-functions/genai-functions/genai-has-tool-calls) | Checks if messages contain tool calls |
| [genai\_input\_cost](/apl/scalar-functions/genai-functions/genai-input-cost) | Calculates the cost for input tokens |
| [genai\_is\_truncated](/apl/scalar-functions/genai-functions/genai-is-truncated) | Checks if a response was truncated |
| [genai\_message\_roles](/apl/scalar-functions/genai-functions/genai-message-roles) | Extracts all message roles from a conversation |
| [genai\_output\_cost](/apl/scalar-functions/genai-functions/genai-output-cost) | Calculates the cost for output tokens |
---
# Hash functions
Source: https://axiom.co/docs/apl/scalar-functions/hash-functions
Use hash functions to transform scalar values into fixed-length digests. They're useful for anonymizing personally identifiable information while preserving joinability, detecting duplicates, creating bucket keys for sampling, and verifying data integrity.
## List of functions [#list-of-functions]
| Function | Description |
| ---------------------------------------------------------------- | -------------------------------------------------------------- |
| [hash](/apl/scalar-functions/hash-functions/hash) | Returns a signed 64-bit integer hash of the input value. |
| [hash\_md5](/apl/scalar-functions/hash-functions/hash-md5) | Returns a 32-character MD5 hex digest of the input value. |
| [hash\_sha1](/apl/scalar-functions/hash-functions/hash-sha1) | Returns a 40-character SHA-1 hex digest of the input value. |
| [hash\_sha256](/apl/scalar-functions/hash-functions/hash-sha256) | Returns a 64-character SHA-256 hex digest of the input value. |
| [hash\_sha512](/apl/scalar-functions/hash-functions/hash-sha512) | Returns a 128-character SHA-512 hex digest of the input value. |
---
# IP functions
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions
The table summarizes the IP functions available in APL.
| Function | Description |
| ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| [format\_ipv4](/apl/scalar-functions/ip-functions/format-ipv4) | Parses input with a netmask and returns string representing IPv4 address. |
| [format\_ipv4\_mask](/apl/scalar-functions/ip-functions/format-ipv4-mask) | Formats an IPv4 address and a bitmask into CIDR notation. |
| [geo\_info\_from\_ip\_address](/apl/scalar-functions/ip-functions/geo-info-from-ip-address) | Extracts geographical, geolocation, and network information from IP addresses. |
| [has\_any\_ipv4](/apl/scalar-functions/ip-functions/has-any-ipv4) | Returns a Boolean value indicating whether the specified column contains any of the given IPv4 addresses. |
| [has\_any\_ipv4\_prefix](/apl/scalar-functions/ip-functions/has-any-ipv4-prefix) | Returns a Boolean value indicating whether the IPv4 address matches any of the specified prefixes. |
| [has\_ipv4](/apl/scalar-functions/ip-functions/has-ipv4) | Returns a Boolean value indicating whether the given IPv4 address is valid and found in the source text. |
| [has\_ipv4\_prefix](/apl/scalar-functions/ip-functions/has-ipv4-prefix) | Returns a Boolean value indicating whether the given IPv4 address starts with a specified prefix. |
| [ipv4\_compare](/apl/scalar-functions/ip-functions/ipv4-compare) | Compares two IPv4 addresses. |
| [ipv4\_is\_in\_any\_range](/apl/scalar-functions/ip-functions/ipv4-is-in-any-range) | Returns a Boolean value indicating whether the given IPv4 address is in any specified range. |
| [ipv4\_is\_in\_range](/apl/scalar-functions/ip-functions/ipv4-is-in-range) | Checks if IPv4 string address is in IPv4-prefix notation range. |
| [ipv4\_is\_match](/apl/scalar-functions/ip-functions/ipv4-is-match) | Returns a Boolean value indicating whether the given IPv4 matches the specified pattern. |
| [ipv4\_is\_private](/apl/scalar-functions/ip-functions/ipv4-is-private) | Checks if IPv4 string address belongs to a set of private network IPs. |
| [ipv4\_netmask\_suffix](/apl/scalar-functions/ip-functions/ipv4-netmask-suffix) | Returns the value of the IPv4 netmask suffix from IPv4 string address. |
| [ipv6\_compare](/apl/scalar-functions/ip-functions/ipv6-compare) | Compares two IPv6 addresses. |
| [ipv6\_is\_in\_any\_range](/apl/scalar-functions/ip-functions/ipv6-is-in-any-range) | Returns a Boolean value indicating whether the given IPv6 address is in any specified range. |
| [ipv6\_is\_in\_range](/apl/scalar-functions/ip-functions/ipv6-is-in-range) | Checks if IPv6 string address is in IPv6-prefix notation range. |
| [ipv6\_is\_match](/apl/scalar-functions/ip-functions/ipv6-is-match) | Returns a Boolean value indicating whether the given IPv6 matches the specified pattern. |
| [parse\_ipv4](/apl/scalar-functions/ip-functions/parse-ipv4) | Converts input to long (signed 64-bit) number representation. |
| [parse\_ipv4\_mask](/apl/scalar-functions/ip-functions/parse-ipv4-mask) | Converts input string and IP-prefix mask to long (signed 64-bit) number representation. |
## IP-prefix notation [#ip-prefix-notation]
You can define IP addresses with IP-prefix notation using a slash (`/`) character. The IP address to the left of the slash is the base IP address. The number (1 to 32) to the right of the slash is the number of contiguous bits in the netmask. For example, `192.168.2.0/24` has an associated net/subnetmask containing 24 contiguous bits or `255.255.255.0` in dotted decimal format.
---
# Mathematical functions
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions
The table summarizes the mathematical functions available in APL.
| Name | Description |
| --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| [abs](/apl/scalar-functions/mathematical-functions/abs) | Calculates the absolute value of the input. |
| [acos](/apl/scalar-functions/mathematical-functions/acos) | Returns the angle whose cosine is the specified number (the inverse operation of cos()). |
| [asin](/apl/scalar-functions/mathematical-functions/asin) | Returns the angle whose sine is the specified number (the inverse operation of sin()). |
| [atan](/apl/scalar-functions/mathematical-functions/atan) | Returns the angle whose tangent is the specified number (the inverse operation of tan()). |
| [atan2](/apl/scalar-functions/mathematical-functions/atan2) | Calculates the angle, in radians, between the positive x-axis and the ray from the origin to the point (y, x). |
| [cos](/apl/scalar-functions/mathematical-functions/cos) | Returns the cosine function. |
| [cot](/apl/scalar-functions/mathematical-functions/cot) | Returns the cotangent function. |
| [degrees](/apl/scalar-functions/mathematical-functions/degrees) | Converts angle value in radians into value in degrees. |
| [exp](/apl/scalar-functions/mathematical-functions/exp) | The base-e exponential function of x: e^x. |
| [exp10](/apl/scalar-functions/mathematical-functions/exp10) | The base-10 exponential function of x: 10^x. |
| [exp2](/apl/scalar-functions/mathematical-functions/exp2) | The base-2 exponential function of x: 2^x. |
| [gamma](/apl/scalar-functions/mathematical-functions/gamma) | Computes the gamma function. |
| [isfinite](/apl/scalar-functions/mathematical-functions/isfinite) | Returns whether input is a finite value (neither infinite nor NaN). |
| [isinf](/apl/scalar-functions/mathematical-functions/isinf) | Returns whether input is an infinite (positive or negative) value. |
| [isint](/apl/scalar-functions/mathematical-functions/isint) | Returns whether input is an integer (positive or negative) value. |
| [isnan](/apl/scalar-functions/mathematical-functions/isnan) | Returns whether input is a Not a Number (NaN) value. |
| [log](/apl/scalar-functions/mathematical-functions/log) | Returns the natural logarithm function. |
| [log10](/apl/scalar-functions/mathematical-functions/log10) | Returns the common (base-10) logarithm function. |
| [log2](/apl/scalar-functions/mathematical-functions/log2) | Returns the base-2 logarithm function. |
| [loggamma](/apl/scalar-functions/mathematical-functions/loggamma) | Computes log of absolute value of the gamma function. |
| [max\_of](/apl/scalar-functions/mathematical-functions/max-of) | Returns the largest of the provided values. |
| [min\_of](/apl/scalar-functions/mathematical-functions/min-of) | Returns the smallest of the provided values. |
| [not](/apl/scalar-functions/mathematical-functions/not) | Reverses the value of its bool argument. |
| [pi](/apl/scalar-functions/mathematical-functions/pi) | Returns the constant value of Pi (π). |
| [pow](/apl/scalar-functions/mathematical-functions/pow) | Returns a result of raising to power. |
| [radians](/apl/scalar-functions/mathematical-functions/radians) | Converts angle value in degrees into value in radians. |
| [rand](/apl/scalar-functions/mathematical-functions/rand) | Returns pseudo-random numbers. |
| [range](/apl/scalar-functions/mathematical-functions/range) | Returns a dynamic array of evenly spaced values. |
| [round](/apl/scalar-functions/mathematical-functions/round) | Returns the rounded source to the specified precision. |
| [set\_difference](/apl/scalar-functions/mathematical-functions/set-difference) | Returns the difference between two arrays. |
| [set\_has\_element](/apl/scalar-functions/mathematical-functions/set-has-element) | Determines if a set contains a specific value. |
| [set\_intersect](/apl/scalar-functions/mathematical-functions/set-intersect) | Returns the intersection of two arrays. |
| [set\_union](/apl/scalar-functions/mathematical-functions/set-union) | Returns the union of two arrays. |
| [sign](/apl/scalar-functions/mathematical-functions/sign) | Sign of a numeric expression. |
| [sin](/apl/scalar-functions/mathematical-functions/sin) | Returns the sine function. |
| [sqrt](/apl/scalar-functions/mathematical-functions/sqrt) | Returns the square root function. |
| [tan](/apl/scalar-functions/mathematical-functions/tan) | Returns the tangent function. |
---
# Metadata functions
Source: https://axiom.co/docs/apl/scalar-functions/metadata-functions
The table summarizes the metadata functions available in APL.
| Function | Description |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| [column\_ifexists](/apl/scalar-functions/metadata-functions/column-ifexists) | Returns a named field if it exists in the dataset, or a fallback expression if it doesn't. |
| [cursor\_current](/apl/scalar-functions/metadata-functions/cursor-current) | Returns a cursor string that marks the current position in the query execution. |
| [ingestion\_time](/apl/scalar-functions/metadata-functions/ingestion-time) | Returns the timestamp of when each record was ingested into Axiom. |
---
# Pair functions
Source: https://axiom.co/docs/apl/scalar-functions/pair-functions
Use pair functions to create, parse, and search key-value pair strings. These functions are useful for working with tags, labels, metadata, and any data stored in key-value format.
## List of functions [#list-of-functions]
| Function | Description |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| [find\_pair](/apl/scalar-functions/pair-functions/find-pair) | Searches an array of key-value pairs and finds the first pair that matches specified key and value patterns. |
| [pair](/apl/scalar-functions/pair-functions/pair) | Creates a dynamic object representing a key-value pair. |
| [parse\_pair](/apl/scalar-functions/pair-functions/parse-pair) | Parses a pair string into its key and value components. |
---
# Rounding functions
Source: https://axiom.co/docs/apl/scalar-functions/rounding-functions
Use rounding functions to adjust numeric values to specific boundaries or intervals. These functions are essential for data binning, time-series aggregation, and converting continuous values to discrete buckets.
## List of functions [#list-of-functions]
| Function | Description |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| [ceiling](/apl/scalar-functions/rounding-functions/ceiling) | Rounds a number up to the smallest integer greater than or equal to the input. |
| [bin](/apl/scalar-functions/rounding-functions/bin) | Rounds values down to an integer multiple of a specified bin size. |
| [bin\_auto](/apl/scalar-functions/rounding-functions/bin-auto) | Rounds datetime values down to a fixed-size bin with automatic size selection. |
| [floor](/apl/scalar-functions/rounding-functions/floor) | Rounds a number down to the largest integer less than or equal to the input. |
---
# SQL functions
Source: https://axiom.co/docs/apl/scalar-functions/sql-functions
Use SQL functions to parse and reconstruct SQL statements stored as strings in your datasets. They're useful in database monitoring, audit log analysis, and SQL validation pipelines.
## List of functions [#list-of-functions]
| Function | Description |
| ------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| [parse\_sql](/apl/scalar-functions/sql-functions/parse-sql) | Parses a SQL statement string into a structured dictionary of its components. |
| [format\_sql](/apl/scalar-functions/sql-functions/format-sql) | Converts the structured dictionary produced by `parse_sql` back into a SQL string. |
---
# String functions
Source: https://axiom.co/docs/apl/scalar-functions/string-functions
The table summarizes the string functions available in APL.
| Name | Description |
| ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| [base64\_decode\_toarray](/apl/scalar-functions/string-functions/base64-decode-toarray) | Decodes a Base64-encoded string into an array of bytes. |
| [base64\_decode\_tostring](/apl/scalar-functions/string-functions/base64-decode-tostring) | Decodes a base64 string to a UTF-8 string. |
| [base64\_encode\_fromarray](/apl/scalar-functions/string-functions/base64-encode-fromarray) | Converts a sequence of bytes into a Base64-encoded string. |
| [base64\_encode\_tostring](/apl/scalar-functions/string-functions/base64-encode-tostring) | Encodes a string as base64 string. |
| [coalesce](/apl/scalar-functions/string-functions/coalesce) | Evaluates a list of expressions and returns the first non-null (or non-empty for string) expression. |
| [countof\_regex](/apl/scalar-functions/string-functions/countof-regex) | Counts occurrences of a substring in a string. Regex matches don’t. |
| [countof](/apl/scalar-functions/string-functions/countof) | Counts occurrences of a substring in a string. |
| [extract\_all](/apl/scalar-functions/string-functions/extract-all) | Gets all matches for a regular expression from a text string. |
| [extract](/apl/scalar-functions/string-functions/extract) | Gets a match for a regular expression from a text string. |
| [format\_bytes](/apl/scalar-functions/string-functions/format-bytes) | Formats a number of bytes as a string including bytes units |
| [format\_url](/apl/scalar-functions/string-functions/format-url) | Formats an input string into a valid URL by adding the necessary protocol if it’s escaping illegal URL characters. |
| [gettype](/apl/scalar-functions/string-functions/gettype) | Returns the runtime type of its single argument. |
| [indexof](/apl/scalar-functions/string-functions/indexof) | Function reports the zero-based index of the first occurrence of a specified string within input string. |
| [isascii](/apl/scalar-functions/string-functions/isascii) | Checks whether all characters in an input string are ASCII characters. |
| [isempty](/apl/scalar-functions/string-functions/isempty) | Returns true if the argument is an empty string or is null. |
| [isnotempty](/apl/scalar-functions/string-functions/isnotempty) | Returns true if the argument isn’t an empty string or a null. |
| [isnotnull](/apl/scalar-functions/string-functions/isnotnull) | Returns true if the argument isn’t null. |
| [isnull](/apl/scalar-functions/string-functions/isnull) | Evaluates its sole argument and returns a bool value indicating if the argument evaluates to a null value. |
| [parse\_bytes](/apl/scalar-functions/string-functions/parse-bytes) | Parses a string including byte size units and returns the number of bytes |
| [parse\_csv](/apl/scalar-functions/string-functions/parse-csv) | Splits a given string representing a single record of comma-separated values and returns a string array with these values. |
| [parse\_json](/apl/scalar-functions/string-functions/parse-json) | Interprets a string as a JSON value and returns the value as dynamic. |
| [parse\_url](/apl/scalar-functions/string-functions/parse-url) | Parses an absolute URL string and returns a dynamic object contains all parts of the URL. |
| [parse\_urlquery](/apl/scalar-functions/string-functions/parse-urlquery) | Parses a URL query string and returns a dynamic object contains the Query parameters. |
| [quote](/apl/scalar-functions/string-functions/quote) | Returns a string representing the input enclosed in double quotes, with internal quotes and escape sequences handled appropriately. |
| [replace\_regex](/apl/scalar-functions/string-functions/replace-regex) | Replaces all regex matches with another string. |
| [replace\_string](/apl/scalar-functions/string-functions/replace-string) | Replaces all string matches with another string. |
| [replace](/apl/scalar-functions/string-functions/replace) | Replace all regex matches with another string. |
| [reverse](/apl/scalar-functions/string-functions/reverse) | Function makes reverse of input string. |
| [split](/apl/scalar-functions/string-functions/split) | Splits a given string according to a given delimiter and returns a string array with the contained substrings. |
| [strcat\_delim](/apl/scalar-functions/string-functions/strcat-delim) | Concatenates between 2 and 64 arguments, with delimiter, provided as first argument. |
| [strcat](/apl/scalar-functions/string-functions/strcat) | Concatenates between 1 and 64 arguments. |
| [strcmp](/apl/scalar-functions/string-functions/strcmp) | Compares two strings. |
| [string-size](/apl/scalar-functions/string-functions/string-size) | Returns the length, in characters, of the input string. |
| [strlen](/apl/scalar-functions/string-functions/strlen) | Returns the length, in characters, of the input string. |
| [strrep](/apl/scalar-functions/string-functions/strrep) | Repeats given string provided number of times (default = 1). |
| [substring](/apl/scalar-functions/string-functions/substring) | Extracts a substring from a source string. |
| [tolower](/apl/scalar-functions/string-functions/tolower) | Converts a string to lower case. |
| [totitle](/apl/scalar-functions/string-functions/totitle) | Converts a string to title case. |
| [toupper](/apl/scalar-functions/string-functions/toupper) | Converts a string to upper case. |
| [translate](/apl/scalar-functions/string-functions/translate) | Substitutes characters in a string, one by one, based on their position in two input lists. |
| [trim\_end\_regex](/apl/scalar-functions/string-functions/trim-end-regex) | Removes trailing match of the specified regular expression. |
| [trim\_end](/apl/scalar-functions/string-functions/trim-end) | Removes trailing match of the specified cutset. |
| [trim\_regex](/apl/scalar-functions/string-functions/trim-regex) | Removes all leading and trailing matches of the specified regular expression. |
| [trim\_space](/apl/scalar-functions/string-functions/trim-space) | Removes all leading and trailing whitespace from a string. |
| [trim\_start\_regex](/apl/scalar-functions/string-functions/trim-start-regex) | Removes leading match of the specified regular expression. |
| [trim\_start](/apl/scalar-functions/string-functions/trim-start) | Removes leading match of the specified cutset. |
| [trim](/apl/scalar-functions/string-functions/trim) | Removes all leading and trailing matches of the specified cutset. |
| [unicode\_codepoints\_from\_string](/apl/scalar-functions/string-functions/unicode-codepoints-from-string) | Converts a UTF-8 string into an array of Unicode code points. |
| [unicode\_codepoints\_to\_string](/apl/scalar-functions/string-functions/unicode-codepoints-to-string) | Converts an array of Unicode code points into a UTF-8 encoded string. |
| [url\_decode](/apl/scalar-functions/string-functions/url-decode) | Converts encoded URL into a regular URL representation. |
| [url\_encode](/apl/scalar-functions/string-functions/url-encode) | Converts characters of the input URL into a format that can be transmitted over the Internet. |
---
# Type funtions
Source: https://axiom.co/docs/apl/scalar-functions/type-functions
The table summarizes the type functions available in APL.
| Function | Description |
| --------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| [iscc](/apl/scalar-functions/type-functions/iscc) | Checks whether a value is a valid credit card (CC) number. |
| [isimei](/apl/scalar-functions/type-functions/isimei) | Checks whether a value is a valid International Mobile Equipment Identity (IMEI) number. |
| [ismap](/apl/scalar-functions/type-functions/ismap) | Checks whether a value is of the `dynamic` type and represents a mapping. |
| [isreal](/apl/scalar-functions/type-functions/isreal) | Checks whether a value is a real number. |
| [isstring](/apl/scalar-functions/type-functions/isstring) | Checks whether a value is a string. |
| [isutf8](/apl/scalar-functions/type-functions/isutf8) | Checks whether a value is a valid UTF-8 encoded sequence. |
---
# Logical operators
Source: https://axiom.co/docs/apl/scalar-operators/logical-operators
## Logical (binary) operators [#logical-binary-operators]
The following logical operators are supported between two values of the `bool` type:
**These logical operators are sometimes referred-to as Boolean operators, and sometimes as binary operators. The names are all synonyms.**
| **Operator name** | **Syntax** | **meaning** | |
| ----------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------ | - |
| Equality | **==** | Returns `true` if both operands are non-null and equal to each other. Otherwise, `false`. | |
| Inequality | **!=** or **\<>** | Returns `true` if either one (or both) of the operands are null, or they aren’t equal to each other. Otherwise, `false`. | |
| Logical and | **and** | Returns `true` if both operands are `true`. | |
| Logical or | **or** | Returns `true `if one of the operands is `true`, regardless of the other operand. | |
---
# Numerical operators
Source: https://axiom.co/docs/apl/scalar-operators/numerical-operators
## Numerical operators [#numerical-operators]
The types `int`, `long`, and `real` represent numerical types. The following operators can be used between pairs of these types:
| **Operator** | **Description** | **Example** | |
| ------------ | ---------------- | ------------------------------------------------ | - |
| `+` | Add | `3.19 + 3.19`, `ago(10m) + 10m` | |
| `-` | Subtract | `0.26 - 0.23` | |
| `*` | Multiply | `1s * 5`, `5 * 5` | |
| `/` | Divide | `10m / 1s`, `4 / 2` | |
| `%` | Modulo | `10 % 3`, `5 % 2` | |
| `<` | Less | `1 < 2`, `1 <= 1` | |
| `>` | Greater | `0.23 > 0.22`, `10min > 1sec`, `now() > ago(1d)` | |
| `==` | Equals | `3 == 3` | |
| `!=` | Not equals | `2 != 1` | |
| `<=` | Less or Equal | `5 <= 6` | |
| `>=` | Greater or Equal | `7 >= 6` | |
For set membership operators (`in`, `!in`, `in~`, `!in~`), see [Set membership operators](/apl/scalar-operators/in-operators/overview).
---
# String operators
Source: https://axiom.co/docs/apl/scalar-operators/string-operators
The table summarizes the string operators available in APL. For set membership operators (`in`, `!in`, `in~`, `!in~`), see [Set membership operators](/apl/scalar-operators/in-operators/overview).
| Operator | Description | Case sensitive | Example |
| --------------- | -------------------------------------------- | -------------- | -------------------------------------------------- |
| == | Equals | Yes | `"aBc" == "aBc"` |
| != | Not equals | Yes | `"abc" != "ABC"` |
| =\~ | Equals | No | `"abc" =~ "ABC"` |
| !\~ | Not equals | No | `"aBc" !~ "xyz"` |
| contains | RHS occurs as a subsequence of LHS | No | `"parentSpanId" contains "Span"` |
| !contains | RHS doesn’t occur in LHS | No | `"parentSpanId" !contains "abc"` |
| contains\_cs | RHS occurs as a subsequence of LHS | Yes | `"parentSpanId" contains_cs "Id"` |
| !contains\_cs | RHS doesn’t occur in LHS | Yes | `"parentSpanId" !contains_cs "Id"` |
| startswith | RHS is an initial subsequence of LHS | No | `"parentSpanId" startswith "parent"` |
| !startswith | RHS isn’t an initial subsequence of LHS | No | `"parentSpanId" !startswith "Id"` |
| startswith\_cs | RHS is an initial subsequence of LHS | Yes | `"parentSpanId" startswith_cs "parent"` |
| !startswith\_cs | RHS isn’t an initial subsequence of LHS | Yes | `"parentSpanId" !startswith_cs "parent"` |
| endswith | RHS is a closing subsequence of LHS | No | `"parentSpanId" endswith "Id"` |
| !endswith | RHS isn’t a closing subsequence of LHS | No | `"parentSpanId" !endswith "Span"` |
| endswith\_cs | RHS is a closing subsequence of LHS | Yes | `"parentSpanId" endswith_cs "Id"` |
| !endswith\_cs | RHS isn’t a closing subsequence of LHS | Yes | `"parentSpanId" !endswith_cs "Span"` |
| matches regex | LHS contains a match for RHS | Yes | `"parentSpanId" matches regex "g.*r"` |
| !matches regex | LHS doesn’t contain a match for RHS | Yes | `"parentSpanId" !matches regex "g.*r"` |
| has | RHS is a whole term in LHS | No | `"North America" has "america"` |
| !has | RHS isn’t a whole term in LHS | No | `"North America" !has "america"` |
| has\_cs | RHS is a whole term in LHS | Yes | `"North America" has_cs "America"` |
| !has\_cs | RHS isn’t a whole term in LHS | Yes | `"North America" !has_cs "America"` |
| has\_any | RHS has any whole term in LHS | No | `"North America" has_any ("America", "Europe")` |
| has\_any\_cs | RHS has any whole term in LHS | Yes | `"North America" has_any_cs ("America", "Europe")` |
| hasprefix | LHS string starts with the RHS string | No | `"Admin_User" hasprefix "Admin"` |
| !hasprefix | LHS string doesn’t start with the RHS string | No | `"Admin_User" !hasprefix "Admin"` |
| hasprefix\_cs | LHS string starts with the RHS string | Yes | `"DOCS_file" hasprefix_cs "DOCS"` |
| !hasprefix\_cs | LHS string doesn’t start with the RHS string | Yes | `"DOCS_file" !hasprefix_cs "DOCS"` |
| hassuffix | LHS string ends with the RHS string | No | `"documentation.docx" hassuffix ".docx"` |
| !hassuffix | LHS string doesn’t end with the RHS string | No | `"documentation.docx" !hassuffix ".docx"` |
| hassuffix\_cs | LHS string ends with the RHS string | Yes | `"Document.HTML" hassuffix_cs ".HTML"` |
| !hassuffix\_cs | LHS string doesn’t end with the RHS string | Yes | `"Document.HTML" !hassuffix_cs ".HTML"` |
RHS = right-hand side of the expression
LHS = left-hand side of the expression
## Case-sensitivity [#case-sensitivity]
Operators with `_cs` suffix are case-sensitive.
When two operators do the same task, use the case-sensitive one for better performance.
For example:
* Instead of `=~`, use `==`
* Instead of `has_any`, use `has_any_cs`
* Instead of `contains`, use `contains_cs`
## Best practices [#best-practices]
* Use case-sensitive operators when you know the case to improve performance.
* Avoid complex regular expressions for basic matching tasks. Use basic string operators instead.
* When matching against a set of values, ensure the set is as small as possible to improve performance.
* For matching substrings, use prefix or suffix matching instead of general substring matching for better performance.
## Equality and inequality operators [#equality-and-inequality-operators]
Operators:
* `==`
* `!=`
* `=~`
* `!~`
* `in`
* `!in`
* `in~`
* `!in~`
Query examples:
```kusto
"get" == "get"
"get" != "GET"
"get" =~ "GET"
"get" !~ "put"
```
* Use `==` or `!=` for exact match comparisons when case sensitivity is important.
* Use `=~` or `!~` for case-insensitive comparisons or when you don't know the exact case.
## Subsequence-matching operators [#subsequence-matching-operators]
Operators:
* `contains`
* `!contains`
* `contains_cs`
* `!contains_cs`
* `startswith`
* `!startswith`
* `startswith_cs`
* `!startswith_cs`
* `endswith`
* `!endswith`
* `endswith_cs`
* `!endswith_cs`
Query examples:
```kusto
"parentSpanId" contains "Span" // True
"parentSpanId" !contains "xyz" // True
"parentSpanId" startswith "parent" // True
"parentSpanId" endswith "Id" // True
"parentSpanId" contains_cs "Span" // True if parentSpanId is "parentSpanId", False if parentSpanId is "parentspanid" or "PARENTSPANID"
"parentSpanId" startswith_cs "parent" // True if parentSpanId is "parentSpanId", False if parentSpanId is "ParentSpanId" or "PARENTSPANID"
"parentSpanId" endswith_cs "Id" // True if parentSpanId is "parentSpanId", False if parentSpanId is "parentspanid" or "PARENTSPANID"
```
Use case-sensitive operators (`contains_cs`, `startswith_cs`, `endswith_cs`) when you know the case to improve performance.
## Regular-expression-matching operators [#regular-expression-matching-operators]
Operators:
* `matches regex`
* `!matches regex`
Query examples:
```kusto
"parentSpanId" matches regex "p.*Id" // True
"parentSpanId" !matches regex "x.*z" // True
```
Avoid complex regular expressions or use string operators for simple substring, prefix, or suffix matching.
## Term-matching operators [#term-matching-operators]
A term is a contiguous sequence of Unicode letters and numbers. Any other character, including spaces, hyphens, underscores, and dots, acts as a term boundary. For example, `foo-bar` and `foo_bar` each produce the terms `foo` and `bar`, so `has "foo"` matches both.
Operators:
* `has`
* `!has`
* `has_cs`
* `!has_cs`
* `has_any`
* `has_any_cs`
* `hasprefix`
* `!hasprefix`
* `hasprefix_cs`
* `!hasprefix_cs`
* `hassuffix`
* `!hassuffix`
* `hassuffix_cs`
* `!hassuffix_cs`
Query examples:
```kusto
"North America" has "america" // True
"North America" !has "america" // False
"North America" has_cs "America" // True
"North America" !has_cs "America" // False
"North America" has_any ("america", "asia") // True
"North America" has_any_cs ("America", "Asia") // True
"Admin_User" hasprefix "Admin" // True
"Admin_User" !hasprefix "Admin" // False
"DOCS_file" hasprefix_cs "DOCS" // True
"DOCS_file" !hasprefix_cs "DOCS" // False
"documentation.docx" hassuffix ".docx" // True
"documentation.docx" !hassuffix ".docx" // False
"Document.HTML" hassuffix_cs ".HTML" // True
"Document.HTML" !hassuffix_cs ".HTML" // False
```
* Use `has` or `has_cs` for term matching which can be more efficient than regular expression matching for simple term searches.
* Use `has_any` or `has_any_cs` for matching against multiple possible terms.
* Use `has_cs` or `has_any_cs` when you know the case to improve performance.
* Unlike the `contains` operator, which matches any substring, the `has` operator looks for exact terms, ensuring more precise results.
---
# count
Source: https://axiom.co/docs/apl/tabular-operators/count-operator
The `count` operator in Axiom Processing Language (APL) is a simple yet powerful aggregation function that returns the total number of records in a dataset. You can use it to calculate the number of rows in a table or the results of a query. The `count` operator is useful in scenarios such as log analysis, telemetry data processing, and security monitoring, where you need to know how many events, transactions, or data entries match certain criteria.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| count
```
### Parameters [#parameters]
The `count` operator doesn’t take any parameters. It simply returns the number of records in the dataset or query result.
### Returns [#returns]
`count` returns an integer representing the total number of records in the dataset.
## Use case examples [#use-case-examples]
In this example, you count the total number of HTTP requests in the `['sample-http-logs']` dataset.
**Query**
```kusto
['sample-http-logs']
| count
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20count%22%7D)
**Output**
| count |
| ----- |
| 15000 |
This query returns the total number of HTTP requests recorded in the logs.
In this example, you count the number of traces in the `['otel-demo-traces']` dataset.
**Query**
```kusto
['otel-demo-traces'] |
count
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20count%22%7D)
**Output**
| count |
| ----- |
| 5000 |
This query returns the total number of OpenTelemetry traces in the dataset.
In this example, you count the number of security events in the `['sample-http-logs']` dataset where the status code indicates an error (status codes 4xx or 5xx).
**Query**
```kusto
['sample-http-logs'] |
where status startswith '4' or status startswith '5' |
count
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20startswith%20'4'%20or%20status%20startswith%20'5'%20%7C%20count%22%7D)
**Output**
| count |
| ----- |
| 1200 |
This query returns the number of HTTP requests that resulted in an error (HTTP status code 4xx or 5xx).
## List of related operators [#list-of-related-operators]
* [summarize](/apl/tabular-operators/summarize-operator): The `summarize` operator is used to aggregate data based on one or more fields, allowing you to calculate sums, averages, and other statistics, including counts. Use `summarize` when you need to group data before counting.
* [extend](/apl/tabular-operators/extend-operator): The `extend` operator adds calculated fields to a dataset. You can use `extend` alongside `count` if you want to add additional calculated data to your query results.
* [project](/apl/tabular-operators/project-operator): The `project` operator selects specific fields from a dataset. While `count` returns the total number of records, `project` can limit or change which fields you see.
* [where](/apl/tabular-operators/where-operator): The `where` operator filters rows based on a condition. Use `where` with `count` to only count records that meet certain criteria.
* [take](/apl/tabular-operators/take-operator): The `take` operator returns a specified number of records. You can use `take` to limit results before applying `count` if you’re interested in counting a sample of records.
## Other query languages [#other-query-languages]
In Splunk’s SPL, the `stats count` function is used to count the number of events in a dataset. In APL, the equivalent operation is simply `count`. You can use `count` in APL without the need for additional function wrapping.
```splunk Splunk example
index=web_logs
| stats count
```
```kusto APL equivalent
['sample-http-logs']
| count
```
In ANSI SQL, you typically use `COUNT(*)` or `COUNT(field)` to count the number of rows in a table. In APL, the `count` operator achieves the same functionality, but it doesn’t require a field name or `*`.
```sql SQL example
SELECT COUNT(*) FROM web_logs;
```
```kusto APL equivalent
['sample-http-logs']
| count
```
---
# distinct
Source: https://axiom.co/docs/apl/tabular-operators/distinct-operator
The `distinct` operator in APL (Axiom Processing Language) returns a unique set of values from a specified field or set of fields. This operator is useful when you need to filter out duplicate entries and focus only on distinct values, such as unique user IDs, event types, or error codes within your datasets. Use the `distinct` operator in scenarios where eliminating duplicates helps you gain clearer insights from your data, like when analyzing logs, monitoring system traces, or reviewing security incidents.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| distinct FieldName1 [, FieldName2, ...]
```
### Parameters [#parameters]
* `FieldName1, FieldName2, ...`: The fields to include in the distinct operation. If you specify multiple fields, the result will include rows where the combination of values across these fields is unique.
### Returns [#returns]
The `distinct` operator returns a dataset with unique values from the specified fields, removing any duplicate entries.
## Use case examples [#use-case-examples]
In this use case, the `distinct` operator helps identify unique users who made HTTP requests in a system.
**Query**
```kusto
['sample-http-logs']
| distinct id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20distinct%20id%22%7D)
**Output**
| id |
| --------- |
| user\_123 |
| user\_456 |
| user\_789 |
This query returns a list of unique user IDs that have made HTTP requests, filtering out duplicate user activity.
Here, the `distinct` operator is used to identify all unique services involved in traces.
**Query**
```kusto
['otel-demo-traces']
| distinct ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20distinct%20%5B'service.name'%5D%22%7D)
**Output**
| service.name |
| --------------------- |
| frontend |
| checkoutservice |
| productcatalogservice |
This query returns a distinct list of services involved in traces.
In this example, you use the `distinct` operator to find unique HTTP status codes from security logs.
**Query**
```kusto
['sample-http-logs']
| distinct status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20distinct%20status%22%7D)
**Output**
| status |
| ------ |
| 200 |
| 404 |
| 500 |
This query provides a distinct list of HTTP status codes that occurred in the logs.
## List of related operators [#list-of-related-operators]
* [count](/apl/tabular-operators/count-operator): Returns the total number of rows. Use it to count occurrences of data rather than filtering for distinct values.
* [summarize](/apl/tabular-operators/summarize-operator): Allows you to aggregate data and perform calculations like sums or averages while grouping by distinct values.
* [project](/apl/tabular-operators/project-operator): Selects specific fields from the dataset. Use it when you want to control which fields are returned before applying `distinct`.
## Other query languages [#other-query-languages]
In Splunk’s SPL, the `dedup` command is often used to retrieve distinct values. In APL, the equivalent is the `distinct` operator, which behaves similarly by returning unique values but without necessarily ordering them.
```splunk Splunk example
index=web_logs
| dedup user_id
```
```kusto APL equivalent
['sample-http-logs']
| distinct id
```
In ANSI SQL, you use `SELECT DISTINCT` to return unique rows from a table. In APL, the `distinct` operator serves a similar function but is placed after the table reference rather than in the `SELECT` clause.
```sql SQL example
SELECT DISTINCT user_id FROM web_logs;
```
```kusto APL equivalent
['sample-http-logs']
| distinct id
```
---
# extend
Source: https://axiom.co/docs/apl/tabular-operators/extend-operator
The `extend` operator in APL allows you to create new calculated fields in your result set based on existing data. You can define expressions or functions to compute new values for each row, making `extend` particularly useful when you need to enrich your data without altering the original dataset. You typically use `extend` when you want to add additional fields to analyze trends, compare metrics, or generate new insights from your data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| extend NewField = Expression
```
### Parameters [#parameters]
* `NewField`: The name of the new field to be created.
* `Expression`: The expression used to compute values for the new field. This can include mathematical operations, string manipulations, or functions.
### Returns [#returns]
The operator returns a copy of the original dataset with the following changes:
* Field names noted by `extend` that already exist in the input are removed and appended as their new calculated values.
* Field names noted by `extend` that don’t exist in the input are appended as their new calculated values.
## Use case examples [#use-case-examples]
In log analysis, you can use `extend` to compute the duration of each request in seconds from a millisecond value.
**Query**
```kusto
['sample-http-logs']
| extend duration_sec = req_duration_ms / 1000
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20duration_sec%20%3D%20req_duration_ms%20%2F%201000%22%7D)
**Output**
| \_time | req\_duration\_ms | id | status | uri | method | geo.city | geo.country | duration\_sec |
| ------------------- | ----------------- | ---- | ------ | ----- | ------ | -------- | ----------- | ------------- |
| 2024-10-17 09:00:01 | 300 | 1234 | 200 | /home | GET | London | UK | 0.3 |
This query calculates the duration of HTTP requests in seconds by dividing the `req_duration_ms` field by 1000.
You can use `extend` to create a new field that categorizes the service type based on the service’s name.
**Query**
```kusto
['otel-demo-traces']
| extend service_type = iff(['service.name'] in ('frontend', 'frontendproxy'), 'Web', 'Backend')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20service_type%20%3D%20iff%28%5B%27service.name%27%5D%20in%20%28%27frontend%27%2C%20%27frontendproxy%27%29%2C%20%27Web%27%2C%20%27Backend%27%29%22%7D)
**Output**
| \_time | span\_id | trace\_id | service.name | kind | status\_code | service\_type |
| ------------------- | -------- | --------- | --------------- | ------ | ------------ | ------------- |
| 2024-10-17 09:00:01 | abc123 | xyz789 | frontend | client | 200 | Web |
| 2024-10-17 09:00:01 | def456 | uvw123 | checkoutservice | server | 500 | Backend |
This query adds a new field `service_type` that categorizes the service into either Web or Backend based on the `service.name` field.
For security logs, you can use `extend` to categorize HTTP statuses as success or failure.
**Query**
```kusto
['sample-http-logs']
| extend status_category = iff(status == '200', 'Success', 'Failure')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20status_category%20%3D%20iff%28status%20%3D%3D%20%27200%27%2C%20%27Success%27%2C%20%27Failure%27%29%22%7D)
**Output**
| \_time | id | status | uri | status\_category |
| ------------------- | ---- | ------ | ----- | ---------------- |
| 2024-10-17 09:00:01 | 1234 | 200 | /home | Success |
This query creates a new field `status_category` that labels each HTTP request as either a Success or Failure based on the status code.
## List of related operators [#list-of-related-operators]
* [project](/apl/tabular-operators/project-operator): Use `project` to select specific fields or rename them. Unlike `extend`, it doesn’t add new fields.
* [summarize](/apl/tabular-operators/summarize-operator): Use `summarize` to aggregate data, which differs from `extend` that only adds new calculated fields without aggregation.
## Other query languages [#other-query-languages]
In Splunk, the `eval` command is used to create new fields or modify existing ones. In APL, you can achieve this using the `extend` operator.
```sql Splunk example
index=myindex
| eval newField = duration * 1000
```
```kusto APL equivalent
['sample-http-logs']
| extend newField = req_duration_ms * 1000
```
In ANSI SQL, you typically use the `SELECT` clause with expressions to create new fields. In APL, `extend` is used instead to define these new computed fields.
```sql SQL example
SELECT id, req_duration_ms, req_duration_ms * 1000 AS newField FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend newField = req_duration_ms * 1000
```
---
# extend-valid
Source: https://axiom.co/docs/apl/tabular-operators/extend-valid-operator
The `extend-valid` operator in Axiom Processing Language (APL) allows you to extend a set of fields with new calculated values, where these calculations are based on conditions of validity for each row. It’s particularly useful when working with datasets that contain missing or invalid data, as it enables you to calculate and assign values only when certain conditions are met. This operator helps you keep your data clean by applying calculations to valid data points, and leaving invalid or missing values untouched.
This is a shorthand operator to create a field while also doing basic checking on the validity of the field. In many cases, additional checks are required and it’s recommended in those cases a combination of an [extend](/apl/tabular-operators/extend-operator) and a [where](/apl/tabular-operators/where-operator) operator are used. The basic checks that Axiom preform depend on the type of the expression:
* **Dictionary:** Check if the dictionary isn’t null and has at least one entry.
* **Array:** Check if the array isn’t null and has at least one value.
* **String:** Check if the string isn’t empty and has at least one character.
* **Number:** Check if the value isn’t one of the following: zero, infinity, or NaN.
* **Other types:** The same logic as `tobool` and a check for true.
You can use `extend-valid` to perform conditional transformations on large datasets, especially in scenarios where data quality varies or when dealing with complex log or telemetry data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| extend-valid FieldName1 = Expression1, FieldName2 = Expression2, FieldName3 = ...
```
### Parameters [#parameters]
* `FieldName`: The name of the existing field that you want to extend.
* `Expression`: The expression to evaluate and apply for valid rows.
### Returns [#returns]
The operator returns a table where the specified fields are extended with new values based on the given expression for valid rows. The original value remains unchanged.
## Use case examples [#use-case-examples]
In this use case, you normalize the HTTP request methods by converting them to uppercase for valid entries.
**Query**
```kusto
['sample-http-logs']
| extend-valid upper_method = toupper(method)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend-valid%20upper_method%20%3D%20toupper\(method\)%22%7D)
**Output**
| \_time | method | upper\_method |
| ------------------- | ------ | ------------- |
| 2023-10-01 12:00:00 | get | GET |
| 2023-10-01 12:01:00 | POST | POST |
| 2023-10-01 12:02:00 | NULL | NULL |
In this query, the `toupper` function converts the `method` field to uppercase, but only for valid entries. If the `method` field is null, the result remains null.
In this use case, you extract the first part of the service namespace (before the hyphen) from valid namespaces in the OpenTelemetry traces.
**Query**
```kusto
['otel-demo-traces']
| extend-valid namespace_prefix = extract('^(.*?)-', 1, ['service.namespace'])
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend-valid%20namespace_prefix%20%3D%20extract\('%5E\(.*%3F\)-'%2C%201%2C%20%5B'service.namespace'%5D\)%22%7D)
**Output**
| \_time | service.namespace | namespace\_prefix |
| ------------------- | ------------------ | ----------------- |
| 2023-10-01 12:00:00 | opentelemetry-demo | opentelemetry |
| 2023-10-01 12:01:00 | opentelemetry-prod | opentelemetry |
| 2023-10-01 12:02:00 | NULL | NULL |
In this query, the `extract` function pulls the first part of the service namespace. It only applies to valid `service.namespace` values, leaving nulls unchanged.
In this use case, you extract the first letter of the city names from the `geo.city` field for valid log entries.
**Query**
```kusto
['sample-http-logs']
| extend-valid city_first_letter = extract('^([A-Za-z])', 1, ['geo.city'])
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend-valid%20city_first_letter%20%3D%20extract\('%5E\(%5BA-Za-z%5D\)'%2C%201%2C%20%5B'geo.city'%5D\)%22%7D)
**Output**
| \_time | geo.city | city\_first\_letter |
| ------------------- | -------- | ------------------- |
| 2023-10-01 12:00:00 | New York | N |
| 2023-10-01 12:01:00 | NULL | NULL |
| 2023-10-01 12:02:00 | London | L |
| 2023-10-01 12:03:00 | 1Paris | NULL |
In this query, the `extract` function retrieves the first letter of the city names from the `geo.city` field for valid entries. If the `geo.city` field is null or starts with a non-alphabetical character, no city name is extracted, and the result remains null.
## List of related operators and functions [#list-of-related-operators-and-functions]
* [extend](/apl/tabular-operators/extend-operator): Use `extend` to add calculated fields unconditionally, without validating data.
* [project](/apl/tabular-operators/project-operator): Use `project` to select and rename fields, without performing conditional extensions.
* [summarize](/apl/tabular-operators/summarize-operator): Use `summarize` for aggregation, often used before extending fields with further calculations.
* [isfinite](/apl/scalar-functions/mathematical-functions#isfinite): Determines whether the input is a finite value (neither infinite nor NaN).
## Other query languages [#other-query-languages]
In Splunk SPL, similar functionality is achieved using the `eval` function, but with the `if` command to handle conditional logic for valid or invalid data. In APL, `extend-valid` is more specialized for handling valid data points directly, allowing you to extend fields based on conditions.
```sql Splunk example
| eval new_field = if(isnotnull(field), field + 1, null())
```
```kusto APL equivalent
['sample-http-logs']
| extend-valid new_field = req_duration_ms + 100
```
In ANSI SQL, similar functionality is often achieved using the `CASE WHEN` expression within a `SELECT` statement to handle conditional logic for fields. In APL, `extend-valid` directly extends a field conditionally, based on the validity of the data.
```sql SQL example
SELECT CASE WHEN req_duration_ms IS NOT NULL THEN req_duration_ms + 100 ELSE NULL END AS new_field FROM sample_http_logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend-valid new_field = req_duration_ms + 100
```
---
# externaldata
Source: https://axiom.co/docs/apl/tabular-operators/externaldata-operator
The `externaldata` operator in APL allows you to retrieve data from external storage sources, such as Azure Blob Storage, AWS S3, or HTTP endpoints, and use it within queries. You can specify the schema of the external data and query it as if it were a native dataset. This operator is useful when you need to analyze data that’s stored externally without importing it into Axiom.
The `externaldata` operator currently supports external data sources with a file size of maximum 5 MB.
The `externaldata` operator is currently in public preview. For more information, see [Feature states](/platform-overview/roadmap#feature-states).
## Usage [#usage]
### Syntax [#syntax]
```kusto
externaldata (FieldName1:FieldType1, FieldName2:FieldType2, ...) ["URL1", "URL2", ...] [with (format = "FormatType", ignoreFirstRecord=false)]
```
### Parameters [#parameters]
| Parameter | Description |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FieldName1:FieldType1, FieldName2:FieldType2, ...` | Defines the schema of the external data. |
| `URL1, URL2, ...` | The external storage URIs where the source data resides. |
| `format` | Optional: Specifies the file format. The supported types are `csv`, `scsv`, `tsv`, `psv`, `json`, `multijson`, `raw`, `txt`. |
| `ignoreFirstRecord` | Optional: A Boolean value that specifies whether to ignore the first record in the external data sources. The default is false. Use this property for CSV files with headers. |
### Returns [#returns]
The operator returns a table with the specified schema, containing data retrieved from the external source.
## Use case examples [#use-case-examples]
You have an Axiom dataset that contains access logs with a field `employeeID`. You want to add extra information to your APL query by cross-referencing each employee ID in the Axiom dataset with an employee ID defined in an external lookup table. The lookup table is hosted somewhere else in CSV format.
**External lookup table**
```
employeeID, email, name, location
00001, tifa@acme.com, Tifa Lockhart, US
00002, barret@acme.com, Barret Wallace, Europe
00003, cid@acme.com, Cid Highwind, Europe
```
**Query**
```kusto
let employees = externaldata (employeeID: string, email: string, name: string, location: string) ["http://example.com/lookup-table.csv"] with (format="csv", skipFirstRow=true);
accessLogs
| where severity == "high"
| lookup employees on employeeID
| project _time, severity, employeeID, email, name
```
**Output**
| \_time | severity | employeeID | email | name |
| ---------------- | -------- | ---------- | ----------------------------------------- | -------------- |
| Mar 13, 10:08:23 | high | 00001 | [tifa@acme.com](mailto:tifa@acme.com) | Tifa Lockhart |
| Mar 13, 10:05:03 | high | 00001 | [tifa@acme.com](mailto:tifa@acme.com) | Tifa Lockhart |
| Mar 13, 10:04:51 | high | 00003 | [cid@acme.com](mailto:cid@acme.com) | Cid Highwind |
| Mar 13, 10:02:29 | high | 00002 | [barret@acme.com](mailto:barret@acme.com) | Barret Wallace |
| Mar 13, 10:01:13 | high | 00001 | [tifa@acme.com](mailto:tifa@acme.com) | Tifa Lockhart |
This example extends the original dataset with the fields `email` and `name`. These new fields come from the external lookup table.
Use a lookup table from an external source to extend an OTel logs dataset with a field that contains human-readable names for each service.
**External lookup table**
```
serviceName,humanreadableServiceName
frontend,Frontend
frontendproxy,Frontendproxy
flagd,Flagd
productcatalogservice,Productcatalog
loadgenerator,Loadgenerator
checkoutservice,Checkout
cartservice,Cart
recommendationservice,Recommendations
emailservice,Email
adservice,Ads
shippingservice,Shipping
quoteservice,Quote
currencyservice,Currency
paymentservice,Payment
frauddetectionservice,Frauddetection
```
**Query**
```kusto
let LookupTable = externaldata (serviceName: string, humanreadableServiceName: string) ["http://example.com/lookup-table.csv"] with (format="csv", ignoreFirstRecord=true);
['otel-demo-traces']
| lookup kind=leftouter LookupTable on $left.['service.name'] == $right.serviceName
| project _time, span_id, ['service.name'], humanreadableServiceName
```
**Output**
| \_time | span\_id | service.name | humanreadableServiceName |
| ---------------- | ---------------- | --------------------- | ------------------------ |
| Mar 13, 10:02:28 | 398050797bb646ef | flagd | Flagd |
| Mar 13, 10:02:28 | 0ccd6baca8bea890 | flagd | Flagd |
| Mar 13, 10:02:28 | 2e579cbb3632381a | flagd | Flagd |
| Mar 13, 10:02:29 | 468be2336e35ca32 | loadgenerator | Loadgenerator |
| Mar 13, 10:02:29 | e06348cc4b50ab0d | frontend | Frontend |
| Mar 13, 10:02:29 | 74571a6fa797f769 | frontendproxy | Frontendproxy |
| Mar 13, 10:02:29 | 7ab5eb0a5cd2e0cd | frontendproxy | Frontendproxy |
| Mar 13, 10:02:29 | 050cf3e9ab7efdda | frontend | Frontend |
| Mar 13, 10:02:29 | b2882e3343414175 | frontend | Frontend |
| Mar 13, 10:02:29 | fd7c06a6a746f3e2 | frontend | Frontend |
| Mar 13, 10:02:29 | 606d8a818bec7637 | productcatalogservice | Productcatalog |
## List of related operators [#list-of-related-operators]
* [lookup](/apl/tabular-operators/lookup-operator): Performs joins between a dataset and an external table.
* [union](/apl/tabular-operators/union-operator): Merges multiple datasets, including external ones.
## Other query languages [#other-query-languages]
Splunk doesn’t have a direct equivalent to `externaldata`, but you can use `inputlookup` or `| rest` commands to retrieve data from external sources.
```sql Splunk example
| inputlookup external_data.csv
```
```kusto APL equivalent
externaldata (id:string, timestamp:datetime) ["https://storage.example.com/data.csv"] with (format="csv")
```
In SQL, the equivalent approach is to use `OPENROWSET` to access external data stored in cloud storage.
```sql SQL example
SELECT * FROM OPENROWSET(BULK 'https://storage.example.com/data.csv', FORMAT = 'CSV') AS data;
```
```kusto APL equivalent
externaldata (id:string, timestamp:datetime) ["https://storage.example.com/data.csv"] with (format="csv")
```
---
# getschema
Source: https://axiom.co/docs/apl/tabular-operators/getschema-operator
The `getschema` operator in APL returns the schema of the input, including field names and their data types. You can use it to inspect the structure of the input at any point in your query pipeline. This operator is useful when exploring data structures, verifying data consistency, or debugging queries.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| getschema
```
### Parameters [#parameters]
The `getschema` operator doesn’t take any parameters.
### Returns [#returns]
| Field | Type | Description |
| ------------- | ------ | ----------------------------------------------------- |
| ColumnName | string | The name of the field in the input. |
| ColumnOrdinal | number | The index number of the field in the input. |
| ColumnType | string | The data type of the field. |
| DataType | string | The APL-internal name for the data type of the field. |
## Use case example [#use-case-example]
**Query**
```kusto
['sample-http-logs'] | getschema
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20getschema%22%7D)
**Output**
| ColumnName | DataType | ColumnOrdinal | ColumnType |
| ------------- | -------- | ------------- | ---------- |
| \_sysTime | datetime | 0 | datetime |
| \_time | datetime | 1 | datetime |
| content\_type | string | 2 | string |
| geo.city | string | 3 | string |
| geo.country | string | 4 | string |
| id | string | 5 | string |
## List of related operators [#list-of-related-operators]
* [project](/apl/tabular-operators/project-operator): Use `project` to select specific fields instead of retrieving the entire schema.
* [extend](/apl/tabular-operators/extend-operator): Use `extend` to add new computed fields to your input after understanding the schema.
* [summarize](/apl/tabular-operators/summarize-operator): Use `summarize` for aggregations once you verify field types using `getschema`.
* [where](/apl/tabular-operators/where-operator): Use `where` to filter your input based on field values after checking their schema.
* [order](/apl/tabular-operators/order-operator): Use `order by` to sort your input after verifying schema details.
## Other query languages [#other-query-languages]
In Splunk SPL, you can use the `fieldsummary` command to get schema-related information about your data. However, `getschema` in APL is more direct and focused specifically on returning field names and types without additional summary statistics.
```sql Splunk example
| fieldsummary
```
```kusto APL equivalent
['sample-http-logs']
| getschema
```
In ANSI SQL, retrieving schema information is typically done using `INFORMATION_SCHEMA` queries. APL’s `getschema` operator provides a more straightforward way to get schema details without requiring system views.
```sql SQL example
SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'sample_http_logs';
```
```kusto APL equivalent
['sample-http-logs']
| getschema
```
---
# join
Source: https://axiom.co/docs/apl/tabular-operators/join-operator
The `join` operator in Axiom Processing Language (APL) combines rows from two datasets based on matching values in specified columns. Use `join` to correlate data from different sources or datasets, such as linking logs to traces or enriching logs with additional metadata.
This operator is useful when you want to:
* Combine information from two datasets with shared keys.
* Analyze relationships between different types of events.
* Enrich existing data with supplementary details.
The `join` operator is currently in public preview. For more information, see [Feature states](/platform-overview/roadmap#feature-states).
The preview of the `join` operator works with variable limits depending on the structure of your dataset. For the left side of the join, the limit is 50,000 rows when the dataset has fewer than 100 fields. This limit decreases linearly as the field count increases. For example, the limit is 25,000 rows when your dataset has 200 fields, 12,500 rows at 400 fields, and 10,000 rows at more than 500 fields. The right side of the join has a consistent limit of 50,000 rows.
You can’t use the `join` operator in dashboard queries. To combine data from multiple datasets in a dashboard, rewrite your query without using joins or use alternative approaches such as virtual fields or pre-aggregated data.
## Kinds of join [#kinds-of-join]
The kinds of join and their typical use cases are the following:
* `inner` (default): Returns rows where the join conditions exist in both datasets. All matching rows from the right dataset are included for each matching row in the left dataset. Useful to retain all matches without limiting duplicates.
* `innerunique`: Matches rows from both datasets where the join conditions exist in both. For each row in the left dataset, only the first matching row from the right dataset is returned. Optimized for performance when duplicate matching rows on the right dataset are irrelevant.
* `leftouter`: Returns all rows from the left dataset. If a match exists in the right dataset, the matching rows are included; otherwise, columns from the right dataset are `null`. Retains all data from the left dataset, enriching it with matching data from the right dataset.
* `rightouter`: Returns all rows from the right dataset. If a match exists in the left dataset, the matching rows are included; otherwise, columns from the left dataset are `null`. Retains all data from the right dataset, enriching it with matching data from the left dataset.
* `fullouter`: Returns all rows from both datasets. Matching rows are combined, while non-matching rows from either dataset are padded with `null` values. Combines both datasets while retaining unmatched rows from both sides.
* `leftanti`: Returns rows from the left dataset that have no matches in the right dataset. Identifies rows in the left dataset that don’t have corresponding entries in the right dataset.
* `rightanti`: Returns rows from the right dataset that have no matches in the left dataset. Identifies rows in the right dataset that don’t have corresponding entries in the left dataset.
* `leftsemi`: Returns rows from the left dataset that have at least one match in the right dataset. Only columns from the left dataset are included. Filters rows in the left dataset based on existence in the right dataset.
* `rightsemi`: Returns rows from the right dataset that have at least one match in the left dataset. Only columns from the right dataset are included. Filters rows in the right dataset based on existence in the left dataset.
The preview of the `join` operator currently only supports the following types of join:
* `inner`
* `innerunique`
* `leftouter`
### Summary of kinds of join [#summary-of-kinds-of-join]
| Kind of join | Behavior | Matches returned |
| ------------- | --------------------------------------------------------------------- | ---------------------------------- |
| `inner` | All matches between left and right datasets | Multiple matches allowed |
| `innerunique` | First match for each row in the left dataset | Only unique matches |
| `leftouter` | All rows from the left, with matching rows from the right or `null` | Left-dominant |
| `rightouter` | All rows from the right, with matching rows from the left or `null` | Right-dominant |
| `fullouter` | All rows from both datasets, with unmatched rows padded with `null` | Complete join |
| `leftanti` | Rows in the left dataset with no matches in the right dataset | No matches |
| `rightanti` | Rows in the right dataset with no matches in the left dataset | No matches |
| `leftsemi` | Rows in the left dataset with at least one match in the right dataset | Matching rows (left dataset only) |
| `rightsemi` | Rows in the right dataset with at least one match in the left dataset | Matching rows (right dataset only) |
### Choose the right kind of join [#choose-the-right-kind-of-join]
* Use `inner` for standard joins where you need all matches.
* Use `leftouter` or `rightouter` when you need to retain all rows from one dataset.
* Use `leftanti` or `rightanti` to find rows that don’t match.
* Use `fullouter` for complete combinations of both datasets.
* Use `leftsemi` or `rightsemi` to filter rows based on existence in another dataset.
## Usage [#usage]
### Syntax [#syntax]
```kusto
LeftDataset
| join kind=KindOfJoin RightDataset on Conditions
```
### Parameters [#parameters]
* `LeftDataset`: The first dataset, also known as the outer dataset or the left side of the join. If you expect one of the datasets to contain consistently less data than the other, specify the smaller dataset as the left side of the join.
* `RightDataset`: The second dataset, also known as the inner dataset or the right side of the join.
* `KindOfJoin`: Optionally, the [kind of join](#kinds-of-join) to perform.
* `Conditions`: The conditions for matching rows. The conditions are equality expressions that determine how Axiom matches rows from the `LeftDataset` (left side of the equality expression) with rows from the `RightDataset` (right side of the equality expression). The two sides of the equality expression must have the same data type.
* To join datasets on a field that has the same name in the two datasets, simply use the field name. For example, `on id`.
* To join datasets on a field that has different names in the two datasets, define the two field names in an equality expression such as `on id == trace_id`.
* You can use expressions in the join conditions. For example, to compare two fields of different data types, use `on id_string == tostring(trace_id_int)`.
* You can define multiple join conditions. To separate conditions, use commas (`,`). Don’t use `and`. For example, `on id == trace_id, span == span_id`.
### Returns [#returns]
The `join` operator returns a new table containing rows that match the specified join condition. The fields from the left and right datasets are included.
## Use case example [#use-case-example]
Join HTTP logs with trace data to correlate user activity with performance metrics.
**Query**
```kusto
['otel-demo-traces']
| join kind=inner ['otel-demo-logs'] on trace_id
```
**Output**
| \_time | trace\_id | span\_id | service.name | duration |
| ---------- | --------- | -------- | ------------ | -------- |
| 2024-12-01 | trace123 | span123 | frontend | `500ms` |
This query links user activity in HTTP logs to trace data to investigate performance issues.
## List of related operators [#list-of-related-operators]
* [union](/apl/tabular-operators/union-operator): Combines rows from multiple datasets without requiring a matching condition.
* [where](/apl/tabular-operators/where-operator): Filters rows based on conditions, often used with `join` for more precise results.
## Other query languages [#other-query-languages]
The `join` operator in APL works similarly to the `join` command in Splunk SPL. However, APL provides additional flexibility by supporting various join types (for example, `inner`, `outer`, `leftouter`). Splunk uses a single default join type.
```sql Splunk example
index=logs | join type=inner [search index=traces]
```
```kusto APL equivalent
['sample-http-logs']
| join kind=inner ['otel-demo-traces'] on id == trace_id
```
The `join` operator in APL resembles SQL joins but uses distinct syntax. SQL uses `FROM` and `ON` clauses, whereas APL uses the `join` operator with explicit `kind` and `on` clauses.
```sql SQL example
SELECT *
FROM logs
JOIN traces
ON logs.id = traces.trace_id
```
```kusto APL equivalent
['sample-http-logs']
| join kind=inner ['otel-demo-traces'] on id == trace_id
```
---
# limit
Source: https://axiom.co/docs/apl/tabular-operators/limit-operator
The `limit` operator in Axiom Processing Language (APL) allows you to restrict the number of rows returned from a query. It’s particularly useful when you want to see only a subset of results from large datasets, such as when debugging or previewing query outputs. The `limit` operator can help optimize performance and focus analysis by reducing the amount of data processed.
Use the `limit` operator when you want to return only the top rows from a dataset, especially in cases where the full result set isn’t necessary.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| limit [N]
```
### Parameters [#parameters]
* `N`: The maximum number of rows to return. This must be a non-negative integer.
### Returns [#returns]
The `limit` operator returns the top **`N`** rows from the input dataset. If fewer than **`N`** rows are available, all rows are returned.
When using `limit` with `summarize` where the first grouping expression is a time bin, the limit behavior differs: Axiom computes the global top **N** groups across all time buckets, then limits each time bucket to only include those groups. This can result in more than **N** total rows. For more information, see [summarize](/apl/tabular-operators/summarize-operator#limit-behavior-with-time-binning).
## Use case examples [#use-case-examples]
In log analysis, you often want to view only the most recent entries, and `limit` can help narrow the focus on those rows.
**Query**
```kusto
['sample-http-logs']
| limit 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20limit%205%22%7D)
**Output**
| \_time | req\_duration\_ms | id | status | uri | method | geo.city | geo.country |
| ------------------- | ----------------- | --- | ------ | -------------- | ------ | -------- | ----------- |
| 2024-10-17T12:00:00 | 200 | 123 | 200 | /index.html | GET | New York | USA |
| 2024-10-17T11:59:59 | 300 | 124 | 404 | /notfound.html | GET | London | UK |
This query limits the output to the first 5 rows from the `['sample-http-logs']` dataset, returning recent HTTP log entries.
When analyzing OpenTelemetry traces, you may want to focus on the most recent traces.
**Query**
```kusto
['otel-demo-traces']
| limit 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20limit%205%22%7D)
**Output**
| \_time | duration | span\_id | trace\_id | service.name | kind | status\_code |
| ------------------- | -------- | -------- | --------- | ------------ | ------ | ------------ |
| 2024-10-17T12:00:00 | 500ms | 1abc | 123xyz | frontend | server | OK |
| 2024-10-17T11:59:59 | 200ms | 2def | 124xyz | cartservice | client | OK |
This query retrieves the first 5 rows from the `['otel-demo-traces']` dataset, helping you analyze the latest traces.
For security log analysis, you might want to review the most recent login attempts to ensure no anomalies exist.
**Query**
```kusto
['sample-http-logs']
| where status == '401'
| limit 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20%3D%3D%20'401'%20%7C%20limit%205%22%7D)
**Output**
| \_time | req\_duration\_ms | id | status | uri | method | geo.city | geo.country |
| ------------------- | ----------------- | --- | ------ | ----------- | ------ | -------- | ----------- |
| 2024-10-17T12:00:00 | 300 | 567 | 401 | /login.html | POST | Berlin | Germany |
| 2024-10-17T11:59:59 | 250 | 568 | 401 | /login.html | POST | Sydney | Australia |
This query limits the output to 5 unauthorized access attempts (`401` status code) from the `['sample-http-logs']` dataset.
## List of related operators [#list-of-related-operators]
* [take](/apl/tabular-operators/take-operator): Similar to `limit`, but explicitly focuses on row sampling.
* [top](/apl/tabular-operators/top-operator): Retrieves the top **N** rows sorted by a specific field.
* [sample](/apl/tabular-operators/sample-operator): Randomly samples **N** rows from the dataset.
## Other query languages [#other-query-languages]
In Splunk, the equivalent to APL’s `limit` is the `head` command, which also returns the top rows of a dataset. The main difference is in the syntax.
```sql Splunk example
| head 10
```
```kusto APL equivalent
['sample-http-logs']
| limit 10
```
In ANSI SQL, the `LIMIT` clause is equivalent to the `limit` operator in APL. The SQL `LIMIT` statement is placed at the end of a query, whereas in APL, the `limit` operator comes after the dataset reference.
```sql SQL example
SELECT * FROM sample_http_logs LIMIT 10;
```
```kusto APL equivalent
['sample-http-logs']
| limit 10
```
---
# lookup
Source: https://axiom.co/docs/apl/tabular-operators/lookup-operator
The `lookup` operator extends a primary dataset with a lookup table based on a specified key column. It retrieves matching rows from the lookup table and appends relevant fields to the primary dataset. You can use `lookup` for enriching event data, adding contextual information, or correlating logs with reference tables.
The `lookup` operator is useful when:
* You need to enrich log events with additional metadata, such as mapping user IDs to user profiles.
* You want to correlate security logs with threat intelligence feeds.
* You need to extend OpenTelemetry traces with supplementary details, such as service dependencies.
## Usage [#usage]
### Syntax [#syntax]
```kusto
PrimaryDataset
| lookup kind=KindOfLookup LookupTable on Conditions
```
### Parameters [#parameters]
* `PrimaryDataset`: The primary dataset that you want to extend. If you expect one of the tables to contain consistently more data than the other, specify the larger table as the primary dataset.
* `LookupTable`: The data table containing additional data, also known as the dimension table or lookup table.
* `KindOfLookup`: Optionally, specifies the lookup type as `leftouter` or `inner`. The default is `leftouter`.
* `leftouter` lookup includes all rows from the primary dataset even if they don’t match the conditions. In unmatched rows, the new fields contain nulls.
* `inner` lookup only includes rows from the primary dataset if they match the conditions. Unmatched rows are excluded from the output.
* `Conditions`: The conditions for matching rows from `PrimaryDataset` to rows from `LookupTable`. The conditions are equality expressions that determine how Axiom matches rows from the `PrimaryDataset` (left side of the equality expression) with rows from the `LookupTable` (right side of the equality expression). The two sides of the equality expression must have the same data type.
* To use `lookup` on a key column that has the same name in the primary dataset and the lookup table, simply use the field name. For example, `on id`.
* To use `lookup` on a key column that has different names in the primary dataset and the lookup table, define the two field names in an equality expression such as `on id == trace_id`.
* You can define multiple conditions. To separate conditions, use commas (`,`). Don’t use `and`. For example, `on id == trace_id, span == span_id`.
### Returns [#returns]
A dataset where rows from `PrimaryDataset` are enriched with matching columns from `LookupTable` based on the key column.
## Use case example [#use-case-example]
Add a field with human-readable names for each service.
**Query**
```kusto
let LookupTable=datatable(serviceName:string, humanreadableServiceName:string)[
'frontend', 'Frontend',
'frontendproxy', 'Frontend proxy',
'flagd', 'Flagd',
'productcatalogservice', 'Product catalog',
'loadgenerator', 'Load generator',
'checkoutservice', 'Checkout',
'cartservice', 'Cart',
'recommendationservice', 'Recommendations',
'emailservice', 'Email',
'adservice', 'Ads',
'shippingservice', 'Shipping',
'quoteservice', 'Quote',
'currencyservice', 'Currency',
'paymentservice', 'Payment',
'frauddetectionservice', 'Fraud detection',
];
['otel-demo-traces']
| lookup kind=leftouter LookupTable on $left.['service.name'] == $right.serviceName
| project _time, span_id, ['service.name'], humanreadableServiceName
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22let%20LookupTable%3Ddatatable\(serviceName%3Astring%2C%20humanreadableServiceName%3Astring\)%5B%20'frontend'%2C%20'Frontend'%2C%20'frontendproxy'%2C%20'Frontend%20proxy'%2C%20'flagd'%2C%20'Flagd'%2C%20'productcatalogservice'%2C%20'Product%20catalog'%2C%20'loadgenerator'%2C%20'Load%20generator'%2C%20'checkoutservice'%2C%20'Checkout'%2C%20'cartservice'%2C%20'Cart'%2C%20'recommendationservice'%2C%20'Recommendations'%2C%20'emailservice'%2C%20'Email'%2C%20'adservice'%2C%20'Ads'%2C%20'shippingservice'%2C%20'Shipping'%2C%20'quoteservice'%2C%20'Quote'%2C%20'currencyservice'%2C%20'Currency'%2C%20'paymentservice'%2C%20'Payment'%2C%20'frauddetectionservice'%2C%20'Fraud%20detection'%2C%20%5D%3B%20%5B'otel-demo-traces'%5D%20%7C%20lookup%20kind%3Dleftouter%20LookupTable%20on%20%24left.%5B'service.name'%5D%20%3D%3D%20%24right.serviceName%20%7C%20project%20_time%2C%20span_id%2C%20%5B'service.name'%5D%2C%20humanreadableServiceName%22%7D)
**Output**
| \_time | span\_id | service.name | humanreadableServiceName |
| ---------------- | ------------------ | ------------- | ------------------------ |
| Feb 27, 12:01:55 | 15bf0a95dfbfcd77 | loadgenerator | Load generator |
| Feb 27, 12:01:55 | 86c27626407be459 | frontendproxy | Frontend proxy |
| Feb 27, 12:01:55 | `89d9b5687056b1cf` | frontendproxy | Frontend proxy |
| Feb 27, 12:01:55 | bbc1bac7ebf6ce8a | frontend | Frontend |
| Feb 27, 12:01:55 | cd12307e154a4817 | frontend | Frontend |
| Feb 27, 12:01:55 | 21fd89efd3d36b15 | frontend | Frontend |
| Feb 27, 12:01:55 | c6e8db2d149ab273 | frontend | Frontend |
| Feb 27, 12:01:55 | fd569a8fce7a8446 | cartservice | Cart |
| Feb 27, 12:01:55 | ed61fac37e9bf220 | loadgenerator | Load generator |
| Feb 27, 12:01:55 | 83fdf8a30477e726 | frontend | Frontend |
| Feb 27, 12:01:55 | `40d94294da7b04ce` | frontendproxy | Frontend proxy |
## List of related operators [#list-of-related-operators]
* [join](/apl/tabular-operators/join-operator): Performs more flexible join operations, including left, right, and outer joins.
* [project](/apl/tabular-operators/project-operator): Selects specific columns from a dataset, which can be used to refine the output of a lookup operation.
* [union](/apl/tabular-operators/union-operator): Combines multiple datasets without requiring a key column.
## Other query languages [#other-query-languages]
In Splunk SPL, the `lookup` command performs a similar function by enriching event data with fields from an external lookup table. However, unlike Splunk, APL’s `lookup` operator only performs an inner join.
```sql Splunk example
index=web_logs | lookup port_lookup port AS client_port OUTPUT service_name
```
```kusto APL equivalent
['sample-http-logs']
| lookup kind=inner ['port_lookup'] on port
```
In ANSI SQL, `lookup` is similar to an `INNER JOIN`, where records from both tables are matched based on a common key. Unlike SQL, APL doesn’t support other types of joins in `lookup`.
```sql SQL example
SELECT logs.*, ports.service_name
FROM logs
INNER JOIN port_lookup ports ON logs.port = ports.port;
```
```kusto APL equivalent
['sample-http-logs']
| lookup kind=inner ['port_lookup'] on port
```
---
# make-series
Source: https://axiom.co/docs/apl/tabular-operators/make-series
## Introduction [#introduction]
The `make-series` operator transforms event data into array-based time series. Instead of producing one row per time bucket, `make-series` encodes the values and corresponding timestamps into arrays stored in table fields. This makes it possible to apply `series_*` functions for advanced manipulations such as moving averages, smoothing, anomaly detection, or other time-series computations.
You find this operator useful when you want to:
* Turn event data into array-encoded time series for further analysis.
* Apply `series_*` functions (for example, `series_fir`, `series_stats`) to aggregated data.
* Postprocess and then expand arrays back into rows with `mv-expand` for visualization or downstream queries.
Unlike `summarize`, which produces row-based aggregations, `make-series` is designed specifically for creating and manipulating array-based time series.
## Usage [#usage]
### Syntax [#syntax]
```kusto
make-series [Aggregation [, ...]]
[default = DefaultValue]
on TimeField
[in Range]
step StepSize
[by GroupingField [, ...]]
```
### Parameters [#parameters]
| Parameter | Description |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `Aggregation` | One or more aggregation functions (for example, `avg()`, `count()`, `sum()`) applied to each time bin, producing arrays of values. |
| `default` | A value to use when no records exist in a time bin. |
| `TimeField` | The field containing timestamps used for binning. |
| `Range` | An optional range expression specifying the start and end of the series (for example, `from ago(1h) to now()`). |
| `StepSize` | The size of each time bin (for example, `1m`, `5m`, `1h`). |
| `GroupingField` | Optional fields to split the series by, producing parallel arrays for each group. |
### Returns [#returns]
The operator returns a table where each aggregation produces an array of values aligned with an array of time bins. Each row represents a group (if specified), with arrays that encode the entire time series.
## Use case examples [#use-case-examples]
You want to create an array-based time series of request counts, then compute a rolling average using a `series_*` function, and finally expand back into rows for visualization.
**Query**
```kusto
['sample-http-logs']
| make-series count() on _time from now()-24h to now() step 5m
| extend moving_avg_count=series_fir(count_, dynamic([1, 1, 1, 1, 1]))
| mv-expand moving_avg_count to typeof(long), count_ to typeof(long), time to typeof(datetime)
| project-rename _time=time
| summarize avg(moving_avg_count), avg(count_) by bin(_time, 5m)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20make-series%20count\(\)%20on%20_time%20from%20now\(\)-24h%20to%20now\(\)%20step%205m%20%7C%20extend%20moving_avg_count%3Dseries_fir\(count_%2C%20dynamic\(%5B1%2C%201%2C%201%2C%201%2C%201%5D\)\)%20%7C%20mv-expand%20moving_avg_count%20to%20typeof\(long\)%2C%20count_%20to%20typeof\(long\)%2C%20time%20to%20typeof\(datetime\)%20%7C%20project-rename%20_time%3Dtime%20%7C%20summarize%20avg\(moving_avg_count\)%2C%20avg\(count_\)%20by%20bin\(_time%2C%205m\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%221d%22%7D%7D)
**Output**
| \_time | count\_ | moving\_avg\_count |
| ------------------- | ------- | ------------------ |
| 2025-09-29T10:00:00 | 120 | 118 |
| 2025-09-29T10:05:00 | 130 | 122 |
| 2025-09-29T10:10:00 | 110 | 121 |
The query turns request counts into arrays, applies a smoothing function, and then expands the arrays back into rows for analysis.
You want to analyze span durations per service, storing them as arrays for later manipulation.
**Query**
```kusto
['otel-demo-traces']
| make-series avg(duration) on _time from ago(2h) to now() step 10m by ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20make-series%20avg\(duration\)%20on%20_time%20from%20ago\(2h\)%20to%20now\(\)%20step%2010m%20by%20%5B'service.name'%5D%22%7D)
**Output**
| service.name | avg\_duration | time |
| ------------ | ------------------------------ | ------------------------ |
| frontend | \[20ms, 18ms, 22ms, 19ms, ...] | \[2025-09-29T08:00, ...] |
| checkout | \[35ms, 40ms, 33ms, 37ms, ...] | \[2025-09-29T08:00, ...] |
The query produces array-encoded time series per service, which you can further process with `series_*` functions.
You want to analyze the rate of HTTP 500 errors in your logs per minute.
**Query**
```kusto
['sample-http-logs']
| where status == '500'
| make-series count() default=0 on _time from ago(30m) to now() step 1m
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20%3D%3D%20'500'%20%7C%20make-series%20count\(\)%20default%3D0%20on%20_time%20from%20ago\(30m\)%20to%20now\(\)%20step%201m%22%7D)
**Output**
| count\_ | \_time |
| ------------------------------------ | -------------------------------------------------------------------------- |
| \[1489, 1428, 1517, 1462, 1509, ...] | \["2025-09-30T09:08:14.921301725Z", "2025-09-30T09:09:14.921301725Z", ...] |
The query generates a time series of HTTP 500 error counts as an array-based time series for further analysis with `series_*` functions.
## List of related operators [#list-of-related-operators]
* [extend](/apl/tabular-operators/extend-operator): Creates new calculated fields, often as preparation before `make-series`. Use `extend` when you want to preprocess data for time series analysis.
* [mv-expand](/apl/tabular-operators/mv-expand): Expands arrays into multiple rows. Use `mv-expand` to work with the arrays returned by `make-series`.
* [summarize](/apl/tabular-operators/summarize-operator): Aggregates rows into groups but doesn't generate continuous time bins. Use `summarize` when you want flexible grouping without forcing evenly spaced intervals.
* [top](/apl/tabular-operators/top-operator): Returns the top rows by a specified expression, not time series. Use `top` when you want to focus on the most significant values instead of trends over time.
## Other query languages [#other-query-languages]
In Splunk SPL, the `timechart` command creates row-based time series, with one row per time bucket. In APL, the `make-series` operator instead encodes the series into arrays, which you can later manipulate or expand. This is a key difference from SPL’s row-based approach.
```sql Splunk example
index=sample-http-logs
| timechart span=1m avg(req_duration_ms)
```
```kusto APL equivalent
['sample-http-logs']
| make-series avg(req_duration_ms) default=0 on _time from ago(1h) to now() step 1m
```
In ANSI SQL, you typically use `GROUP BY` with a generated series or calendar table to create row-based time buckets. In APL, `make-series` creates arrays of values and timestamps in a single row. This lets you perform array-based computations on the time series before optionally expanding back into rows.
```sql SQL example
SELECT
time_bucket('1 minute', _time) AS minute,
AVG(req_duration_ms) AS avg_duration
FROM sample_http_logs
WHERE _time > NOW() - interval '1 hour'
GROUP BY minute
ORDER BY minute
```
```kusto APL equivalent
['sample-http-logs']
| make-series avg(req_duration_ms) default=0 on _time from ago(1h) to now() step 1m
```
---
# mv-expand
Source: https://axiom.co/docs/apl/tabular-operators/mv-expand
The `mv-expand` operator expands dynamic arrays and property bags into multiple rows. Each element of the array or each property of the bag becomes its own row, while other columns are duplicated.
You use `mv-expand` when you want to analyze or filter individual values inside arrays or objects. This is especially useful when working with logs that include lists of values, OpenTelemetry traces that contain arrays of spans, or security events that group multiple attributes into one field.
## Usage [#usage]
### Syntax [#syntax]
```kusto
mv-expand [kind=(bag|array)] [with_itemindex=IndexFieldName] FieldName [to typeof(Typename)] [limit Rowlimit]
```
### Parameters [#parameters]
| Parameter | Description |
| ------------------------------- | ------------------------------------------------------------------------------------------ |
| `kind` | Optional. Specifies whether the column is a bag (object) or an array. Defaults to `array`. |
| `with_itemindex=IndexFieldName` | Optional. Outputs an additional column with the zero-based index of the expanded item. |
| `FieldName` | Required. The name of the column that contains an array or object to expand. |
| `to typeof(Typename)` | Optional. Converts each expanded element to the specified type. |
| `limit Rowlimit` | Optional. Limits the number of expanded rows per record. |
### Returns [#returns]
The operator returns a table where each element of the expanded array or each property of the expanded object is placed in its own row. Other columns are duplicated for each expanded row.
## Use case example [#use-case-example]
When analyzing logs, some values can be stored as arrays. You can use `mv-expand` to expand them into individual rows for easier filtering.
**Query**
```kusto
['sample-http-logs']
| limit 100
| mv-expand territories
| summarize count = count() by territory_name = tostring(territories)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20limit%20100%20%7C%20mv-expand%20territories%20%7C%20summarize%20count%20%3D%20count\(\)%20by%20territory_name%20%3D%20tostring\(territories\)%22%7D)
**Output**
| territory\_name | count |
| --------------- | ----- |
| United States | 67 |
| India | 22 |
| Japan | 12 |
This query expands the `territories` array into rows and counts the most frequent territories.
## List of related operators [#list-of-related-operators]
* [project](/apl/tabular-operators/project-operator): Selects or computes columns. Use it when you want to reshape data, not expand arrays.
* [summarize](/apl/tabular-operators/summarize-operator): Aggregates data across rows. Use it after expanding arrays to compute statistics.
* [top](/apl/tabular-operators/top-operator): Returns the top N rows by expression. Use it after expansion to find the most frequent values.
## Other query languages [#other-query-languages]
In Splunk SPL, the `mvexpand` command expands multi-value fields into separate events. The APL `mv-expand` operator works in a very similar way, splitting array values into individual rows. The main difference is that APL explicitly works with dynamic arrays or property bags, while Splunk handles multi-value fields implicitly.
```sql Splunk example
... | mvexpand request_uri
```
```kusto APL equivalent
['sample-http-logs']
| mv-expand uri
```
In ANSI SQL, you use `CROSS JOIN UNNEST` or `CROSS APPLY` to flatten arrays into rows. In APL, `mv-expand` provides a simpler and more direct way to achieve the same result.
```sql SQL example
SELECT id, value
FROM logs
CROSS JOIN UNNEST(request_uris) AS t(value)
```
```kusto APL equivalent
['sample-http-logs']
| mv-expand uri
```
---
# order
Source: https://axiom.co/docs/apl/tabular-operators/order-operator
The `order` operator in Axiom Processing Language (APL) allows you to sort the rows of a result set by one or more specified fields. You can use this operator to organize data for easier interpretation, prioritize specific values, or prepare data for subsequent analysis steps. The `order` operator is particularly useful when working with logs, telemetry data, or any dataset where ranking or sorting by values (such as time, status, or user ID) is necessary.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| order by FieldName [asc | desc], FieldName [asc | desc]
```
### Parameters [#parameters]
* `FieldName`: The name of the field by which to sort.
* `asc`: Sorts the field in ascending order.
* `desc`: Sorts the field in descending order.
### Returns [#returns]
The `order` operator returns the input dataset, sorted according to the specified fields and order (ascending or descending). If multiple fields are specified, sorting is done based on the first field, then by the second if values in the first field are equal, and so on.
## Use case examples [#use-case-examples]
In this example, you sort HTTP logs by request duration in descending order to prioritize the longest requests.
**Query**
```kusto
['sample-http-logs']
| order by req_duration_ms desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20order%20by%20req_duration_ms%20desc%22%7D)
**Output**
| \_time | req\_duration\_ms | id | status | uri | method | geo.city | geo.country |
| ------------------- | ----------------- | ------ | ------ | -------------------- | ------ | -------- | ----------- |
| 2024-10-17 10:10:01 | 1500 | user12 | 200 | /api/v1/get-orders | GET | Seattle | US |
| 2024-10-17 10:09:47 | 1350 | user23 | 404 | /api/v1/get-products | GET | New York | US |
| 2024-10-17 10:08:21 | 1200 | user45 | 500 | /api/v1/post-order | POST | London | UK |
This query sorts the logs by request duration, helping you identify which requests are taking the most time to complete.
In this example, you sort OpenTelemetry trace data by span duration in descending order, which helps you identify the longest-running spans across your services.
**Query**
```kusto
['otel-demo-traces']
| order by duration desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20order%20by%20duration%20desc%22%7D)
**Output**
| \_time | duration | span\_id | trace\_id | service.name | kind | status\_code |
| ------------------- | -------- | -------- | --------- | --------------------- | ------ | ------------ |
| 2024-10-17 10:10:01 | 15.3s | span4567 | trace123 | frontend | server | 200 |
| 2024-10-17 10:09:47 | 12.4s | span8910 | trace789 | checkoutservice | client | 200 |
| 2024-10-17 10:08:21 | 10.7s | span1112 | trace456 | productcatalogservice | server | 500 |
This query helps you detect performance bottlenecks by sorting spans based on their duration.
In this example, you analyze security logs by sorting them by time to view the most recent logs.
**Query**
```kusto
['sample-http-logs']
| order by _time desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20order%20by%20_time%20desc%22%7D)
**Output**
| \_time | req\_duration\_ms | id | status | uri | method | geo.city | geo.country |
| ------------------- | ----------------- | ------ | ------ | ---------------------- | ------ | -------- | ----------- |
| 2024-10-17 10:10:01 | 300 | user34 | 200 | /api/v1/login | POST | Berlin | DE |
| 2024-10-17 10:09:47 | 150 | user78 | 401 | /api/v1/get-profile | GET | Paris | FR |
| 2024-10-17 10:08:21 | 200 | user56 | 500 | /api/v1/update-profile | PUT | Madrid | ES |
This query sorts the security logs by time to display the most recent log entries first, helping you quickly review recent security events.
## List of related operators [#list-of-related-operators]
* [top](/apl/tabular-operators/top-operator): The `top` operator returns the top N records based on a specific sorting criteria, which is similar to `order` but only retrieves a fixed number of results.
* [summarize](/apl/tabular-operators/summarize-operator): The `summarize` operator groups data and often works in combination with `order` to rank summarized values.
* [extend](/apl/tabular-operators/extend-operator): The `extend` operator can be used to create calculated fields, which can then be used as sorting criteria in the `order` operator.
## Other query languages [#other-query-languages]
In Splunk SPL, the equivalent operator to `order` is `sort`. SPL uses a similar syntax to APL but with some differences. In SPL, `sort` allows both ascending (`asc`) and descending (`desc`) sorting, while in APL, you achieve sorting using the `asc()` and `desc()` functions for fields.
```splunk Splunk example
| sort - _time
```
```kusto APL equivalent
['sample-http-logs']
| order by _time desc
```
In ANSI SQL, the equivalent of `order` is `ORDER BY`. SQL uses `ASC` for ascending and `DESC` for descending order. In APL, sorting works similarly, with the `asc()` and `desc()` functions added around field names to specify the order.
```sql SQL example
SELECT * FROM logs ORDER BY _time DESC;
```
```kusto APL equivalent
['sample-http-logs']
| order by _time desc
```
---
# Tabular operators
Source: https://axiom.co/docs/apl/tabular-operators/overview
The table summarizes the tabular operators available in APL.
| Function | Description |
| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [count](/apl/tabular-operators/count-operator) | Returns an integer representing the total number of records in the dataset. |
| [distinct](/apl/tabular-operators/distinct-operator) | Returns a dataset with unique values from the specified fields, removing any duplicate entries. |
| [extend](/apl/tabular-operators/extend-operator) | Returns the original dataset with one or more new fields appended, based on the defined expressions. |
| [extend-valid](/apl/tabular-operators/extend-valid-operator) | Returns a table where the specified fields are extended with new values based on the given expression for valid rows. |
| [externaldata](/apl/tabular-operators/externaldata-operator) | Returns a table with the specified schema, containing data retrieved from an external source. |
| [getschema](/apl/tabular-operators/getschema-operator) | Returns the schema of the input, including field names and their data types |
| [join](/apl/tabular-operators/join-operator) | Returns a dataset containing rows from two different tables based on conditions. |
| [limit](/apl/tabular-operators/limit-operator) | Returns the top N rows from the input dataset. |
| [lookup](/apl/tabular-operators/lookup-operator) | Returns a dataset where rows from one dataset are enriched with matching columns from a lookup table based on conditions. |
| [make-series](/apl/tabular-operators/make-series) | Returns a dataset where the specified field is aggregated into a time series. |
| [mv-expand](/apl/tabular-operators/mv-expand) | Returns a dataset where the specified field is expanded into multiple rows. |
| [order](/apl/tabular-operators/order-operator) | Returns the input dataset, sorted according to the specified fields and order. |
| [parse](/apl/tabular-operators/parse-operator) | Returns the input dataset with new fields added based on the specified parsing pattern. |
| [parse-kv](/apl/tabular-operators/parse-kv) | Returns a dataset where key-value pairs are extracted from a string field into individual columns. |
| [parse-where](/apl/tabular-operators/parse-where) | Returns a dataset where values from a string are extracted based on a pattern. |
| [project](/apl/tabular-operators/project-operator) | Returns a dataset containing only the specified fields. |
| [project-away](/apl/tabular-operators/project-away-operator) | Returns the input dataset excluding the specified fields. |
| [project-keep](/apl/tabular-operators/project-keep-operator) | Returns a dataset with only the specified fields. |
| [project-rename](/apl/tabular-operators/project-rename) | Returns a dataset where the specified field is renamed according to the specified pattern. |
| [project-reorder](/apl/tabular-operators/project-reorder-operator) | Returns a table with the specified fields reordered as requested followed by any unspecified fields in their original order. |
| [redact](/apl/tabular-operators/redact-operator) | Returns the input dataset with sensitive data replaced or hashed. |
| [sample](/apl/tabular-operators/sample-operator) | Returns a table containing the specified number of rows, selected randomly from the input dataset. |
| [search](/apl/tabular-operators/search-operator) | Returns all rows where the specified keyword appears in any field. |
| [sort](/apl/tabular-operators/sort-operator) | Returns a table with rows ordered based on the specified fields. |
| [summarize](/apl/tabular-operators/summarize-operator) | Returns a table where each row represents a unique combination of values from the by fields, with the aggregated results calculated for the other fields. |
| [take](/apl/tabular-operators/take-operator) | Returns the specified number of rows from the dataset. |
| [top](/apl/tabular-operators/top-operator) | Returns the top N rows from the dataset based on the specified sorting criteria. |
| [union](/apl/tabular-operators/union-operator) | Returns all rows from the specified tables or queries. |
| [where](/apl/tabular-operators/where-operator) | Returns a filtered dataset containing only the rows where the condition evaluates to true. |
---
# parse-kv
Source: https://axiom.co/docs/apl/tabular-operators/parse-kv
The `parse-kv` operator parses key-value pairs from a string field into individual columns. You use it when your data is stored in a single string that contains structured information, such as `key=value` pairs. With `parse-kv`, you can extract the values into separate columns to make them easier to query, filter, and analyze.
This operator is useful in scenarios where logs, traces, or security events contain metadata encoded as key-value pairs. Instead of manually splitting strings, you can use `parse-kv` to transform the data into a structured format.
## Usage [#usage]
### Syntax [#syntax]
```kusto
parse-kv Expression as (KeysList) with (pair_delimiter = PairDelimiter, kv_delimiter = KvDelimiter [, Options...])
```
### Parameters [#parameters]
| Parameter | Description |
| --------------- | ------------------------------------------------------------------------------- |
| `Expression` | The string expression that contains the key-value pairs. |
| `KeysList` | A list of keys to extract into separate columns. |
| `PairDelimiter` | A character or string that separates key-value pairs (for example, `;` or `,`). |
| `KvDelimiter` | A character or string that separates keys and values (for example, `=` or `:`). |
| `Options` | Additional parsing options, such as case sensitivity. |
### Returns [#returns]
A dataset where each specified key is extracted into its own column with the corresponding value. If a key is missing in the original string, the column is empty for that row.
## Use case example [#use-case-example]
When analyzing HTTP logs, you might encounter a field where request metadata is encoded as key-value pairs. You can extract values like status and duration for easier analysis.
**Query**
```kusto
['sample-http-logs']
| parse-kv kvdata as (status, req_duration_ms) with (pair_delimiter=';', kv_delimiter='=')
| project _time, status, req_duration_ms, method, uri
```
**Output**
| \_time | status | req\_duration\_ms | method | uri |
| -------------------- | ------ | ----------------- | ------ | -------- |
| 2024-05-01T10:00:00Z | 200 | 120 | GET | /home |
| 2024-05-01T10:01:00Z | 404 | 35 | GET | /missing |
This query extracts status and request duration from a concatenated field and projects them alongside other useful fields.
## List of related operators [#list-of-related-operators]
* [extend](/apl/tabular-operators/extend-operator): Adds calculated columns. Use when parsing isn't required but you want to create new derived columns.
* [parse](/apl/tabular-operators/parse-operator): Extracts values from a string expression without filtering out non-matching rows. Use when you want to keep all rows, including those that fail to parse.
* [project](/apl/tabular-operators/project-operator): Selects and computes columns without parsing. Use when you want to transform data rather than extract values.
* [where](/apl/tabular-operators/where-operator): Filters rows based on conditions. Use alongside parsing functions if you want more control over filtering logic.
## Other query languages [#other-query-languages]
In Splunk, you often use the `kv` or `extract` commands to parse key-value pairs from raw log data. In APL, you achieve similar functionality with the `parse-kv` operator. The difference is that `parse-kv` explicitly lets you define which keys to extract and what delimiters to use.
```sql Splunk example
... | kv pairdelim=";" kvdelim="=" keys="key1,key2,key3"
```
```kusto APL equivalent
datatable(data:string)
[
'key1=a;key2=b;key3=c'
]
| parse-kv data as (key1, key2, key3) with (pair_delimiter=';', kv_delimiter='=')
```
ANSI SQL does not have a direct equivalent of `parse-kv`. Typically, you would use string functions such as `SUBSTRING` or `SPLIT_PART` to manually extract key-value pairs. In APL, `parse-kv` simplifies this process by automatically extracting multiple keys in one step.
```sql SQL example
SELECT
SUBSTRING_INDEX(SUBSTRING_INDEX(data, ';', 1), '=', -1) as key1,
SUBSTRING_INDEX(SUBSTRING_INDEX(data, ';', 2), '=', -1) as key2,
SUBSTRING_INDEX(SUBSTRING_INDEX(data, ';', 3), '=', -1) as key3
FROM logs;
```
```kusto APL equivalent
datatable(data:string)
[
'key1=a;key2=b;key3=c'
]
| parse-kv data as (key1, key2, key3) with (pair_delimiter=';', kv_delimiter='=')
```
---
# parse
Source: https://axiom.co/docs/apl/tabular-operators/parse-operator
The `parse` operator in APL enables you to extract and structure information from unstructured or semi-structured text data, such as log files or strings. You can use the operator to specify a pattern for parsing the data and define the fields to extract. This is useful when analyzing logs, tracing information from text fields, or extracting key-value pairs from message formats.
You can find the `parse` operator helpful when you need to process raw text fields and convert them into a structured format for further analysis. It’s particularly effective when working with data that doesn’t conform to a fixed schema, such as log entries or custom messages.
## Importance of the parse operator [#importance-of-the-parse-operator]
* **Data extraction:** It allows you to extract structured data from unstructured or semi-structured string fields, enabling you to transform raw data into a more usable format.
* **Flexibility:** The parse operator supports different parsing modes (simple, relaxed, regex) and provides various options to define parsing patterns, making it adaptable to different data formats and requirements.
* **Performance:** By extracting only the necessary information from string fields, the parse operator helps optimize query performance by reducing the amount of data processed and enabling more efficient filtering and aggregation.
* **Readability:** The parse operator provides a clear and concise way to define parsing patterns, making the query code more readable and maintainable.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| parse [kind=simple|regex|relaxed] Expression with [*] StringConstant FieldName [: FieldType] [*] ...
```
### Parameters [#parameters]
* `kind`: Optional parameter to specify the parsing mode. Its value can be `simple` for exact matches, `regex` for regular expressions, or `relaxed` for relaxed parsing. The default is `simple`.
* `Expression`: The string expression to parse.
* `StringConstant`: A string literal or regular expression pattern to match against.
* `FieldName`: The name of the field to assign the extracted value.
* `FieldType`: Optional parameter to specify the data type of the extracted field. The default is `string`.
* `*`: Wildcard to match any characters before or after the `StringConstant`.
* `...`: You can specify additional `StringConstant` and `FieldName` pairs to extract multiple values.
### Returns [#returns]
The parse operator returns the input dataset with new fields added based on the specified parsing pattern. The new fields contain the extracted values from the parsed string expression. If the parsing fails for a particular row, the corresponding fields have null values.
## Use case examples [#use-case-examples]
For log analysis, you can extract the HTTP request duration from the `uri` field using the `parse` operator.
**Query**
```kusto
['sample-http-logs']
| parse uri with * 'duration=' req_duration_ms:int
| project _time, req_duration_ms, uri
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20parse%20uri%20with%20%2A%20'duration%3D'%20req_duration_ms%3Aint%20%7C%20project%20_time%2C%20req_duration_ms%2C%20uri%22%7D)
**Output**
| \_time | req\_duration\_ms | uri |
| ------------------- | ----------------- | ----------------------------- |
| 2024-10-18T12:00:00 | 200 | /api/v1/resource?duration=200 |
| 2024-10-18T12:00:05 | 300 | /api/v1/resource?duration=300 |
This query extracts the `req_duration_ms` from the `uri` field and projects the time and duration for each HTTP request.
In OpenTelemetry traces, the `parse` operator is useful for extracting components of trace data, such as the service name or status code.
**Query**
```kusto
['otel-demo-traces']
| parse trace_id with * '-' ['service.name']
| project _time, ['service.name'], trace_id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20parse%20trace_id%20with%20%2A%20'-'%20%5B'service.name'%5D%20%7C%20project%20_time%2C%20%5B'service.name'%5D%2C%20trace_id%22%7D)
**Output**
| \_time | service.name | trace\_id |
| ------------------- | ------------ | -------------------- |
| 2024-10-18T12:00:00 | frontend | a1b2c3d4-frontend |
| 2024-10-18T12:01:00 | cartservice | e5f6g7h8-cartservice |
This query extracts the `service.name` from the `trace_id` and projects the time and service name for each trace.
For security logs, you can use the `parse` operator to extract status codes and the method of HTTP requests.
**Query**
```kusto
['sample-http-logs']
| parse method with * '/' status
| project _time, method, status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20parse%20method%20with%20%2A%20'%2F'%20status%20%7C%20project%20_time%2C%20method%2C%20status%22%7D)
**Output**
| \_time | method | status |
| ------------------- | ------ | ------ |
| 2024-10-18T12:00:00 | GET | 200 |
| 2024-10-18T12:00:05 | POST | 404 |
This query extracts the HTTP method and status from the `method` field and shows them along with the timestamp.
## Other examples [#other-examples]
### Parse content type [#parse-content-type]
This example parses the `content_type` field to extract the `datatype` and `format` values separated by a `/`. The extracted values are projected as separate fields.
**Original string**
```bash
application/charset=utf-8
```
**Query**
```kusto
['sample-http-logs']
| parse content_type with datatype '/' format
| project datatype, format
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20parse%20content_type%20with%20datatype%20'%2F'%20format%20%7C%20project%20datatype%2C%20format%22%7D)
**Output**
```json
{
"datatype": "application",
"format": "charset=utf-8"
}
```
### Parse user agent [#parse-user-agent]
This example parses the `user_agent` field to extract the operating system name (`os_name`) and version (`os_version`) enclosed within parentheses. The extracted values are projected as separate fields.
**Original string**
```bash
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
```
**Query**
```kusto
['sample-http-logs']
| parse user_agent with * '(' os_name ' ' os_version ';' * ')' *
| project os_name, os_version
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20parse%20user_agent%20with%20*%20'\('%20os_name%20'%20'%20os_version%20'%3B'%20*%20'\)'%20*%20%7C%20project%20os_name%2C%20os_version%22%7D)
**Output**
```json
{
"os_name": "Windows NT 10.0; Win64; x64",
"os_version": "10.0"
}
```
### Parse URI endpoint [#parse-uri-endpoint]
This example parses the `uri` field to extract the `endpoint` value that appears after `/api/v1/`. The extracted value is projected as a new field.
**Original string**
```bash
/api/v1/ping/user/textdata
```
**Query**
```kusto
['sample-http-logs']
| parse uri with '/api/v1/' endpoint
| project endpoint
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20parse%20uri%20with%20'%2Fapi%2Fv1%2F'%20endpoint%20%7C%20project%20endpoint%22%7D)
**Output**
```json
{
"endpoint": "ping/user/textdata"
}
```
### Parse ID into region, tenant, and user ID [#parse-id-into-region-tenant-and-user-id]
This example demonstrates how to parse the `id` field into three parts: `region`, `tenant`, and `userId`. The `id` field is structured with these parts separated by hyphens (`-`). The extracted parts are projected as separate fields.
**Original string**
```bash
usa-acmeinc-3iou24
```
**Query**
```kusto
['sample-http-logs']
| parse id with region '-' tenant '-' userId
| project region, tenant, userId
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20parse%20id%20with%20region%20'-'%20tenant%20'-'%20userId%20%7C%20project%20region%2C%20tenant%2C%20userId%22%7D)
**Output**
```json
{
"region": "usa",
"tenant": "acmeinc",
"userId": "3iou24"
}
```
### Parse in relaxed mode [#parse-in-relaxed-mode]
The parse operator supports a relaxed mode that allows for more flexible parsing. In relaxed mode, Axiom treats the parsing pattern as a regular string and matches results in a relaxed manner. If some parts of the pattern are missing or don’t match the expected type, Axiom assigns null values.
This example parses the `log` field into four separate parts (`method`, `url`, `status`, and `responseTime`) based on a structured format. The extracted parts are projected as separate fields.
**Original string**
```bash
GET /home 200 123ms
POST /login 500 nonValidResponseTime
PUT /api/data 201 456ms
DELETE /user/123 404 nonValidResponseTime
```
**Query**
```kusto
['HttpRequestLogs']
| parse kind=relaxed log with method " " url " " status:int " " responseTime
| project method, url, status, responseTime
```
**Output**
```json
[
{
"method": "GET",
"url": "/home",
"status": 200,
"responseTime": "123ms"
},
{
"method": "POST",
"url": "/login",
"status": 500,
"responseTime": null
},
{
"method": "PUT",
"url": "/api/data",
"status": 201,
"responseTime": "456ms"
},
{
"method": "DELETE",
"url": "/user/123",
"status": 404,
"responseTime": null
}
]
```
### Parse in regex mode [#parse-in-regex-mode]
The parse operator supports a regex mode that allows you to parse use regular expressions. In regex mode, Axiom treats the parsing pattern as a regular expression and matches results based on the specified regex pattern.
This example demonstrates how to parse Kubernetes pod log entries using regex mode to extract various fields such as `podName`, `namespace`, `phase`, `startTime`, `nodeName`, `hostIP`, and `podIP`. The parsing pattern is treated as a regular expression, and the extracted values are assigned to the respective fields.
**Original string**
```bash
Log: PodStatusUpdate (podName=nginx-pod, namespace=default, phase=Running, startTime=2023-05-14 08:30:00, nodeName=node-1, hostIP=192.168.1.1, podIP=10.1.1.1)
```
**Query**
```kusto
['PodLogs']
| parse kind=regex AppName with @"Log: PodStatusUpdate \(podName=" podName: string @", namespace=" namespace: string @", phase=" phase: string @", startTime=" startTime: datetime @", nodeName=" nodeName: string @", hostIP=" hostIP: string @", podIP=" podIP: string @"\)"
| project podName, namespace, phase, startTime, nodeName, hostIP, podIP
```
**Output**
```json
{
"podName": "nginx-pod",
"namespace": "default",
"phase": "Running",
"startTime": "2023-05-14 08:30:00",
"nodeName": "node-1",
"hostIP": "192.168.1.1",
"podIP": "10.1.1.1"
}
```
## Best practices [#best-practices]
When using the parse operator, consider the following best practices:
* Use appropriate parsing modes: Choose the parsing mode (simple, relaxed, regex) based on the complexity and variability of the data being parsed. Simple mode is suitable for fixed patterns, while relaxed and regex modes offer more flexibility.
* Handle missing or invalid data: Consider how to handle scenarios where the parsing pattern doesn’t match or the extracted values don’t conform to the expected types. Use the relaxed mode or provide default values to handle such cases.
* Project only necessary fields: After parsing, use the project operator to select only the fields that are relevant for further querying. This helps reduce the amount of data transferred and improves query performance.
* Use parse in combination with other operators: Combine parse with other APL operators like where, extend, and summarize to filter, transform, and aggregate the parsed data effectively.
By following these best practices and understanding the capabilities of the parse operator, you can effectively extract and transform data from string fields in APL, enabling powerful querying and insights.
## List of related operators [#list-of-related-operators]
* [extend](/apl/tabular-operators/extend-operator): Use the `extend` operator when you want to add calculated fields without parsing text.
* [project](/apl/tabular-operators/project-operator): Use `project` to select and rename fields after parsing text.
* [extract](/apl/scalar-functions/string-functions#extract): Use `extract` to retrieve the first substring matching a regular expression from a source string.
* [extract\_all](/apl/scalar-functions/string-functions#extract-all): Use `extract_all` to retrieve all substrings matching a regular expression from a source string.
## Other query languages [#other-query-languages]
In Splunk, the `rex` command is often used to extract fields from raw events or text. In APL, the `parse` operator performs a similar function. You define the text pattern to match and extract fields, allowing you to extract structured data from unstructured strings.
```splunk Splunk example
index=web_logs | rex field=_raw "duration=(?\d+)"
```
```kusto APL equivalent
['sample-http-logs']
| parse uri with * "duration=" req_duration_ms:int
```
In ANSI SQL, there isn’t a direct equivalent to the `parse` operator. Typically, you use string functions such as `SUBSTRING` or `REGEXP` to extract parts of a text field. However, APL’s `parse` operator simplifies this process by allowing you to define a text pattern and extract multiple fields in a single statement.
```sql SQL example
SELECT SUBSTRING(uri, CHARINDEX('duration=', uri) + 9, 3) AS req_duration_ms
FROM sample_http_logs;
```
```kusto APL equivalent
['sample-http-logs']
| parse uri with * "duration=" req_duration_ms:int
```
---
# parse-where
Source: https://axiom.co/docs/apl/tabular-operators/parse-where
The `parse-where` operator lets you extract values from a string expression based on a pattern and at the same time filter out rows that don’t match the pattern. This operator is useful when you want to ensure that your results contain only rows where the parsing succeeds, reducing the need for an additional filtering step.
You can use `parse-where` when working with logs or event data that follow a known structure but may contain noise or irrelevant lines. For example, you can parse request logs to extract structured information like HTTP method, status code, or error messages, and automatically discard any rows that don’t match the format.
## Usage [#usage]
### Syntax [#syntax]
```kusto
parse-where [kind=kind [flags=regexFlags]] expression with [*] stringConstant columnName [:columnType] [*]
```
### Parameters [#parameters]
| Parameter | Description |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `kind` | (Optional) Specifies the parsing method. The default is `simple`. You can also specify `regex` for regular expression parsing. |
| `flags` | (Optional) Regex flags to control the behavior of pattern matching. Used only with `kind=regex`. |
| `expression` | The string expression to parse. |
| `stringConstant` | The constant parts of the pattern that must match exactly. |
| `columnName` | The name of the column where the extracted value is stored. |
| `columnType` | (Optional) The type of the extracted value (for example, `string`, `int`, `real`). |
### Returns [#returns]
The operator returns a table with the original columns and the newly extracted columns. Rows that don't match the parsing pattern are removed.
## Use case example [#use-case-example]
You want to extract the HTTP method and status code from request logs while ignoring rows that don’t follow the expected format.
**Query**
```kusto
['http-logs']
| parse-where uri with '/' method:string '/' * 'status=' status:string
| project _time, method, status, uri
```
**Output**
| \_time | method | status | uri |
| -------------------- | ------ | ------ | --------------------------- |
| 2025-09-01T12:00:00Z | GET | 200 | /GET/api/items?status=200 |
| 2025-09-01T12:00:05Z | POST | 500 | /POST/api/orders?status=500 |
This query extracts the method and status from the `uri` field and discards rows where the `uri` doesn't match the pattern.
## List of related operators [#list-of-related-operators]
* [extend](/apl/tabular-operators/extend-operator): Adds calculated columns. Use when parsing isn't required but you want to create new derived columns.
* [parse](/apl/tabular-operators/parse-operator): Extracts values from a string expression without filtering out non-matching rows. Use when you want to keep all rows, including those that fail to parse.
* [project](/apl/tabular-operators/project-operator): Selects and computes columns without parsing. Use when you want to transform data rather than extract values.
* [where](/apl/tabular-operators/where-operator): Filters rows based on conditions. Use alongside parsing functions if you want more control over filtering logic.
## Other query languages [#other-query-languages]
In Splunk SPL, you use the `rex` command with the `max_match=1` option to extract fields and filter out non-matching events. In APL, `parse-where` provides the same functionality in a more direct way. Rows that do not match the pattern are automatically excluded.
```sql Splunk example
... | rex field=log_line "\[(?\w+)\] (?.+)"
```
```kusto APL equivalent
datatable(log_line:string)
[
'[INFO] Service started',
'invalid line'
]
| parse-where log_line with '[', level:string, '] ', message:string
```
ANSI SQL does not have a direct equivalent to `parse-where`. You often use `LIKE` or `REGEXP` functions to test string patterns and then combine them with `CASE` expressions to extract substrings. In APL, `parse-where` simplifies this by combining extraction and filtering into one operator.
```sql SQL example
SELECT
REGEXP_SUBSTR(log_line, '\\[(\\w+)\\]', 1, 1) AS level,
REGEXP_SUBSTR(log_line, '\\] (.+)', 1, 1) AS message
FROM logs
WHERE log_line REGEXP '\\[(\\w+)\\] (.+)';
```
```kusto APL equivalent
datatable(log_line:string)
[
'[INFO] Service started',
'invalid line'
]
| parse-where log_line with '[', level:string, '] ', message:string
```
---
# project-away
Source: https://axiom.co/docs/apl/tabular-operators/project-away-operator
The `project-away` operator in APL is used to exclude specific fields from the output of a query. This operator is useful when you want to return a subset of fields from a dataset, without needing to manually specify every field you want to keep. Instead, you specify the fields you want to remove, and the operator returns all remaining fields.
You can use `project-away` in scenarios where your dataset contains irrelevant or sensitive fields that you don’t want in the results. It simplifies queries, especially when dealing with wide datasets, by allowing you to filter out fields without having to explicitly list every field to include.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| project-away FieldName1, FieldName2, ...
```
### Parameters [#parameters]
* `FieldName`: The field you want to exclude from the result set.
### Returns [#returns]
The `project-away` operator returns the input dataset excluding the specified fields. The result contains the same number of rows as the input table.
## Use case examples [#use-case-examples]
In log analysis, you might want to exclude unnecessary fields to focus on the relevant fields, such as timestamp, request duration, and user information.
**Query**
```kusto
['sample-http-logs']
| project-away status, uri, method
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20project-away%20status%2C%20uri%2C%20method%22%7D)
**Output**
| \_time | req\_duration\_ms | id | geo.city | geo.country |
| ------------------- | ----------------- | -- | -------- | ----------- |
| 2023-10-17 10:23:00 | 120 | u1 | Seattle | USA |
| 2023-10-17 10:24:00 | 135 | u2 | Berlin | Germany |
The query removes the `status`, `uri`, and `method` fields from the output, keeping the focus on the key fields.
When analyzing OpenTelemetry traces, you can remove fields that aren't necessary for specific trace evaluations, such as span IDs and statuses.
**Query**
```kusto
['otel-demo-traces']
| project-away span_id, status_code
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20project-away%20span_id%2C%20status_code%22%7D)
**Output**
| \_time | duration | trace\_id | service.name | kind |
| ------------------- | -------- | --------- | --------------- | ------ |
| 2023-10-17 11:01:00 | 00:00:03 | t1 | frontend | server |
| 2023-10-17 11:02:00 | 00:00:02 | t2 | checkoutservice | client |
The query removes the `span_id` and `status_code` fields, focusing on key service information.
In security log analysis, excluding unnecessary fields such as the HTTP method or URI can help focus on user behavior patterns and request durations.
**Query**
```kusto
['sample-http-logs']
| project-away method, uri
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20project-away%20method%2C%20uri%22%7D)
**Output**
| \_time | req\_duration\_ms | id | status | geo.city | geo.country |
| ------------------- | ----------------- | -- | ------ | -------- | ----------- |
| 2023-10-17 10:25:00 | 95 | u3 | 200 | London | UK |
| 2023-10-17 10:26:00 | 180 | u4 | 404 | Paris | France |
The query excludes the `method` and `uri` fields, keeping information like status and geographical details.
## Wildcard [#wildcard]
Wildcard refers to a special character or a set of characters that can be used to substitute for any other character in a search pattern. Use wildcards to create more flexible queries and perform more powerful searches.
The syntax for wildcard can either be `data*` or `['data.fo']*`.
Here’s how you can use wildcards in `project-away`:
```kusto
['sample-http-logs']
| project-away status*, user*, is*, ['geo.']*
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20project-away%20status%2A%2C%20user%2A%2C%20is%2A%2C%20%20%5B%27geo.%27%5D%2A%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
```kusto
['github-push-event']
| project-away push*, repo*, ['commits']*
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27github-push-event%27%5D%5Cn%7C%20project-away%20push%2A%2C%20repo%2A%2C%20%5B%27commits%27%5D%2A%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
## List of related operators [#list-of-related-operators]
* [project](/apl/tabular-operators/project-operator): The `project` operator lets you select specific fields to include, rather than excluding them.
* [extend](/apl/tabular-operators/extend-operator): The `extend` operator is used to add new fields, whereas `project-away` is for removing fields.
* [summarize](/apl/tabular-operators/summarize-operator): While `project-away` removes fields, `summarize` is useful for aggregating data across multiple fields.
## Other query languages [#other-query-languages]
In Splunk SPL, you use the `fields` command to remove fields from your results. In APL, the `project-away` operator provides a similar functionality, removing specified fields while returning the remaining ones.
```splunk Splunk example
... | fields - status, uri, method
```
```kusto APL equivalent
['sample-http-logs']
| project-away status, uri, method
```
In SQL, you typically use the `SELECT` statement to explicitly include fields. In contrast, APL’s `project-away` operator allows you to exclude fields, offering a more concise approach when you want to keep many fields but remove a few.
```sql SQL example
SELECT _time, req_duration_ms, id, geo.city, geo.country
FROM sample_http_logs;
```
```kusto APL equivalent
['sample-http-logs']
| project-away status, uri, method
```
---
# project-keep
Source: https://axiom.co/docs/apl/tabular-operators/project-keep-operator
The `project-keep` operator in APL is a powerful tool for field selection. It allows you to explicitly keep specific fields from a dataset, discarding any others not listed in the operator’s parameters. This is useful when you only need to work with a subset of fields in your query results and want to reduce clutter or improve performance by eliminating unnecessary fields.
You can use `project-keep` when you need to focus on particular data points, such as in log analysis, security event monitoring, or extracting key fields from traces.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| project-keep FieldName1, FieldName2, ...
```
### Parameters [#parameters]
* `FieldName`: The field you want to keep in the result set.
### Returns [#returns]
`project-keep` returns a dataset with only the specified fields. All other fields are removed from the output. The result contains the same number of rows as the input table.
## Use case examples [#use-case-examples]
For log analysis, you might want to keep only the fields that are relevant to investigating HTTP requests.
**Query**
```kusto
['sample-http-logs']
| project-keep _time, status, uri, method, req_duration_ms
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20project-keep%20_time%2C%20status%2C%20uri%2C%20method%2C%20req_duration_ms%22%7D)
**Output**
| \_time | status | uri | method | req\_duration\_ms |
| ------------------- | ------ | ------------------ | ------ | ----------------- |
| 2024-10-17 10:00:00 | 200 | /index.html | GET | 120 |
| 2024-10-17 10:01:00 | 404 | /non-existent.html | GET | 50 |
| 2024-10-17 10:02:00 | 500 | /server-error | POST | 300 |
This query filters the dataset to show only the request timestamp, status, URI, method, and duration, which can help you analyze server performance or errors.
For OpenTelemetry trace analysis, you may want to focus on key tracing details such as service names and trace IDs.
**Query**
```kusto
['otel-demo-traces']
| project-keep _time, trace_id, span_id, ['service.name'], duration
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20project-keep%20_time%2C%20trace_id%2C%20span_id%2C%20%5B%27service.name%27%5D%2C%20duration%22%7D)
**Output**
| \_time | trace\_id | span\_id | service.name | duration |
| ------------------- | --------- | -------- | --------------- | -------- |
| 2024-10-17 10:03:00 | abc123 | xyz789 | frontend | 500ms |
| 2024-10-17 10:04:00 | def456 | mno345 | checkoutservice | 250ms |
This query extracts specific tracing information, such as trace and span IDs, the name of the service, and the span’s duration.
In security log analysis, focusing on essential fields like user ID and HTTP status can help track suspicious activity.
**Query**
```kusto
['sample-http-logs']
| project-keep _time, id, status, uri, ['geo.city'], ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20project-keep%20_time%2C%20id%2C%20status%2C%20uri%2C%20%5B%27geo.city%27%5D%2C%20%5B%27geo.country%27%5D%22%7D)
**Output**
| \_time | id | status | uri | geo.city | geo.country |
| ------------------- | ------- | ------ | ------ | ------------- | ----------- |
| 2024-10-17 10:05:00 | user123 | 403 | /admin | New York | USA |
| 2024-10-17 10:06:00 | user456 | 200 | /login | San Francisco | USA |
This query narrows down the data to track HTTP status codes by users, helping identify potential unauthorized access attempts.
## List of related operators [#list-of-related-operators]
* [project](/apl/tabular-operators/project-operator): Use `project` to explicitly specify the fields you want in your result, while also allowing transformations or calculations on those fields.
* [extend](/apl/tabular-operators/extend-operator): Use `extend` to add new fields or modify existing ones without dropping any fields.
* [summarize](/apl/tabular-operators/summarize-operator): Use `summarize` when you need to perform aggregation operations on your dataset, grouping data as necessary.
## Wildcard [#wildcard]
Wildcard refers to a special character or a set of characters that can be used to substitute for any other character in a search pattern. Use wildcards to create more flexible queries and perform more powerful searches.
The syntax for wildcard can either be `data*` or `['data.fo']*`.
Here’s how you can use wildcards in `project-keep`:
```kusto
['sample-http-logs']
| project-keep resp*, content*, ['geo.']*
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20project-keep%20resp%2A%2C%20content%2A%2C%20%20%5B%27geo.%27%5D%2A%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
```kusto
['github-push-event']
| project-keep size*, repo*, ['commits']*, id*
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27github-push-event%27%5D%5Cn%7C%20project-keep%20size%2A%2C%20repo%2A%2C%20%5B%27commits%27%5D%2A%2C%20id%2A%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
## Other query languages [#other-query-languages]
In Splunk SPL, the `table` command performs a similar task to APL’s `project-keep`. It selects only the fields you specify and excludes any others.
```splunk Splunk example
index=main | table _time, status, uri
```
```kusto APL equivalent
['sample-http-logs']
| project-keep _time, status, uri
```
In ANSI SQL, the `SELECT` statement combined with field names performs a task similar to `project-keep` in APL. Both allow you to specify which fields to retrieve from the dataset.
```sql SQL example
SELECT _time, status, uri FROM sample_http_logs
```
```kusto APL equivalent
['sample-http-logs']
| project-keep _time, status, uri
```
---
# project
Source: https://axiom.co/docs/apl/tabular-operators/project-operator
# project operator [#project-operator]
The `project` operator in Axiom Processing Language (APL) is used to select specific fields from a dataset, potentially renaming them or applying calculations on the fly. With `project`, you can control which fields are returned by the query, allowing you to focus on only the data you need.
This operator is useful when you want to refine your query results by reducing the number of fields, renaming them, or deriving new fields based on existing data. It’s a powerful tool for filtering out unnecessary fields and performing light transformations on your dataset.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| project FieldName [= Expression] [, ...]
```
Or
```kusto
| project FieldName, FieldName, FieldName, ...
```
Or
```kusto
| project [FieldName, FieldName[,] = Expression [, ...]
```
### Parameters [#parameters]
* `FieldName`: The names of the fields in the order you want them to appear in the result set. If there is no Expression, then FieldName is compulsory and a field of that name must appear in the input.
* `Expression`: Optional scalar expression referencing the input fields.
### Returns [#returns]
The `project` operator returns a dataset containing only the specified fields.
## Use case examples [#use-case-examples]
In this example, you’ll extract the timestamp, HTTP status code, and request URI from the sample HTTP logs.
**Query**
```kusto
['sample-http-logs']
| project _time, status, uri
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20project%20_time%2C%20status%2C%20uri%22%7D)
**Output**
| \_time | status | uri |
| ------------------- | ------ | --------------- |
| 2024-10-17 12:00:00 | 200 | /api/v1/getData |
| 2024-10-17 12:01:00 | 404 | /api/v1/getUser |
The query returns only the timestamp, HTTP status code, and request URI, reducing unnecessary fields from the dataset.
In this example, you’ll extract trace information such as the service name, span ID, and duration from OpenTelemetry traces.
**Query**
```kusto
['otel-demo-traces']
| project ['service.name'], span_id, duration
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20project%20%5B'service.name'%5D%2C%20span_id%2C%20duration%22%7D)
**Output**
| service.name | span\_id | duration |
| ------------ | ------------- | -------- |
| frontend | span-1234abcd | 00:00:02 |
| cartservice | span-5678efgh | 00:00:05 |
The query isolates relevant tracing data, such as the service name, span ID, and duration of spans.
In this example, you’ll focus on security log entries by projecting only the timestamp, user ID, and HTTP status from the sample HTTP logs.
**Query**
```kusto
['sample-http-logs']
| project _time, id, status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20project%20_time%2C%20id%2C%20status%22%7D)
**Output**
| \_time | id | status |
| ------------------- | ----- | ------ |
| 2024-10-17 12:00:00 | user1 | 200 |
| 2024-10-17 12:01:00 | user2 | 403 |
The query extracts only the timestamp, user ID, and HTTP status for analysis of access control in security logs.
## List of related operators [#list-of-related-operators]
* [extend](/apl/tabular-operators/extend-operator): Use `extend` to add new fields or calculate values without removing any existing fields.
* [summarize](/apl/tabular-operators/summarize-operator): Use `summarize` to aggregate data across groups of rows, which is useful when you’re calculating totals or averages.
* [where](/apl/tabular-operators/where-operator): Use `where` to filter rows based on conditions, often paired with `project` to refine your dataset further.
## Other query languages [#other-query-languages]
In Splunk SPL, the equivalent of the `project` operator is typically the `table` or `fields` command. While SPL’s `table` focuses on selecting fields, `fields` controls both selection and exclusion, similar to `project` in APL.
```sql Splunk example
| table _time, status, uri
```
```kusto APL equivalent
['sample-http-logs']
| project _time, status, uri
```
In ANSI SQL, the `SELECT` statement serves a similar role to the `project` operator in APL. SQL users will recognize that `project` behaves like selecting fields from a table, with the ability to rename or transform fields inline.
```sql SQL example
SELECT _time, status, uri FROM sample_http_logs;
```
```kusto APL equivalent
['sample-http-logs']
| project _time, status, uri
```
---
# project-rename
Source: https://axiom.co/docs/apl/tabular-operators/project-rename
The `project-rename` operator in APL lets you rename columns in a dataset while keeping all existing rows intact. You can use it when you want to make column names clearer, align them with naming conventions, or prepare data for downstream processing. Unlike `project`, which also controls which columns appear in the result, `project-rename` only changes the names of selected columns and keeps the full set of columns in the dataset.
You find this operator useful when:
* You want to standardize field names across multiple queries.
* You want to replace long or inconsistent column names with simpler ones.
* You want to improve query readability without altering the underlying data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
Table
| project-rename NewName1 = OldName1, NewName2 = OldName2, ...
```
### Parameters [#parameters]
| Name | Type | Description |
| --------- | ------ | --------------------------------------- |
| `NewName` | string | The new column name you want to assign. |
| `OldName` | string | The existing column name to rename. |
### Returns [#returns]
A dataset with the same rows and columns as the input, except that the specified columns have new names.
## Use case examples [#use-case-examples]
When analyzing HTTP logs, you might want to rename fields to shorter or more descriptive names before creating dashboards or reports.
**Query**
```kusto
['sample-http-logs']
| project-rename city = ['geo.city'], country = ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20project-rename%20city%20%3D%20%5B'geo.city'%5D%2C%20country%20%3D%20%5B'geo.country'%5D%22%7D)
**Output**
| \_time | req\_duration\_ms | id | status | uri | method | city | country |
| -------------------- | ----------------- | ----- | ------ | ------ | ------ | ------ | ------- |
| 2025-09-01T10:00:00Z | 120 | user1 | 200 | /home | GET | Paris | FR |
| 2025-09-01T10:01:00Z | 85 | user2 | 404 | /about | GET | Berlin | DE |
This query renames the `geo.city` and `geo.country` fields to `city` and `country` for easier use in queries.
When inspecting distributed traces, you can rename service-related fields to match your internal naming conventions.
**Query**
```kusto
['otel-demo-traces']
| project-rename service = ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20project-rename%20service%20%3D%20%5B'service.name'%5D%22%7D)
**Output**
| \_time | duration | span\_id | trace\_id | service | kind |
| -------------------- | ------------ | -------- | --------- | -------- | ------ |
| 2025-09-01T09:55:00Z | 00:00:01.200 | abc123 | trace789 | frontend | server |
| 2025-09-01T09:56:00Z | 00:00:00.450 | def456 | trace790 | checkout | client |
This query renames `service.name` to `service`, making it shorter for downstream filtering.
For security-related HTTP log analysis, you can rename status and URI fields to match existing security dashboards.
**Query**
```kusto
['sample-http-logs']
| project-rename http_status = status, url = uri
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20project-rename%20http_status%20%3D%20status%2C%20url%20%3D%20uri%22%7D)
**Output**
| \_time | req\_duration\_ms | id | http\_status | url | method | geo.city | geo.country |
| -------------------- | ----------------- | ----- | ------------ | ------ | ------ | -------- | ----------- |
| 2025-09-01T11:00:00Z | 150 | user5 | 403 | /admin | POST | Madrid | ES |
| 2025-09-01T11:02:00Z | 200 | user6 | 500 | /login | POST | Rome | IT |
This query renames `status` to `http_status` and `uri` to `url`, making the output align with security alerting systems.
## List of related operators [#list-of-related-operators]
* [extend](/apl/tabular-operators/extend-operator): Creates new calculated columns. Use it when you want to add columns rather than rename existing ones.
* [project](/apl/tabular-operators/project-operator): Lets you select and rename columns at the same time. Use it when you want to control which columns appear in the result.
* [project-away](/apl/tabular-operators/project-away-operator): Removes specific columns from the dataset. Use it when you want to drop columns rather than rename them.
* [summarize](/apl/tabular-operators/summarize-operator): Aggregates data into groups. Use it when you want to compute metrics rather than adjust column names.
## Other query languages [#other-query-languages]
In Splunk SPL, renaming fields uses the `rename` command. The `project-rename` operator in APL works in a similar way. Both let you map existing fields to new names without altering the dataset content.
```sql Splunk example
... | rename uri AS url, status AS http_status
```
```kusto APL equivalent
['sample-http-logs']
| project-rename url = uri, http_status = status
```
In ANSI SQL, renaming columns is done with `AS` in a `SELECT` statement. In APL, `project-rename` is the closest equivalent, but unlike SQL, it preserves all columns by default while renaming only the specified ones.
```sql SQL example
SELECT uri AS url, status AS http_status, method, id
FROM sample_http_logs;
```
```kusto APL equivalent
['sample-http-logs']
| project-rename url = uri, http_status = status
```
---
# project-reorder
Source: https://axiom.co/docs/apl/tabular-operators/project-reorder-operator
The `project-reorder` operator in APL allows you to rearrange the fields of a dataset without modifying the underlying data. This operator is useful when you need to control the display order of fields in query results, making your data easier to read and analyze. It can be especially helpful when working with large datasets where field ordering impacts the clarity of the output.
Use `project-reorder` when you want to emphasize specific fields by adjusting their order in the result set without changing their values or structure.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| project-reorder Field1 [asc | desc | granny-asc | granny-desc], Field2 [asc | desc | granny-asc | granny-desc], ...
```
### Parameters [#parameters]
* `Field1, Field2, ...`: The names of the fields in the order you want them to appear in the result set.
* `[asc | desc | granny-asc | granny-desc]`: Optional: Specifies the sort order for the reordered fields. `asc` or `desc` order fields by field name in ascending or descending manner. `granny-asc` or `granny-desc` order by ascending or descending while secondarily sorting by the next numeric value. For example, `b50` comes before `b9` when you use `granny-asc`.
### Returns [#returns]
A table with the specified fields reordered as requested followed by any unspecified fields in their original order. `project-reorder` doesn‘t rename or remove fields from the dataset. All fields that existed in the dataset appear in the results table.
## Use case examples [#use-case-examples]
In this example, you reorder HTTP log fields to prioritize the most relevant ones for log analysis.
**Query**
```kusto
['sample-http-logs']
| project-reorder _time, method, status, uri, req_duration_ms, ['geo.city'], ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20project-reorder%20_time%2C%20method%2C%20status%2C%20uri%2C%20req_duration_ms%2C%20%5B%27geo.city%27%5D%2C%20%5B%27geo.country%27%5D%22%7D)
**Output**
| \_time | method | status | uri | req\_duration\_ms | geo.city | geo.country |
| ------------------- | ------ | ------ | ---------------- | ----------------- | -------- | ----------- |
| 2024-10-17 12:34:56 | GET | 200 | /home | 120 | New York | USA |
| 2024-10-17 12:35:01 | POST | 404 | /api/v1/resource | 250 | Berlin | Germany |
This query rearranges the fields for clarity, placing the most crucial fields (`_time`, `method`, `status`) at the front for easier analysis.
Here’s an example where OpenTelemetry trace fields are reordered to prioritize service and status information.
**Query**
```kusto
['otel-demo-traces']
| project-reorder _time, ['service.name'], kind, status_code, trace_id, span_id, duration
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20project-reorder%20_time%2C%20%5B%27service.name%27%5D%2C%20kind%2C%20status_code%2C%20trace_id%2C%20span_id%2C%20duration%22%7D)
**Output**
| \_time | service.name | kind | status\_code | trace\_id | span\_id | duration |
| ------------------- | --------------------- | ------ | ------------ | --------- | -------- | -------- |
| 2024-10-17 12:34:56 | frontend | client | 200 | abc123 | span456 | 00:00:01 |
| 2024-10-17 12:35:01 | productcatalogservice | server | 500 | xyz789 | span012 | 00:00:05 |
This query emphasizes service-related fields like `service.name` and `status_code` at the start of the output.
In this example, fields in a security log are reordered to prioritize key fields for investigating HTTP request anomalies.
**Query**
```kusto
['sample-http-logs']
| project-reorder _time, status, method, uri, id, ['geo.city'], ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20project-reorder%20_time%2C%20status%2C%20method%2C%20uri%2C%20id%2C%20%5B%27geo.city%27%5D%2C%20%5B%27geo.country%27%5D%22%7D)
**Output**
| \_time | status | method | uri | id | geo.city | geo.country |
| ------------------- | ------ | ------ | ---------------- | ------ | -------- | ----------- |
| 2024-10-17 12:34:56 | 200 | GET | /home | user01 | New York | USA |
| 2024-10-17 12:35:01 | 404 | POST | /api/v1/resource | user02 | Berlin | Germany |
This query reorders the fields to focus on the HTTP status, request method, and URI, which are critical for security-related analyses.
## Wildcard [#wildcard]
Wildcard refers to a special character or a set of characters that can be used to substitute for any other character in a search pattern. Use wildcards to create more flexible queries and perform more powerful searches.
The syntax for wildcard can either be `data*` or `['data.fo']*`.
Here’s how you can use wildcards in `project-reorder`:
Reorder all fields in ascending order:
```kusto
['sample-http-logs']
| project-reorder * asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20project-reorder%20%2A%20asc%22%7D)
Reorder specific fields to the beginning:
```kusto
['sample-http-logs']
| project-reorder method, status, uri
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20project-reorder%20method%2C%20status%2C%20uri%22%7D)
Reorder fields using wildcards and sort in descending order:
```kusto
['github-push-event']
| project-reorder repo*, num_commits, push_id, ref, size, ['id'], size_large desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27github-push-event%27%5D%5Cn%7C%20project-reorder%20repo%2A%2C%20num_commits%2C%20push_id%2C%20ref%2C%20size%2C%20%5B%27id%27%5D%2C%20size_large%20desc%22%7D)
Reorder specific fields and keep others in original order:
```kusto
['otel-demo-traces']
| project-reorder trace_id, *, span_id // orders the trace_id then everything else, then span_id fields
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27otel-demo-traces%27%5D%5Cn%7C%20project-reorder%20trace_id%2C%20%2A%2C%20span_id%22%7D)
## List of related operators [#list-of-related-operators]
* [project](/apl/tabular-operators/project-operator): Use the `project` operator to select and rename fields without changing their order.
* [extend](/apl/tabular-operators/extend-operator): `extend` adds new calculated fields while keeping the original ones in place.
* [summarize](/apl/tabular-operators/summarize-operator): Use `summarize` to perform aggregations on fields, which can then be reordered using `project-reorder`.
* [sort](/apl/tabular-operators/sort-operator): Sorts rows based on field values, and the results can then be reordered with `project-reorder`.
## Other query languages [#other-query-languages]
In Splunk SPL, you use the `table` command to reorder fields, which works similarly to how `project-reorder` functions in APL.
```splunk Splunk example
| table FieldA, FieldB, FieldC
```
```kusto APL equivalent
['dataset.name']
| project-reorder FieldA, FieldB, FieldC
```
In ANSI SQL, the order of fields in a `SELECT` statement determines their arrangement in the output. In APL, `project-reorder` provides more explicit control over the field order without requiring a full `SELECT` clause.
```sql SQL example
SELECT FieldA, FieldB, FieldC FROM dataset;
```
```kusto APL equivalent
| project-reorder FieldA, FieldB, FieldC
```
---
# redact
Source: https://axiom.co/docs/apl/tabular-operators/redact-operator
The `redact` operator in APL replaces sensitive or unwanted data in string fields using regular expressions. You can use it to sanitize log data, obfuscate personal information, or anonymize text for auditing or analysis. The operator allows you to define one or multiple regular expressions to identify and replace matching patterns. You can customize the replacement token, generate hashes of redacted values, or retain structural elements while obfuscating specific segments of data.
This operator is useful when you need to ensure data privacy or compliance with regulations such as GDPR or HIPAA. For example, you can redact credit card numbers, email addresses, or personally identifiable information from logs and datasets.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| redact [replaceToken="*"] [replaceHash=false] [redactGroups=false] , () [on Field]
```
### Parameters [#parameters]
| Parameter | Type | Description |
| -------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `replaceToken` | string | The string with which to replace matches. If you specify a single character, Axiom replaces each character in the matching text with `replaceToken`. If you specify more than one character, Axiom replaces the whole of the matching text with `replaceToken`. The default `replaceToken` is the `*` character. |
| `replaceHash` | bool | Specifies whether to replace matches with a hash of the data. You can’t use both `replaceToken` and `replaceHash` in the same query. |
| `redactGroups` | bool | Specifies whether to look for capturing groups in the regex and only redact characters in the capturing groups. Use this option for partial replacements or replacements that maintain the structure of the data. The default is false. |
| `regex` | regex | A single regex or an array/map of regexes to match against field values. |
| `on Field` | | Limits redaction to specific fields. If you omit this parameter, Axiom redacts all string fields in the dataset. |
### Returns [#returns]
Returns the input dataset with sensitive data replaced or hashed.
## Sample regular expressions [#sample-regular-expressions]
| Operation | Sample regex | Original string | Redacted string |
| ------------------------------ | ---------------------------------------------------------------------------------- | --------------------------------------------------- | ------------------------------------------------ |
| Redact email addresses | \[a-zA-Z0-9\_.+-]+@\[a-zA-Z0-9-]+.\[a-zA-Z0-9-.]+ | Incoming Mail - [abc@test.com](mailto:abc@test.com) | Incoming Mail - \*\*\*\*\*\*\*\*\*\*\*\* |
| Redact social security numbers | \d{3}-\d{2}-\d{4} | SSN 123-12-1234.pdf | SSN \*\*\*\*\*\*\*\*\*\*\*.pdf |
| Redact IBAN | \[A-Z]{2}\[0-9]{2}(?:\[ ]?\[0-9]{4}){4}(?!(?:\[ ]?\[0-9]){3})(?:\[ ]?\[0-9]{1,2})? | AB12 1234 1234 1234 1234 | \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* |
## Use case examples [#use-case-examples]
Use the `redact` operator to sanitize HTTP logs by obfuscating geographical data.
**Query**
```kusto
['sample-http-logs']
| redact replaceToken="x" @'.*' on ['geo.city'], ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20redact%20replaceToken%3D'x'%20%40'.*'%20on%20%5B'geo.city'%5D%2C%20%5B'geo.country'%5D%22%7D)
**Output**
| \_time | geo.city | geo.country |
| ------------------- | -------- | ------------ |
| 2025-01-01 12:00:00 | `xxx` | `xxxxxxxx` |
| 2025-01-01 12:05:00 | `xxxxxx` | `xxxxxxxxxx` |
The query replaces all characters matching the pattern `.*` with the character `x` in the `geo.city` and `geo.country` fields.
In OpenTelemetry traces, use `redact` to anonymize Kubernetes node names with their hashes while preserving the service structure.
**Query**
```kusto
['otel-demo-traces']
| redact replaceHash=true @'.*' on ['resource.k8s.node.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20redact%20replaceHash%3Dtrue%20%40'.*'%20on%20%5B'resource.k8s.node.name'%5D%22%7D)
**Output**
| \_time | resource.k8s.node.name | service.name |
| ------------------- | ---------------------- | ----------------- |
| 2025-01-01 12:00:00 | `QQXRv6VU` | `frontend` |
| 2025-01-01 12:05:00 | `Q1urOteW` | `checkoutservice` |
The query replaces Kubernetes node names with hashed values while keeping the rest of the trace intact.
Use the `redact` operator to remove parts of a URL from security logs.
**Query**
```kusto
['sample-http-logs']
| redact replaceToken="" redactGroups=true @'.*/(.*)' on uri
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20redact%20replaceToken%3D'%3CREDACTED%3E'%20redactGroups%3Dtrue%20%40'.*%2F\(.*\)'%20on%20uri%22%7D)
**Output**
| \_time | uri |
| ------------------- | ----------------------------- |
| 2025-01-01 12:00:00 | `/api/v1/pub/sub/` |
| 2025-01-01 12:05:00 | `/api/v1/textdata/` |
| 2025-01-01 12:10:00 | `/api/v1/payment/` |
The query performs a partial redaction in the capturing groups of the regex. It replaces the slug of the URL (the part after the last `/`) with the text ``.
## List of related operators [#list-of-related-operators]
* [project](/apl/tabular-operators/project-operator): Select specific fields from the dataset. Useful for focused analysis.
* [summarize](/apl/tabular-operators/summarize-operator): Aggregate data. Helpful when combining redacted data with statistical analysis.
* [parse](/apl/tabular-operators/parse-operator): Extract and parse structured data using regex patterns.
When you need custom replacement patterns, use the [replace\_regex](/apl/scalar-functions/string-functions#replace-regex) function for precise control over string replacements. `redact` provides a simpler, security-focused interface. Use `redact` if you’re primarily focused on data privacy and compliance, and `replace_regex` if you need more control over the replacement text format.
## Other query languages [#other-query-languages]
In Splunk SPL, data sanitization is often achieved using custom regex-based transformations or eval functions. The `redact` operator in APL simplifies this process by directly applying regular expressions and offering options for replacement or hashing.
```sql Splunk example
| eval sanitized_field=replace(field, "regex_pattern", "*")
```
```kusto APL equivalent
| redact 'regex_pattern' on field
```
ANSI SQL typically requires a combination of functions like `REPLACE` or `REGEXP_REPLACE` for data obfuscation. APL’s `redact` operator consolidates these capabilities into a single, flexible command.
```sql SQL example
SELECT REGEXP_REPLACE(field, 'regex_pattern', '*') AS sanitized_field FROM table;
```
```kusto APL equivalent
| redact 'regex_pattern' on field
```
---
# sample
Source: https://axiom.co/docs/apl/tabular-operators/sample-operator
The `sample` operator in APL psuedo-randomly selects rows from the input dataset at a rate specified by a parameter. This operator is useful when you want to analyze a subset of data, reduce the dataset size for testing, or quickly explore patterns without processing the entire dataset. The sampling algorithm isn’t statistically rigorous but provides a way to explore and understand a dataset. For statistically rigorous analysis, use `summarize` instead.
You can find the `sample` operator useful when working with large datasets, where processing the entire dataset is resource-intensive or unnecessary. It’s ideal for scenarios like log analysis, performance monitoring, or sampling for data quality checks.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| sample ProportionOfRows
```
### Parameters [#parameters]
* `ProportionOfRows`: A float greater than 0 and less than 1 which specifies the proportion of rows to return from the dataset. The rows are selected randomly.
### Returns [#returns]
The operator returns a table containing the specified number of rows, selected randomly from the input dataset.
## Use case examples [#use-case-examples]
In this use case, you sample a small number of rows from your HTTP logs to quickly analyze trends without working through the entire dataset.
**Query**
```kusto
['sample-http-logs']
| sample 0.05
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20sample%200.05%22%7D)
**Output**
| \_time | req\_duration\_ms | id | status | uri | method | geo.city | geo.country |
| ------------------- | ----------------- | ----- | ------ | --------- | ------ | -------- | ----------- |
| 2023-10-16 12:45:00 | 234 | user1 | 200 | /index | GET | New York | US |
| 2023-10-16 12:47:00 | 120 | user2 | 404 | /login | POST | Paris | FR |
| 2023-10-16 12:48:00 | 543 | user3 | 500 | /checkout | POST | Tokyo | JP |
This query returns a random subset of 5 % of all rows from the HTTP logs, helping you quickly identify any potential issues or patterns without analyzing the entire dataset.
In this use case, you sample traces to investigate performance metrics for a particular service across different spans.
**Query**
```kusto
['otel-demo-traces']
| where ['service.name'] == 'checkoutservice'
| sample 0.05
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20where%20%5B%27service.name%27%5D%20%3D%3D%20%27checkoutservice%27%20%7C%20sample%200.05%22%7D)
**Output**
| \_time | duration | span\_id | trace\_id | service.name | kind | status\_code |
| ------------------- | -------- | -------- | --------- | --------------- | ------ | ------------ |
| 2023-10-16 14:05:00 | 1.34s | span5678 | trace123 | checkoutservice | client | 200 |
| 2023-10-16 14:06:00 | 0.89s | span3456 | trace456 | checkoutservice | server | 500 |
This query returns 5 % of all traces for the `checkoutservice` to identify potential performance bottlenecks.
In this use case, you sample security log data to spot irregular activity in requests, such as 500-level HTTP responses.
**Query**
```kusto
['sample-http-logs']
| where status == '500'
| sample 0.03
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20where%20status%20%3D%3D%20%27500%27%20%7C%20sample%200.03%22%7D)
**Output**
| \_time | req\_duration\_ms | id | status | uri | method | geo.city | geo.country |
| ------------------- | ----------------- | ----- | ------ | -------- | ------ | -------- | ----------- |
| 2023-10-16 14:30:00 | 543 | user4 | 500 | /payment | POST | Berlin | DE |
| 2023-10-16 14:32:00 | 876 | user5 | 500 | /order | POST | London | GB |
This query helps you quickly spot failed requests (HTTP 500 responses) and investigate any potential causes of these errors.
## List of related operators [#list-of-related-operators]
* [take](/apl/tabular-operators/take-operator): Use `take` when you want to return the first N rows in the dataset rather than a random subset.
* [where](/apl/tabular-operators/where-operator): Use `where` to filter rows based on conditions rather than sampling randomly.
* [top](/apl/tabular-operators/top-operator): Use `top` to return the highest N rows based on a sorting criterion.
## Other query languages [#other-query-languages]
In Splunk SPL, the `sample` command works similarly, returning a subset of data rows randomly. However, the APL `sample` operator requires a simpler syntax without additional arguments for biasing the randomness.
```sql Splunk example
| sample 10
```
```kusto APL equivalent
['sample-http-logs']
| sample 0.1
```
In ANSI SQL, there is no direct equivalent to the `sample` operator, but you can achieve similar results using the `TABLESAMPLE` clause. In APL, `sample` operates independently and is more flexible, as it’s not tied to a table scan.
```sql SQL example
SELECT * FROM table TABLESAMPLE (10 ROWS);
```
```kusto APL equivalent
['sample-http-logs']
| sample 0.1
```
---
# search
Source: https://axiom.co/docs/apl/tabular-operators/search-operator
The `search` operator in APL is used to perform a full-text search across multiple fields in a dataset. This operator allows you to locate specific keywords, phrases, or patterns, helping you filter data quickly and efficiently. You can use `search` to query logs, traces, and other data sources without the need to specify individual fields, making it particularly useful when you’re unsure where the relevant data resides.
Use `search` when you want to search multiple fields in a dataset, especially for ad-hoc analysis or quick lookups across logs or traces. It’s commonly applied in log analysis, security monitoring, and trace analysis, where multiple fields may contain the desired data.
## Importance of the search operator [#importance-of-the-search-operator]
* **Versatility:** It allows you to find a specific text or term across various fields within a dataset that they choose or select for their search, without the necessity to specify each field.
* **Efficiency:** Saves time when you aren’t sure which field or datasets in APL might contain the information you are looking for.
* **User-friendliness:** It’s particularly useful for users or developers unfamiliar with the schema details of a given database.
## Usage [#usage]
### Syntax [#syntax]
```kusto
search [kind=CaseSensitivity] SearchPredicate
```
or
```kusto
search [kind=CaseSensitivity] [in (DatasetPattern)] SearchPredicate
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **CaseSensitivity** | string | | A flag that controls the behavior of all `string` scalar operators, such as `has`, with respect to case sensitivity. Valid values are `default`, `case_insensitive`, `case_sensitive`. The options `default` and `case_insensitive` are synonymous, since the default behavior is case insensitive. |
| **DatasetPattern** | string | | A wildcard pattern to match datasets. The wildcard `*` is useful to match multiple datasets, but it increases query complexity and decreases performance. |
| **SearchPredicate** | string | ✓ | A Boolean expression to be evaluated for every event in the input. If it returns `true`, the record is outputted. |
## Returns [#returns]
Returns all rows where the specified keyword appears in any field.
## Search predicate syntax [#search-predicate-syntax]
The SearchPredicate allows you to search for specific terms in all fields of a dataset. The operator that will be applied to a search term depends on the presence and placement of a wildcard asterisk (\*) in the term, as shown in the following table.
| Literal | Operator |
| ---------- | --------------- |
| `axiomk` | `has` |
| `*axiomk` | `hassuffix` |
| `axiomk*` | `hasprefix` |
| `*axiomk*` | `contains` |
| `ax*ig` | `matches regex` |
You can also restrict the search to a specific field, look for an exact match instead of a term match, or search by regular expression. The syntax for each of these cases is shown in the following table.
| Syntax | Explanation |
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| **FieldName**`:`**StringLiteral** | This syntax can be used to restrict the search to a specific field. The default behavior is to search all fields. |
| **FieldName**`==`**StringLiteral** | This syntax can be used to search for exact matches of a field against a string value. The default behavior is to look for a term-match. |
| **Field** `matches regex` **StringLiteral** | This syntax indicates regular expression matching, in which *StringLiteral* is the regex pattern. |
Use boolean expressions to combine conditions and create more complex searches. For example, `"axiom" and b==789` would result in a search for events that have the term axiom in any field and the value 789 in the b field.
### Search predicate syntax examples [#search-predicate-syntax-examples]
| # | Syntax | Meaning (equivalent `where`) | Comments |
| -- | ---------------------------------------- | --------------------------------------------------------- | ----------------------------------------- |
| 1 | `search "axiom"` | `where * has "axiom"` | |
| 2 | `search field:"axiom"` | `where field has "axiom"` | |
| 3 | `search field=="axiom"` | `where field=="axiom"` | |
| 4 | `search "axiom*"` | `where * hasprefix "axiom"` | |
| 5 | `search "*axiom"` | `where * hassuffix "axiom"` | |
| 6 | `search "*axiom*"` | `where * contains "axiom"` | |
| 7 | `search "Pad*FG"` | `where * matches regex @"\bPad.*FG\b"` | |
| 8 | `search *` | `where 0==0` | |
| 9 | `search field matches regex "..."` | `where field matches regex "..."` | |
| 10 | `search kind=case_sensitive` | | All string comparisons are case-sensitive |
| 11 | `search "axiom" and ("log" or "metric")` | `where * has "axiom" and (* has "log" or * has "metric")` | |
| 12 | `search "axiom" or (A>a and Aa and A datetime('2022-09-16')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20search%20%5C%22get%5C%22%20and%20_time%20%3E%20datetime%28%272022-09-16%27%29%22%7D\&queryOptions=%7B%22quickRange%22%3A%2230d%22%7D)
### Use kind=default [#use-kinddefault]
By default, the search is case-insensitive and uses the simple search.
```kusto
['sample-http-logs']
| search kind=default "INDIA"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20search%20kind%3Ddefault%20%5C%22INDIA%5C%22%22%7D\&queryOptions=%7B%22quickRange%22%3A%2230d%22%7D)
### Use kind=case\_sensitive [#use-kindcase_sensitive]
Search for logs that contain the term "text" with case sensitivity.
```kusto
['sample-http-logs']
| search kind=case_sensitive "text"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20search%20kind%3Dcase_sensitive%20%5C%22text%5C%22%22%7D\&queryOptions=%7B%22quickRange%22%3A%2230d%22%7D)
### Use kind=case\_insensitive [#use-kindcase_insensitive]
Explicitly search for logs that contain the term "CSS" without case sensitivity.
```kusto
['sample-http-logs']
| search kind=case_insensitive "CSS"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20search%20kind%3Dcase_insensitive%20%5C%22CSS%5C%22%22%7D\&queryOptions=%7B%22quickRange%22%3A%2230d%22%7D)
### Use search \* [#use-search-]
Search all logs. This would essentially return all rows in the dataset.
```kusto
['sample-http-logs']
| search *
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20search%20%2A%22%7D\&queryOptions=%7B%22quickRange%22%3A%2230d%22%7D)
### Contain any substring [#contain-any-substring]
Search for logs that contain any substring of `brazil`.
```kusto
['sample-http-logs']
| search "*brazil*"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20search%20%5C%22%2Abrazil%2A%5C%22%22%7D\&queryOptions=%7B%22quickRange%22%3A%2230d%22%7D)
### Search for multiple independent terms [#search-for-multiple-independent-terms]
Search the logs for entries that contain either the term `GET` or `covina`, irrespective of their context or the fields they appear in.
```kusto
['sample-http-logs']
| search "GET" or "covina"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20search%20%5C%22GET%5C%22%20or%20%5C%22covina%5C%22%22%7D\&queryOptions=%7B%22quickRange%22%3A%2230d%22%7D)
### Search across multiple datasets with wildcards [#search-across-multiple-datasets-with-wildcards]
Use the `in` clause with wildcards to search across multiple datasets matching a pattern:
```kusto
search in (github*) "error"
```
This searches for the term `error` across all datasets that start with `github`, such as `github-push-event` and `github-pull-request-event`. The wildcard `*` in `in (github*)` is useful to match multiple datasets, but it increases query complexity and decreases performance.
## Use the search operator efficiently [#use-the-search-operator-efficiently]
Using non-field-specific filters such as the `search` operator has an impact on performance, especially when used over a high volume of events in a wide time range. To use the `search` operator efficiently, follow these guidelines:
* Use field-specific filters when possible. Field-specific filters narrow your query results to events where a field has a given value. They're more efficient than non-field-specific filters, such as the `search` operator, that narrow your query results by searching across all fields for a given value. When you know the target field, replace the `search` operator with `where` clauses that filter for values in a specific field.
* After using the `search` operator in your query, use other operators, such as `project` statements, to limit the number of returned fields.
* Use the `kind` flag when possible. When you know the pattern that string values in your data follow, use the `kind` flag to specify the case-sensitivity of the search.
---
# sort
Source: https://axiom.co/docs/apl/tabular-operators/sort-operator
The `sort` operator in APL arranges the rows of a result set based on one or more fields in ascending or descending order. You can use it to organize your data logically or optimize subsequent operations that depend on ordered data. This operator is useful when analyzing logs, traces, or any dataset where the order of results matters, such as when you’re interested in top or bottom performers, chronological sequences, or sorting by status codes.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| sort by Field1 [asc | desc], Field2 [asc | desc], ...
```
### Parameters [#parameters]
* `Field1`, `Field2`, `...`: The fields to sort by.
* \[asc | desc]: Specify the sorting direction for each field as either `asc` for ascending order or `desc` for descending order.
### Returns [#returns]
A table with rows ordered based on the specified fields.
## Use sort and project together [#use-sort-and-project-together]
When you use `project` and `sort` in the same query, ensure you project the fields that you want to sort on. Similarly, when you use `project-away` and `sort` in the same query, ensure you don’t remove the fields that you want to sort on.
The above is also true for time fields. For example, to project the field `status` and sort on the field `_time`, project both fields similarly to the query below:
```apl
['sample-http-logs']
| project status, _time
| sort by _time desc
```
## Use case examples [#use-case-examples]
Sorting HTTP logs by request duration and then by status code is useful to identify slow requests and their corresponding statuses.
**Query**
```kusto
['sample-http-logs']
| sort by req_duration_ms desc, status asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20sort%20by%20req_duration_ms%20desc%2C%20status%20asc%22%7D)
**Output**
| \_time | req\_duration\_ms | id | status | uri | method | geo.city | geo.country |
| ------------------- | ----------------- | ---- | ------ | ---------- | ------ | -------- | ----------- |
| 2024-10-18 12:34:56 | 5000 | abc1 | 500 | /api/data | GET | New York | US |
| 2024-10-18 12:35:56 | 4500 | abc2 | 200 | /api/users | POST | London | UK |
The query sorts the HTTP logs by the duration of each request in descending order, showing the longest-running requests at the top. If two requests have the same duration, they are sorted by status code in ascending order.
Sorting OpenTelemetry traces by span duration helps identify the longest-running spans within a specific service.
**Query**
```kusto
['otel-demo-traces']
| sort by duration desc, ['service.name'] asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20sort%20by%20duration%20desc%2C%20%5B%27service.name%27%5D%20asc%22%7D)
**Output**
| \_time | duration | span\_id | trace\_id | service.name | kind | status\_code |
| ------------------- | -------- | -------- | --------- | ------------ | ------ | ------------ |
| 2024-10-18 12:36:56 | 00:00:15 | span1 | trace1 | frontend | server | 200 |
| 2024-10-18 12:37:56 | 00:00:14 | span2 | trace2 | cartservice | client | 500 |
This query sorts spans by their duration in descending order, with the longest spans at the top, followed by the service name in ascending order.
Sorting security logs by status code and then by timestamp can help in investigating recent failed requests.
**Query**
```kusto
['sample-http-logs']
| sort by status asc, _time desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20sort%20by%20status%20asc%2C%20_time%20desc%22%7D)
**Output**
| \_time | req\_duration\_ms | id | status | uri | method | geo.city | geo.country |
| ------------------- | ----------------- | ---- | ------ | ---------- | ------ | -------- | ----------- |
| 2024-10-18 12:40:56 | 3000 | abc3 | 400 | /api/login | POST | Toronto | CA |
| 2024-10-18 12:39:56 | 2000 | abc4 | 400 | /api/auth | GET | Berlin | DE |
This query sorts security logs by status code first (in ascending order) and then by the most recent events.
## List of related operators [#list-of-related-operators]
* [top](/apl/tabular-operators/top-operator): Use `top` to return a specified number of rows with the highest or lowest values, but unlike `sort`, `top` limits the result set.
* [project](/apl/tabular-operators/project-operator): Use `project` to select and reorder fields without changing the order of rows.
* [extend](/apl/tabular-operators/extend-operator): Use `extend` to create calculated fields that can then be used in conjunction with `sort` to refine your results.
* [summarize](/apl/tabular-operators/summarize-operator): Use `summarize` to group and aggregate data before applying `sort` for detailed analysis.
## Other query languages [#other-query-languages]
In Splunk SPL, the equivalent of `sort` is the `sort` command, which orders search results based on one or more fields. However, in APL, you must explicitly specify the sorting direction for each field, and sorting by multiple fields requires chaining them with commas.
```splunk Splunk example
| sort - _time, status
```
```kusto APL equivalent
['sample-http-logs']
| sort by _time desc, status asc
```
In SQL, sorting is done using the `ORDER BY` clause. The APL `sort` operator behaves similarly but uses the `by` keyword instead of `ORDER BY`. Additionally, APL requires specifying the order direction (`asc` or `desc`) explicitly for each field.
```sql SQL example
SELECT * FROM sample_http_logs
ORDER BY _time DESC, status ASC
```
```kusto APL equivalent
['sample-http-logs']
| sort by _time desc, status asc
```
---
# summarize
Source: https://axiom.co/docs/apl/tabular-operators/summarize-operator
## Introduction [#introduction]
The `summarize` operator in APL enables you to perform data aggregation and create summary tables from large datasets. You can use it to group data by specified fields and apply aggregation functions such as `count()`, `sum()`, `avg()`, `min()`, `max()`, and many others. This is particularly useful when analyzing logs, tracing OpenTelemetry data, or reviewing security events. The `summarize` operator is helpful when you want to reduce the granularity of a dataset to extract insights or trends.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| summarize [[Field1 =] AggregationFunction [, ...]] [by [Field2 =] GroupExpression [, ...]]
```
### Parameters [#parameters]
* `Field1`: A field name.
* `AggregationFunction`: The aggregation function to apply. Examples include `count()`, `sum()`, `avg()`, `min()`, and `max()`.
* `GroupExpression`: A scalar expression that can reference the dataset.
### Returns [#returns]
The `summarize` operator returns a table where:
* The input rows are arranged into groups having the same values of the `by` expressions.
* The specified aggregation functions are computed over each group, producing a row for each group.
* The result contains the `by` fields and also at least one field for each computed aggregate. Some aggregation functions return multiple fields.
## Use case examples [#use-case-examples]
In log analysis, you can use `summarize` to count the number of HTTP requests grouped by method, or to compute the average request duration.
**Query**
```kusto
['sample-http-logs']
| summarize count() by method
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20count\(\)%20by%20method%22%7D)
**Output**
| method | count\_ |
| ------ | ------- |
| GET | 1000 |
| POST | 450 |
This query groups the HTTP requests by the `method` field and counts how many times each method is used.
You can use `summarize` to analyze OpenTelemetry traces by calculating the average span duration for each service.
**Query**
```kusto
['otel-demo-traces']
| summarize avg(duration) by ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20summarize%20avg\(duration\)%20by%20%5B%27service.name%27%5D%22%7D)
**Output**
| service.name | avg\_duration |
| ------------ | ------------- |
| frontend | 50ms |
| cartservice | 75ms |
This query calculates the average duration of traces for each service in the dataset.
In security log analysis, `summarize` can help group events by status codes and see the distribution of HTTP responses.
**Query**
```kusto
['sample-http-logs']
| summarize count() by status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20count\(\)%20by%20status%22%7D)
**Output**
| status | count\_ |
| ------ | ------- |
| 200 | 1200 |
| 404 | 300 |
This query summarizes HTTP status codes, giving insight into the distribution of responses in your logs.
## Other examples [#other-examples]
```kusto
['sample-http-logs']
| summarize topk(content_type, 20)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20topk\(content_type%2C%2020\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
```kusto
['github-push-event']
| summarize topk(repo, 20) by bin(_time, 24h)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27github-push-event%27%5D%7C%20summarize%20topk\(repo%2C%2020\)%20by%20bin\(_time%2C%2024h\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
Returns a table that shows the heatmap in each interval \[0, 30], \[30, 20, 10], and so on. This example has a cell for `HISTOGRAM(req_duration_ms)`.
```kusto
['sample-http-logs']
| summarize histogram(req_duration_ms, 30)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20histogram\(req_duration_ms%2C%2030\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
```kusto
['github-push-event']
| where _time > ago(7d)
| where repo contains "axiom"
| summarize count(), numCommits=sum(size) by _time=bin(_time, 3h), repo
| take 100
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27github-push-event%27%5D%20%7C%20where%20_time%20%3E%20ago\(7d\)%20%7C%20where%20repo%20contains%20%5C%22axiom%5C%22%20%7C%20summarize%20count\(\)%2C%20numCommits%3Dsum\(size\)%20by%20_time%3Dbin\(_time%2C%203h\)%2C%20repo%20%7C%20take%20100%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
## Limit behavior with time-binning [#limit-behavior-with-time-binning]
When using `limit` or `take` with `summarize` where the first grouping expression is a time bin, Axiom applies the following limit behavior:
1. Compute the global top **N** groups across all time buckets, disregarding the time dimension.
2. Limit each time bucket to only include those groups that are in the global top **N**.
This means the total number of output rows can be more than **N** rows because each time bucket may contain up to **N** groups. For example, if you have 10 time buckets and limit to 5 groups, you can get up to 50 rows.
To limit the result set to exactly **N** rows:
* Apply a second `summarize` statement after the first to aggregate further and limit the results. For example:
```kusto
['sample-http-logs']
| summarize count() by _time=bin(_time, 1h), status
| summarize make_list(count_), make_list(_time) by status
| limit 10
```
* If you don’t need time as the first grouping expression, reorder your groups so time is not first. For example:
```kusto
['sample-http-logs']
| summarize count() by status, _time=bin(_time, 1h)
| limit 10
```
* Convert time to an integer and bin on that instead:
```kusto
['sample-http-logs']
| extend intTime = toint(_time)
| summarize count() by bin(intTime, 3600), status
| limit 10
```
## List of related operators [#list-of-related-operators]
* [count](/apl/tabular-operators/count-operator): Use when you only need to count rows without grouping by specific fields.
* [extend](/apl/tabular-operators/extend-operator): Use to add new calculated fields to a dataset.
* [project](/apl/tabular-operators/project-operator): Use to select specific fields or create new calculated fields, often in combination with `summarize`.
## Other query languages [#other-query-languages]
In Splunk SPL, the `stats` command performs a similar function to APL’s `summarize` operator. Both operators are used to group data and apply aggregation functions. In APL, `summarize` is more explicit about the fields to group by and the aggregation functions to apply.
```sql Splunk example
index="sample-http-logs" | stats count by method
```
```kusto APL equivalent
['sample-http-logs']
| summarize count() by method
```
The `summarize` operator in APL is conceptually similar to SQL’s `GROUP BY` clause with aggregation functions. In APL, you explicitly specify the aggregation function (like `count()`, `sum()`) and the fields to group by.
```sql SQL example
SELECT method, COUNT(*)
FROM sample_http_logs
GROUP BY method
```
```kusto APL equivalent
['sample-http-logs']
| summarize count() by method
```
---
# take
Source: https://axiom.co/docs/apl/tabular-operators/take-operator
The `take` operator in APL allows you to retrieve a specified number of rows from a dataset. It’s useful when you want to preview data, limit the result set for performance reasons, or fetch a random sample from large datasets. The `take` operator can be particularly effective in scenarios like log analysis, security monitoring, and telemetry where large amounts of data are processed, and only a subset is needed for analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| take N
```
### Parameters [#parameters]
* `N`: The number of rows to take from the dataset. `N` must be a positive integer.
### Returns [#returns]
The operator returns the specified number of rows from the dataset.
## Use case examples [#use-case-examples]
The `take` operator is useful in log analysis when you need to view a subset of logs to quickly identify trends or errors without analyzing the entire dataset.
**Query**
```kusto
['sample-http-logs']
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20take%205%22%7D)
**Output**
| \_time | req\_duration\_ms | id | status | uri | method | geo.city | geo.country |
| -------------------- | ----------------- | ---- | ------ | --------- | ------ | -------- | ----------- |
| 2023-10-18T10:00:00Z | 120 | u123 | 200 | /home | GET | Berlin | Germany |
| 2023-10-18T10:01:00Z | 85 | u124 | 404 | /login | POST | New York | USA |
| 2023-10-18T10:02:00Z | 150 | u125 | 500 | /checkout | POST | Tokyo | Japan |
This query retrieves the first 5 rows from the `sample-http-logs` dataset.
In the context of OpenTelemetry traces, the `take` operator helps extract a small number of traces to analyze span performance or trace behavior across services.
**Query**
```kusto
['otel-demo-traces']
| take 3
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20take%203%22%7D)
**Output**
| \_time | duration | span\_id | trace\_id | service.name | kind | status\_code |
| -------------------- | -------- | -------- | --------- | --------------- | -------- | ------------ |
| 2023-10-18T10:10:00Z | 250ms | s123 | t456 | frontend | server | OK |
| 2023-10-18T10:11:00Z | 300ms | s124 | t457 | checkoutservice | client | OK |
| 2023-10-18T10:12:00Z | 100ms | s125 | t458 | cartservice | internal | ERROR |
This query retrieves the first 3 spans from the OpenTelemetry traces dataset.
For security logs, `take` allows quick sampling of log entries to detect patterns or anomalies without needing the entire log file.
**Query**
```kusto
['sample-http-logs']
| take 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20take%2010%22%7D)
**Output**
| \_time | req\_duration\_ms | id | status | uri | method | geo.city | geo.country |
| -------------------- | ----------------- | ---- | ------ | ---------- | ------ | -------- | ----------- |
| 2023-10-18T10:20:00Z | 200 | u223 | 200 | /admin | GET | London | UK |
| 2023-10-18T10:21:00Z | 190 | u224 | 403 | /dashboard | GET | Berlin | Germany |
This query retrieves the first 10 security log entries, useful for quick investigations.
## List of related operators [#list-of-related-operators]
* [limit](/apl/tabular-operators/limit-operator): Similar to `take`, but explicitly limits the result set and often used for pagination or performance optimization.
* [sort](/apl/tabular-operators/sort-operator): Used in combination with `take` when you want to fetch a subset of sorted data.
* [where](/apl/tabular-operators/where-operator): Filters rows based on a condition before using `take` for sampling specific subsets.
## Other query languages [#other-query-languages]
In Splunk SPL, the `head` and `tail` commands perform similar operations to the APL `take` operator, where `head` returns the first N results, and `tail` returns the last N. In APL, `take` is a flexible way to fetch any subset of rows in a dataset.
```sql Splunk example
| head 10
```
```kusto APL equivalent
['sample-http-logs']
| take 10
```
In ANSI SQL, the equivalent of the APL `take` operator is `LIMIT`. While SQL requires you to specify a sorting order with `ORDER BY` for deterministic results, APL allows you to use `take` to fetch a specific number of rows without needing explicit sorting.
```sql SQL example
SELECT * FROM sample_http_logs LIMIT 10;
```
```kusto APL equivalent
['sample-http-logs']
| take 10
```
---
# top
Source: https://axiom.co/docs/apl/tabular-operators/top-operator
The `top` operator in Axiom Processing Language (APL) allows you to retrieve the top N rows from a dataset based on specified criteria. It’s particularly useful when you need to analyze the highest values in large datasets or want to quickly identify trends, such as the highest request durations in logs or top error occurrences in traces. You can apply it in scenarios like log analysis, security investigations, or tracing system performance.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| top N by Expression [asc | desc]
```
### Parameters [#parameters]
* `N`: The number of rows to return.
* `Expression`: A scalar expression used for sorting. The type of the values must be numeric, date, time, or string.
* `[asc | desc]`: Optional. Use to sort in ascending or descending order. The default is descending.
### Returns [#returns]
The `top` operator returns the top N rows from the dataset based on the specified sorting criteria.
## Use case examples [#use-case-examples]
The `top` operator helps you find the HTTP requests with the longest durations.
**Query**
```kusto
['sample-http-logs']
| top 5 by req_duration_ms
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20top%205%20by%20req_duration_ms%22%7D)
**Output**
| \_time | req\_duration\_ms | id | status | uri | method | geo.city | geo.country |
| ------------------- | ----------------- | --- | ------ | ---------------- | ------ | -------- | ----------- |
| 2024-10-01 10:12:34 | 5000 | 123 | 200 | /api/get-data | GET | New York | US |
| 2024-10-01 11:14:20 | 4900 | 124 | 200 | /api/post-data | POST | Chicago | US |
| 2024-10-01 12:15:45 | 4800 | 125 | 200 | /api/update-item | PUT | London | UK |
This query returns the top 5 HTTP requests that took the longest time to process.
The `top` operator is useful for identifying the spans with the longest duration in distributed tracing systems.
**Query**
```kusto
['otel-demo-traces']
| top 5 by duration
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20top%205%20by%20duration%22%7D)
**Output**
| \_time | duration | span\_id | trace\_id | service.name | kind | status\_code |
| ------------------- | -------- | -------- | --------- | --------------- | ------ | ------------ |
| 2024-10-01 10:12:34 | 300ms | span123 | trace456 | frontend | server | 200 |
| 2024-10-01 10:13:20 | 290ms | span124 | trace457 | cartservice | client | 200 |
| 2024-10-01 10:15:45 | 280ms | span125 | trace458 | checkoutservice | server | 500 |
This query returns the top 5 spans with the longest durations from the OpenTelemetry traces.
The `top` operator is useful for identifying the most frequent HTTP status codes in security logs.
**Query**
```kusto
['sample-http-logs']
| summarize count() by status
| top 3 by count_
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20count\(\)%20by%20status%20%7C%20top%203%20by%20count_%22%7D)
**Output**
| status | count\_ |
| ------ | ------- |
| 200 | 500 |
| 404 | 50 |
| 500 | 20 |
This query shows the top 3 most common HTTP status codes in security logs.
## List of related operators [#list-of-related-operators]
* [order](/apl/tabular-operators/order-operator): Use when you need full control over row ordering without limiting the number of results.
* [summarize](/apl/tabular-operators/summarize-operator): Useful when aggregating data over fields and obtaining summarized results.
* [take](/apl/tabular-operators/take-operator): Returns the first N rows without sorting. Use when ordering isn’t necessary.
## Other query languages [#other-query-languages]
The `top` operator in APL is similar to `top` in Splunk SPL but allows greater flexibility in specifying multiple sorting criteria.
```sql Splunk example
index="sample_http_logs" | top limit=5 req_duration_ms
```
```kusto APL equivalent
['sample-http-logs']
| top 5 by req_duration_ms
```
In ANSI SQL, the `TOP` operator is used with an `ORDER BY` clause to limit the number of rows. In APL, the syntax is similar but uses `top` in a pipeline and specifies the ordering criteria directly.
```sql SQL example
SELECT TOP 5 req_duration_ms FROM sample_http_logs ORDER BY req_duration_ms DESC
```
```kusto APL equivalent
['sample-http-logs']
| top 5 by req_duration_ms
```
---
# union
Source: https://axiom.co/docs/apl/tabular-operators/union-operator
The `union` operator in APL allows you to combine the results of two or more queries into a single output. The operator is useful when you need to analyze or compare data from different datasets or tables in a unified manner. By using `union`, you can merge multiple sets of records, keeping all data from the source tables without applying any aggregation or filtering.
The `union` operator is particularly helpful in scenarios like log analysis, tracing OpenTelemetry events, or correlating security logs across multiple sources. You can use it to perform comprehensive investigations by bringing together information from different datasets into one query.
## Union of two datasets [#union-of-two-datasets]
To understand how the `union` operator works, consider these datasets:
**Server requests**
| \_time | status | method | trace\_id |
| ------ | ------ | ------ | --------- |
| 12:10 | 200 | GET | 1 |
| 12:15 | 200 | POST | 2 |
| 12:20 | 503 | POST | 3 |
| 12:25 | 200 | POST | 4 |
**App logs**
| \_time | trace\_id | message |
| ------ | --------- | ------- |
| 12:12 | 1 | foo |
| 12:21 | 3 | bar |
| 13:35 | 27 | baz |
Performing a union on `Server requests` and `Application logs` would result in a new dataset with all the rows from both `DatasetA` and `DatasetB`.
A union of **requests** and **logs** would produce the following result set:
| \_time | status | method | trace\_id | message |
| ------ | ------ | ------ | --------- | ------- |
| 12:10 | 200 | GET | 1 | |
| 12:12 | | | 1 | foo |
| 12:15 | 200 | POST | 2 | |
| 12:20 | 503 | POST | 3 | |
| 12:21 | | | 3 | bar |
| 12:25 | 200 | POST | 4 | |
| 13:35 | | | 27 | baz |
This result combines the rows and merges types for overlapping fields.
## Usage [#usage]
### Syntax [#syntax]
```kusto
T1 | union [withsource=FieldName] [T2], [T3], ...
```
### Parameters [#parameters]
* `T1, T2, T3, ...`: Tables or query results you want to combine into a single output.
* `withsource`: Optional, adds a field to the output where each value specifies the source dataset of the row. Specify the name of this additional field in `FieldName`.
### Returns [#returns]
The `union` operator returns all rows from the specified tables or queries. If fields overlap, they're merged. Non-overlapping fields are retained in their original form.
## Use case examples [#use-case-examples]
In log analysis, you can use the `union` operator to combine HTTP logs from different sources, such as web servers and security systems, to analyze trends or detect anomalies.
**Query**
```kusto
['sample-http-logs']
| union ['security-logs']
| where status == '500'
```
**Output**
| \_time | id | status | uri | method | geo.city | geo.country | req\_duration\_ms |
| ------------------- | ------- | ------ | ------------------- | ------ | -------- | ----------- | ----------------- |
| 2024-10-17 12:34:56 | user123 | 500 | /api/login | GET | London | UK | 345 |
| 2024-10-17 12:35:10 | user456 | 500 | /api/update-profile | POST | Berlin | Germany | 123 |
This query combines two datasets (HTTP logs and security logs) and filters the combined data to show only those entries where the HTTP status code is 500.
When working with OpenTelemetry traces, you can use the `union` operator to combine tracing information from different services for a unified view of system performance.
**Query**
```kusto
['otel-demo-traces']
| union ['otel-backend-traces']
| where ['service.name'] == 'frontend' and status_code == 'error'
```
**Output**
| \_time | trace\_id | span\_id | \['service.name'] | kind | status\_code |
| ------------------- | ---------- | -------- | ----------------- | ------ | ------------ |
| 2024-10-17 12:36:10 | trace-1234 | span-567 | frontend | server | error |
| 2024-10-17 12:38:20 | trace-7890 | span-345 | frontend | client | error |
This query combines traces from two different datasets and filters them to show only errors occurring in the `frontend` service.
For security logs, the `union` operator is useful to combine logs from different sources, such as intrusion detection systems (IDS) and firewall logs.
**Query**
```kusto
['sample-http-logs']
| union ['security-logs']
| where ['geo.country'] == 'Germany'
```
**Output**
| \_time | id | status | uri | method | geo.city | geo.country | req\_duration\_ms |
| ------------------- | ------- | ------ | ---------------- | ------ | -------- | ----------- | ----------------- |
| 2024-10-17 12:34:56 | user789 | 200 | /api/login | GET | Berlin | Germany | 245 |
| 2024-10-17 12:40:22 | user456 | 404 | /api/nonexistent | GET | Munich | Germany | 532 |
This query combines web and security logs, then filters the results to show only those records where the request originated from Germany.
## Other examples [#other-examples]
### Basic union [#basic-union]
This example combines all rows from `github-push-event` and `github-pull-request-event` without any transformation or filtering.
```kusto
['github-push-event']
| union ['github-pull-request-event']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27github-push-event%27%5D%5Cn%7C%20union%20%5B%27github-pull-request-event%27%5D%22%7D)
### Filter after union [#filter-after-union]
This example combines the datasets, and then filters the data to only include rows where the `method` is `GET`.
```kusto
['sample-http-logs']
| union ['github-issues-event']
| where method == "GET"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20union%20%5B%27github-issues-event%27%5D%5Cn%7C%20where%20method%20%3D%3D%20%5C%22GET%5C%22%22%7D)
### Aggregate after union [#aggregate-after-union]
This example combines the datasets and summarizes the data, counting the occurrences of each combination of `content_type` and `actor`.
```kusto
['sample-http-logs']
| union ['github-pull-request-event']
| summarize Count = count() by content_type, actor
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20union%20%5B%27github-pull-request-event%27%5D%5Cn%7C%20summarize%20Count%20%3D%20count%28%29%20by%20content_type%2C%20actor%22%7D)
### Filter and project specific data from combined log sources [#filter-and-project-specific-data-from-combined-log-sources]
This query combines GitHub pull request event logs and GitHub push events, filters by actions made by `github-actions[bot]`, and displays key event details such as `time`, `repository`, `commits`, `head` , `id`.
```kusto
['github-pull-request-event']
| union ['github-push-event']
| where actor == "github-actions[bot]"
| project _time, repo, ['id'], commits, head
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27github-pull-request-event%27%5D%5Cn%7C%20union%20%5B%27github-push-event%27%5D%5Cn%7C%20where%20actor%20%3D%3D%20%5C%22github-actions%5Bbot%5D%5C%22%5Cn%7C%20project%20_time%2C%20repo%2C%20%5B%27id%27%5D%2C%20commits%2C%20head%22%7D)
### Union with field removing [#union-with-field-removing]
This example removes the `content_type` and `commits` field in the datasets `sample-http-logs` and `github-push-event` before combining the datasets.
```kusto
['sample-http-logs']
| union ['github-push-event']
| project-away content_type, commits
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20union%20%5B%27github-push-event%27%5D%5Cn%7C%20project-away%20content_type%2C%20commits%22%7D)
### Filter after union [#filter-after-union-1]
This example performs a union and then filters the resulting set to only include rows where the `method` is `GET`.
```kusto
['sample-http-logs']
| union ['github-issues-event']
| where method == "GET"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20union%20%5B%27github-issues-event%27%5D%5Cn%7C%20where%20method%20%3D%3D%20%5C%22GET%5C%22%22%7D)
### Union with order by [#union-with-order-by]
After the union, the result is ordered by the `type` field.
```kusto
['sample-http-logs']
| union hn
| order by type
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20union%20hn%5Cn%7C%20order%20by%20type%22%7D)
### Union with joint conditions [#union-with-joint-conditions]
This example performs a union and then filters the resulting dataset for rows where `content_type` contains the letter `a` and `city` is `seattle`.
```kusto
['sample-http-logs']
| union ['github-pull-request-event']
| where content_type contains "a" and ['geo.city'] == "Seattle"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20union%20%5B%27github-pull-request-event%27%5D%5Cn%7C%20where%20content_type%20contains%20%5C%22a%5C%22%20and%20%5B%27geo.city%27%5D%20%20%3D%3D%20%5C%22Seattle%5C%22%22%7D)
### Union and count unique values [#union-and-count-unique-values]
After the union, the query calculates the number of unique `geo.city` and `repo` entries in the combined dataset.
```kusto
['sample-http-logs']
| union ['github-push-event']
| summarize UniqueNames = dcount(['geo.city']), UniqueData = dcount(repo)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20union%20%5B%27github-push-event%27%5D%5Cn%7C%20summarize%20UniqueNames%20%3D%20dcount%28%5B%27geo.city%27%5D%29%2C%20UniqueData%20%3D%20dcount%28repo%29%22%7D)
### Union using withsource [#union-using-withsource]
The example below returns the union of all datasets that match the pattern `github*` and counts the number of events in each.
```kusto
union withsource=dataset github*
| summarize count() by dataset
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22union%20withsource%3Ddataset%20github*%20%7C%20summarize%20count\(\)%20by%20dataset%22%7D)
### Union with wildcards [#union-with-wildcards]
The `union` operator supports wildcards to combine multiple datasets matching a pattern. Use `*` to match all datasets, or a suffix pattern like `github*` to match datasets starting with a prefix.
The wildcard `*` is useful to match multiple datasets, but it increases query complexity and decreases performance.
Using `union *` to query all datasets is very expensive. Avoid it in production. Use specific dataset names or prefix patterns instead.
**Match all datasets:**
```kusto
union *
| summarize count() by dataset
```
**Match datasets with a prefix:**
```kusto
union withsource=dataset github*
| summarize count() by dataset
```
## Best practices for the union operator [#best-practices-for-the-union-operator]
To maximize the effectiveness of the union operator in APL, here are some best practices to consider:
* Before using the `union` operator, ensure that the fields being merged have compatible data types.
* Use `project` or `project-away` to include or exclude specific fields. This can improve performance and the clarity of your results, especially when you only need a subset of the available data.
## Other query languages [#other-query-languages]
In Splunk SPL, the `append` command works similarly to the `union` operator in APL. Both operators are used to combine multiple datasets. However, while `append` in Splunk typically adds one dataset to the end of another, APL’s `union` merges datasets while preserving all records.
```splunk Splunk example
index=web OR index=security
```
```kusto APL equivalent
['sample-http-logs']
| union ['security-logs']
```
In ANSI SQL, the `UNION` operator performs a similar function to the APL `union` operator. Both are used to combine the results of two or more queries. However, SQL’s `UNION` removes duplicates by default, whereas APL’s `union` keeps all rows unless you use `union with=kind=unique`.
```sql SQL example
SELECT * FROM web_logs
UNION
SELECT * FROM security_logs;
```
```kusto APL equivalent
['sample-http-logs']
| union ['security-logs']
```
---
# where
Source: https://axiom.co/docs/apl/tabular-operators/where-operator
The `where` operator in APL is used to filter rows based on specified conditions. You can use the `where` operator to return only the records that meet the criteria you define. It’s a foundational operator in querying datasets, helping you focus on specific data by applying conditions to filter out unwanted rows. This is useful when working with large datasets, logs, traces, or security events, allowing you to extract meaningful information quickly.
## Usage [#usage]
### Syntax [#syntax]
```kusto
| where condition
```
### Parameters [#parameters]
* `condition`: A Boolean expression that specifies the filtering condition. The `where` operator returns only the rows that satisfy this condition.
### Returns [#returns]
The `where` operator returns a filtered dataset containing only the rows where the condition evaluates to true.
## Use case examples [#use-case-examples]
In this use case, you filter HTTP logs to focus on records where the HTTP status is 404 (Not Found).
**Query**
```kusto
['sample-http-logs']
| where status == '404'
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20%3D%3D%20'404'%22%7D)
**Output**
| \_time | id | status | method | uri | req\_duration\_ms | geo.city | geo.country |
| ------------------- | ----- | ------ | ------ | -------------- | ----------------- | -------- | ----------- |
| 2024-10-17 10:20:00 | 12345 | 404 | GET | /notfound.html | 120 | Seattle | US |
This query filters out all HTTP requests except those that resulted in a 404 error, making it easy to investigate pages that were not found.
Here, you filter OpenTelemetry traces to retrieve spans where the `duration` exceeded 500 milliseconds.
**Query**
```kusto
['otel-demo-traces']
| where duration > 500ms
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20duration%20%3E%20500ms%22%7D)
**Output**
| \_time | span\_id | trace\_id | duration | service.name | kind | status\_code |
| ------------------- | -------- | --------- | -------- | ------------ | ------ | ------------ |
| 2024-10-17 11:15:00 | abc123 | xyz789 | 520ms | frontend | server | OK |
This query helps identify spans with durations longer than 500 milliseconds, which might indicate performance issues.
In this security use case, you filter logs to find requests from users in a specific country, such as Germany.
**Query**
```kusto
['sample-http-logs']
| where ['geo.country'] == 'Germany'
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20%5B'geo.country'%5D%20%3D%3D%20'Germany'%22%7D)
**Output**
| \_time | id | status | method | uri | req\_duration\_ms | geo.city | geo.country |
| ------------------- | ----- | ------ | ------ | ------ | ----------------- | -------- | ----------- |
| 2024-10-17 09:45:00 | 54321 | 200 | POST | /login | 100 | Berlin | Germany |
This query helps filter logs to investigate activity originating from a specific country, useful for security and compliance.
## where \* has [#where--has]
The `* has` pattern in APL is a dynamic and powerful tool within the `where` operator. It offers you the flexibility to search for specific substrings across all fields in a dataset without the need to specify each field name individually. This becomes especially advantageous when dealing with datasets that have numerous or dynamically named fields.
`where * has` is an expensive operation because it searches all fields. For a more efficient query, explicitly list the fields in which you want to search. For example: `where firstName has "miguel" or lastName has "miguel"`.
### Basic where \* has usage [#basic-where--has-usage]
Find events where any field contains a specific substring.
```kusto
['sample-http-logs']
| where * has "GET"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20where%20%2A%20has%20%5C%22GET%5C%22%22%7D\&queryOptions=%7B%22quickRange%22%3A%2230d%22%7D)
### Combine multiple substrings [#combine-multiple-substrings]
Find events where any field contains one of multiple substrings.
```kusto
['sample-http-logs']
| where * has "GET" or * has "text"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20where%20%2A%20has%20%5C%22GET%5C%22%20or%20%2A%20has%20%5C%22text%5C%22%22%7D\&queryOptions=%7B%22quickRange%22%3A%2230d%22%7D)
### Use \* has with other operators [#use--has-with-other-operators]
Find events where any field contains a substring, and another specific field equals a certain value.
```kusto
['sample-http-logs']
| where * has "css" and req_duration_ms == 1
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20where%20%2A%20has%20%5C%22css%5C%22%20and%20req_duration_ms%20%3D%3D%201%22%7D\&queryOptions=%7B%22quickRange%22%3A%2230d%22%7D)
### Advanced chaining [#advanced-chaining]
Filter data based on several conditions, including fields containing certain substrings, then summarize by another specific criterion.
```kusto
['sample-http-logs']
| where * has "GET" and * has "css"
| summarize Count=count() by method, content_type, server_datacenter
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20where%20%2A%20has%20%5C%22GET%5C%22%20and%20%2A%20has%20%5C%22css%5C%22%5Cn%7C%20summarize%20Count%3Dcount%28%29%20by%20method%2C%20content_type%2C%20server_datacenter%22%7D\&queryOptions=%7B%22quickRange%22%3A%2230d%22%7D)
### Use with aggregations [#use-with-aggregations]
Find the average of a specific field for events where any field contains a certain substring.
```kusto
['sample-http-logs']
| where * has "Japan"
| summarize avg(req_duration_ms)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20where%20%2A%20has%20%5C%22Japan%5C%22%5Cn%7C%20summarize%20avg%28req_duration_ms%29%22%7D\&queryOptions=%7B%22quickRange%22%3A%2230d%22%7D)
### String case transformation [#string-case-transformation]
The `has` operator is case insensitive. Use `has` if you’re unsure about the case of the substring in the dataset. For the case-sensitive operator, use `has_cs`.
```kusto
['sample-http-logs']
| where * has "mexico"
| summarize avg(req_duration_ms)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%5Cn%7C%20where%20%2A%20has%20%5C%22mexico%5C%22%5Cn%7C%20summarize%20avg%28req_duration_ms%29%22%7D\&queryOptions=%7B%22quickRange%22%3A%2230d%22%7D)
## List of related operators [#list-of-related-operators]
* [count](/apl/tabular-operators/count-operator): Use `count` to return the number of records that match specific criteria.
* [distinct](/apl/tabular-operators/distinct-operator): Use `distinct` to return unique values in a dataset, complementing filtering.
* [take](/apl/tabular-operators/take-operator): Use `take` to return a specific number of records, typically in combination with `where` for pagination.
## Other query languages [#other-query-languages]
In Splunk SPL, the `where` operator filters events based on boolean expressions. APL’s `where` operator functions similarly, allowing you to filter rows that satisfy a condition.
```sql Splunk example
index=main | where status="200"
```
```kusto APL equivalent
['sample-http-logs']
| where status == '200'
```
In ANSI SQL, the `WHERE` clause filters rows in a `SELECT` query based on a condition. APL’s `where` operator behaves similarly, but the syntax reflects APL’s specific dataset structures.
```sql SQL example
SELECT * FROM sample_http_logs WHERE status = '200'
```
```kusto APL equivalent
['sample-http-logs']
| where status == '200'
```
---
# Axiom alerting skill
Source: https://axiom.co/docs/console/intelligence/skills/axiom-alerting
The Axiom alerting skill helps AI agents manage Axiom alerting end-to-end:
* **Notifier management**: Create, update, list, and delete notifiers for email, Slack, and webhooks
* **Monitor lifecycle**: Create, update, inspect, and delete monitors for threshold, match-event, and anomaly workflows
* **Validation workflow**: Check monitor history and tune alert behavior based on real execution outcomes
* **Operational guardrails**: Structured guidance for payload validation, monitor field choices, and troubleshooting API errors
## Use Axiom alerting skill [#use-axiom-alerting-skill]
The Axiom alerting skill activates automatically when you ask your AI agent to:
* Create or manage monitors and notifiers
* Route alerts to the right destination
* Validate monitor behavior and alert noise
* Maintain and tune existing alert configurations
Example prompts:
* "Create a monitor that alerts when error count exceeds 100 in 5 minutes"
* "List available notifiers and create a Slack notifier for on-call alerts"
* "Show monitor history for the last day and help tune alert thresholds"
* "Update this monitor to reduce noisy alerts with N-of-M triggering"
---
# Build dashboards skill
Source: https://axiom.co/docs/console/intelligence/skills/build-dashboards
The Build dashboards skill turns AI agents into dashboard design experts:
* **Design methodology**: Decision-first dashboard design with overview-to-drilldown structure
* **Chart types**: Complete reference for statistic, time series, table, pie, log stream, heatmap, and more
* **APL patterns**: Golden signal queries, cardinality guardrails, and time-based aggregations
* **Smart filters**: Interactive filtering with cascading dropdowns
* **API helpers**: Scripts for creating, updating, and validating dashboards via the Axiom API
* **Templates**: Pre-built dashboard templates for common use cases
## Use Build dashboards skill [#use-build-dashboards-skill]
The Build dashboards skill activates automatically when you ask your AI agent to:
* Create or design dashboards
* Migrate Splunk dashboards to Axiom
* Configure chart types and options
* Build smart filters for interactive filtering
Example prompts:
* "Create a service health dashboard for the API"
* "Build a dashboard with error rate, latency percentiles, and traffic trends"
* "Add a smart filter dropdown for filtering by environment"
* "Migrate this Splunk dashboard to Axiom"
## Dashboard templates [#dashboard-templates]
The Build dashboards skill includes pre-built templates:
| Template | Use case |
| ------------------------------------ | ---------------------------------------------------- |
| `service-overview.json` | Single-service on-call dashboard with heatmap |
| `service-overview-with-filters.json` | Single-service dashboard with smart filter dropdowns |
| `api-health.json` | HTTP API with traffic, errors, and latency |
| `blank.json` | Minimal dashboard skeleton for custom dashboards |
---
# Control costs skill
Source: https://axiom.co/docs/console/intelligence/skills/control-costs
The Control costs skill helps AI agents optimize Axiom usage and reduce costs:
* **Waste identification**: Find unused columns, never-queried datasets, and high-volume low-value data
* **Query analysis**: Analyze query patterns to identify optimization opportunities
* **Dashboard deployment**: Deploy cost control dashboards with ingest trends, burn rate, and projections
* **Monitor creation**: Set up monitors for contract limits, spike detection, and reduction glidepaths
* **Analysis workflow**: Systematic approach to prioritize and analyze datasets by impact
## Use Control costs skill [#use-control-costs-skill]
The Control costs skill activates automatically when you ask your AI agent to:
* Reduce Axiom costs or find waste
* Set up cost control dashboards and monitors
* Analyze query coverage and unused data
* Track ingest spend and burn rate
Example prompts:
* "Help reduce Axiom costs"
* "Find unused columns in the datasets"
* "Set up cost control monitoring for the organization"
* "Which datasets are being paid for but never queried?"
## Cost control workflow [#cost-control-workflow]
The Control costs skill follows a phased approach:
| Phase | Description |
| ------------ | ----------------------------------------------------------- |
| Discovery | Capture baseline ingest statistics and build analysis queue |
| Dashboard | Deploy cost control dashboard with trends and projections |
| Monitors | Create monitors for contract limits and spike detection |
| Optimization | Analyze datasets to find waste and recommend actions |
| Glidepath | Track reduction progress with weekly threshold updates |
---
# Query metrics skill
Source: https://axiom.co/docs/console/intelligence/skills/query-metrics
The Query metrics skill turns AI agents into metrics exploration experts:
* **Metrics query specification**: Self-describing query endpoint that teaches the agent how to write metrics queries on demand
* **Metrics discovery**: Explore available metrics, tags, and tag values in any OTel metrics dataset
* **Query execution**: Compose and run metrics queries with filtering, aggregation, and grouping
* **Error handling**: Structured error reporting with trace IDs for backend debugging
The target dataset must be an OTel metrics dataset.
## Use Query metrics skill [#use-query-metrics-skill]
The Query metrics skill activates automatically when you ask your AI agent to:
* Query metrics data from Axiom MetricsDB
* Explore available metrics, tags, and tag values in a dataset
* Investigate OTel metrics data
* Check metric values for debugging or monitoring
Example prompts:
* "Query the CPU usage metric from the app.metrics dataset"
* "What metrics are available in the dataset?"
* "Show the tag values for the service.name tag"
* "Find metrics related to HTTP requests"
## How it works [#how-it-works]
The Query metrics skill uses a structured workflow:
1. **Learn the query specification**: The agent calls the self-describing query endpoint to fetch the full metrics query specification.
2. **Discover metrics**: The agent searches for metrics matching relevant terms or lists available metrics in the target dataset using the discovery endpoints.
3. **Explore tags**: The agent lists tags and tag values to understand the available filtering options.
4. **Write and execute query**: The agent composes a metrics query and runs it against MetricsDB.
5. **Iterate**: The agent refines filters, aggregations, and groupings based on results.
---
# System Reliability Engineering (SRE) skill
Source: https://axiom.co/docs/console/intelligence/skills/sre
The SRE skill turns AI agents into expert SRE investigators:
* **Investigation methodology**: Hypothesis-driven debugging with systematic triage using Golden Signals and USE/RED methods
* **APL query patterns**: Reference materials for constructing effective Axiom queries
* **Memory system**: Persistent storage that learns from debugging sessions, capturing what works and what doesn't
* **API helpers**: Scripts for querying Axiom directly from the agent
## Use SRE skill [#use-sre-skill]
The SRE skill activates automatically when you ask your AI agent about:
* Incident response and debugging
* Root cause analysis
* Log investigation
* Production troubleshooting
Example prompts:
* "Investigate why API latency increased in the last hour"
* "Find the root cause of the 500 errors in production"
* "Analyze error patterns in the logs dataset"
## Memory system [#memory-system]
The SRE skill learns from every debugging session. The memory system initializes automatically on first use. Memory persists at `~/.config/amp/memory/axiom-sre/` (global) or `.agents/memory/axiom-sre/` (project/local).
### Tell the agent to remember things [#tell-the-agent-to-remember-things]
* `Remember this for next time.`
* `Save this query, it worked.`
* `Add to memory: the orders team uses #orders-oncall.`
### Learn automatically [#learn-automatically]
The SRE skill automatically captures what works and what doesn't when:
* A query or approach finds the root cause.
* You correct it, and it records what didn't work and what did.
* A debugging session completes successfully.
### Customize with your own knowledge [#customize-with-your-own-knowledge]
Customize the SRE skill by adding your own knowledge to the knowledge base files:
* `kb/facts.md`: Team contacts, Slack channels, conventions.
* `kb/integrations.md`: Database connections, API endpoints.
* `kb/patterns.md`: Failure patterns you've seen before.
---
# Translate SPL to APL skill
Source: https://axiom.co/docs/console/intelligence/skills/translate-spl-to-apl
The Translate SPL to APL skill helps AI agents translate Splunk queries to Axiom:
* **Command mappings**: Complete translation of SPL commands to APL operators
* **Function equivalents**: Mapping of SPL functions to APL functions
* **Syntax transformations**: Handle differences in time handling, field escaping, and query structure
* **Common patterns**: Ready-to-use translations for frequent query patterns
* **Performance tips**: Guidance on writing efficient APL queries
## Use Translate SPL to APL skill [#use-translate-spl-to-apl-skill]
The Translate SPL to APL skill activates automatically when you ask your AI agent to:
* Translate SPL queries to APL
* Migrate Splunk queries to APL
* Find APL equivalents of SPL commands
* Convert Splunk dashboards or saved searches
Example prompts:
* "Convert this SPL query to APL: `index=logs | stats count by host`"
* "What's the APL equivalent of `timechart span=5m count by status`?"
* "Translate this Splunk search to APL"
* "How to write `eval` statements in APL?"
---
# array_concat
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-concat
The `array_concat` function in APL (Axiom Processing Language) concatenates two or more arrays into a single array. Use this function when you need to merge multiple arrays into a single array structure. It’s particularly useful for situations where you need to handle and combine collections of elements across different fields or sources, such as log entries, OpenTelemetry trace data, or security logs.
## Usage [#usage]
### Syntax [#syntax]
```kusto
array_concat(array1, array2, ...)
```
### Parameters [#parameters]
* `array1`: The first array to concatenate.
* `array2`: The second array to concatenate.
* `...`: Additional arrays to concatenate.
### Returns [#returns]
An array containing all elements from the input arrays in the order they're provided.
## Use case examples [#use-case-examples]
In log analysis, you can use `array_concat` to merge collections of user requests into a single array to analyze request patterns across different endpoints.
**Query**
```kusto
['sample-http-logs']
| take 50
| summarize combined_requests = array_concat(pack_array(uri), pack_array(method))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20take%2050%20%7C%20summarize%20combined_requests%20%3D%20array_concat\(pack_array\(uri\)%2C%20pack_array\(method\)\)%22%7D)
**Output**
| \_time | uri | method | combined\_requests |
| ------------------- | ----------------------- | ------ | ------------------------------------ |
| 2024-10-28T12:30:00 | /api/v1/textdata/cnfigs | POST | \["/api/v1/textdata/cnfigs", "POST"] |
This example concatenates the `uri` and `method` values into a single array for each log entry, allowing for combined analysis of access patterns and request methods in log data.
In OpenTelemetry traces, use `array_concat` to join span IDs and trace IDs for a comprehensive view of trace behavior across services.
**Query**
```kusto
['otel-demo-traces']
| take 50
| summarize combined_ids = array_concat(pack_array(span_id), pack_array(trace_id))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20take%2050%20%7C%20summarize%20combined_ids%20%3D%20array_concat\(pack_array\(span_id\)%2C%20pack_array\(trace_id\)\)%22%7D)
**Output**
| combined\_ids |
| ---------------------------------- |
| \["span1", "trace1", "span2", ...] |
| \_time | trace\_id | span\_id | combined\_ids |
| ------------------- | ------------- | --------- | ------------------------------- |
| 2024-10-28T12:30:00 | trace\_abc123 | span\_001 | \["trace\_abc123", "span\_001"] |
This example creates an array containing both `span_id` and `trace_id` values, offering a unified view of the trace journey across services.
In security logs, `array_concat` can consolidate multiple IP addresses or user IDs to detect potential attack patterns involving different locations or users.
**Query**
```kusto
['sample-http-logs']
| where status == '500'
| take 50
| summarize failed_attempts = array_concat(pack_array(id), pack_array(['geo.city']))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20%3D%3D%20'500'%20%7C%20take%2050%20%7C%20summarize%20failed_attempts%20%3D%20array_concat\(pack_array\(id\)%2C%20pack_array\(%5B'geo.city'%5D\)\)%22%7D)
**Output**
| \_time | id | geo.city | combined\_ids |
| ------------------- | ------------------------------------ | -------- | --------------------------------------------------- |
| 2024-10-28T12:30:00 | fc1407f5-04ca-4f4e-ad01-f72063736e08 | Avenal | \["fc1407f5-04ca-4f4e-ad01-f72063736e08", "Avenal"] |
This query combines failed user IDs and cities where the request originated, allowing security analysts to detect suspicious patterns or brute force attempts from different regions.
## List of related functions [#list-of-related-functions]
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the number of elements in an array.
* [array\_index\_of](/apl/scalar-functions/array-functions/array-index-of): Finds the index of an element in an array.
* [array\_slice](/apl/scalar-functions/array-functions/array-slice): Extracts a subset of elements from an array.
## Other query languages [#other-query-languages]
In SPL, you typically use the `mvappend` function to concatenate multiple fields or arrays into a single array. In APL, the equivalent is `array_concat`, which also combines arrays but requires you to specify each array as a parameter.
```sql Splunk example
| eval combined_array = mvappend(array1, array2, array3)
```
```kusto APL equivalent
| extend combined_array = array_concat(array1, array2, array3)
```
ANSI SQL doesn’t natively support an array concatenation function across different arrays. Instead, you typically use `UNION` to combine results from multiple arrays or collections. In APL, `array_concat` allows you to directly concatenate multiple arrays, providing a more straightforward approach.
```sql SQL example
SELECT array1 UNION ALL array2 UNION ALL array3
```
```kusto APL equivalent
| extend combined_array = array_concat(array1, array2, array3)
```
---
# array_extract
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-extract
Use the `array_extract` function to extract specific values from a dynamic array using a JSON path expression. You can use this function to transform structured array data, such as arrays of objects, into simpler arrays of scalars. This is useful when working with nested JSON-like structures where you need to extract only selected fields for analysis, visualization, or filtering.
Use `array_extract` when:
* You need to pull scalar values from arrays of objects.
* You want to simplify a nested data structure before further analysis.
* You are working with structured logs or metrics where key values are nested inside arrays.
## Usage [#usage]
### Syntax [#syntax]
```kusto
array_extract(sourceArray, jsonPath)
```
### Parameters [#parameters]
| Name | Type | Description |
| ------------- | --------- | ------------------------------------------------------- |
| `sourceArray` | `dynamic` | A JSON-like dynamic array to extract values from. |
| `jsonPath` | `string` | A JSON path expression to select values from the array. |
### Returns [#returns]
A dynamic array of values that match the JSON path expression. The function always returns an array, even when the path matches only one element or no elements.
## Use case examples [#use-case-examples]
Use `array_extract` to retrieve specific fields from structured arrays, such as arrays of request metadata.
**Query**
```kusto
['sample-http-logs']
| extend extracted_value = array_extract(dynamic([{'id': 1, 'value': true}, {'id': 2, 'value': false}]), @'$[*].value')
| project _time, extracted_value
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20extracted_value%20%3D%20array_extract%28dynamic%28\[%7B'id'%3A%201%2C%20'value'%3A%20true%7D%2C%20%7B'id'%3A%202%2C%20'value'%3A%20false%7D]%29%2C%20%40'%24%5B*%5D.value'%29%20%7C%20project%20_time%2C%20extracted_value%22%7D)
**Output**
| \_time | extracted\_value |
| ---------------- | ------------------ |
| Jun 24, 09:28:10 | \["true", "false"] |
| Jun 24, 09:28:10 | \["true", "false"] |
| Jun 24, 09:28:10 | \["true", "false"] |
This query extracts the `value` field from an array of objects, returning a flat array of booleans in string form.
Use `array_extract` to extract service names from a nested structure—for example, collecting `service.name` from span records in a trace bundle.
**Query**
```kusto
['otel-demo-traces']
| summarize traces=make_list(pack('trace_id', trace_id, 'service', ['service.name'])) by span_id
| extend services=array_extract(traces, @'$[*].service')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20traces%3Dmake_list%28pack%28'trace_id'%2C%20trace_id%2C%20'service'%2C%20%5B'service.name'%5D%29%29%20by%20span_id%20%7C%20extend%20services%3Darray_extract%28traces%2C%20%40'%24%5B*%5D.service'%29%22%7D)
**Output**
| span\_id | services |
| ---------------- | ----------------- |
| 24157518330f7967 | \[frontend-proxy] |
| 209a0815d291d88a | \[currency] |
| aca763479149f1d0 | \[frontend-web] |
This query collects and extracts the `service.name` fields from a constructed nested structure of spans.
Use `array_extract` to extract HTTP status codes from structured log entries grouped into sessions.
**Query**
```kusto
['sample-http-logs']
| summarize events=make_list(pack('uri', uri, 'status', status)) by id
| extend status_codes=array_extract(events, @'$[*].status')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20events%3Dmake_list%28pack%28'uri'%2C%20uri%2C%20'status'%2C%20status%29%29%20by%20id%20%7C%20extend%20status_codes%3Darray_extract%28events%2C%20%40'%24%5B*%5D.status'%29%22%7D)
**Output**
| id | status\_codes |
| ----- | ------------- |
| user1 | \[200] |
| user2 | \[201] |
| user3 | \[200] |
This query extracts all HTTP status codes per user session, helping to identify patterns like repeated failures or suspicious behavior.
## List of related functions [#list-of-related-functions]
* [array\_slice](/apl/scalar-functions/array-functions/array-slice): Returns a subarray like `array_extract`, but supports negative indexing.
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the number of elements in an array. Useful before applying `array_extract`.
* [array\_concat](/apl/scalar-functions/array-functions/array-concat): Joins arrays end-to-end. Use before or after slicing arrays with `array_extract`.
* [array\_index\_of](/apl/scalar-functions/array-functions/array-index-of): Finds the position of an element in an array, which can help set the `startIndex` for `array_extract`.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use `spath` with a wildcard or field extraction logic to navigate nested structures. APL’s `array_extract` uses JSON path syntax to extract array elements that match a given pattern.
```sql Splunk example
| eval arr=mvappend("{\"id\":1,\"value\":true}", "{\"id\":2,\"value\":false}")
| spath input=arr path="{}.value" output=extracted_value
```
```kusto APL equivalent
['sample-http-logs']
| extend extracted_value = array_extract(dynamic([{'id': 1, 'value': true}, {'id': 2, 'value': false}]), @'$[*].value')
| project _time, extracted_value
```
ANSI SQL doesn’t offer native support for JSON path queries on arrays in standard syntax. While some engines support functions like `JSON_VALUE` or `JSON_TABLE`, they operate on single objects. APL’s `array_extract` provides a concise and expressive way to query arrays using JSON path.
```sql SQL example
SELECT JSON_EXTRACT(data, '$[*].value') AS extracted_value
FROM my_table;
```
```kusto APL equivalent
['sample-http-logs']
| extend extracted_value = array_extract(dynamic([{'id': 1, 'value': true}, {'id': 2, 'value': false}]), @'$[*].value')
| project _time, extracted_value
```
---
# array_iff
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-iff
The `array_iff` function in Axiom Processing Language (APL) allows you to create arrays based on a condition. It returns an array with elements from two specified arrays, choosing each element from the first array when a condition is met and from the second array otherwise. This function is useful for scenarios where you need to evaluate a series of conditions across multiple datasets, especially in log analysis, trace data, and other applications requiring conditional element selection within arrays.
## Usage [#usage]
### Syntax [#syntax]
```kusto
array_iff(condition_array, array1, array2)
```
### Parameters [#parameters]
* `condition_array`: An array of boolean values, where each element determines whether to choose the corresponding element from `array1` or `array2`.
* `array1`: The array to select elements from when the corresponding `condition_array` element is `true`.
* `array2`: The array to select elements from when the corresponding `condition_array` element is `false`.
### Returns [#returns]
An array where each element is selected from `array1` if the corresponding `condition_array` element is `true`, and from `array2` otherwise.
## Use case examples [#use-case-examples]
The `array_iff` function can help filter log data conditionally, such as choosing specific durations based on HTTP status codes.
**Query**
```kusto
['sample-http-logs']
| order by _time desc
| limit 1000
| summarize is_ok = make_list(status == '200'), request_duration = make_list(req_duration_ms)
| project ok_request_duration = array_iff(is_ok, request_duration, 0)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20order%20by%20_time%20desc%20%7C%20limit%201000%20%7C%20summarize%20is_ok%20%3D%20make_list\(status%20%3D%3D%20'200'\)%2C%20request_duration%20%3D%20make_list\(req_duration_ms\)%20%7C%20project%20ok_request_duration%20%3D%20array_iff\(is_ok%2C%20request_duration%2C%200\)%22%7D)
**Output**
| ok\_request\_duration |
| -------------------------------------------------------------------- |
| \[0.3150485097707766, 0, 0.21691408087847264, 0, 0.2757618582190533] |
This example filters the `req_duration_ms` field to include only durations for the most recent 1,000 requests with status `200`, replacing others with `0`.
With OpenTelemetry trace data, you can use `array_iff` to filter spans based on the service type, such as selecting durations for `server` spans and setting others to zero.
**Query**
```kusto
['otel-demo-traces']
| order by _time desc
| limit 1000
| summarize is_server = make_list(kind == 'server'), duration_list = make_list(duration)
| project server_durations = array_iff(is_server, duration_list, 0)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20order%20by%20_time%20desc%20%7C%20limit%201000%20%7C%20summarize%20is_server%20%3D%20make_list\(kind%20%3D%3D%20'server'\)%2C%20duration_list%20%3D%20make_list\(duration\)%20%7C%20project%20%20server_durations%20%3D%20array_iff\(is_server%2C%20duration_list%2C%200\)%22%7D)
**Output**
| server\_durations |
| ---------------------------------------- |
| \["45.632µs", "54.622µs", 0, "34.051µs"] |
In this example, `array_iff` selects durations only for `server` spans, setting non-server spans to `0`.
In security logs, `array_iff` can be used to focus on specific cities in which HTTP requests originated, such as showing response durations for certain cities and excluding others.
**Query**
```kusto
['sample-http-logs']
| limit 1000
| summarize is_london = make_list(['geo.city'] == "London"), request_duration = make_list(req_duration_ms)
| project london_duration = array_iff(is_london, request_duration, 0)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%20%7C%20limit%201000%20%7C%20summarize%20is_london%20%3D%20make_list\(%5B'geo.city'%5D%20%3D%3D%20'London'\)%2C%20request_duration%20%3D%20make_list\(req_duration_ms\)%20%7C%20project%20london_duration%20%3D%20array_iff\(is_london%2C%20request_duration%2C%200\)%22%7D)
**Output**
| london\_duration |
| ---------------- |
| \[100, 0, 250] |
This example filters the `req_duration_ms` array to show durations for requests from London, with non-matching cities having `0` as duration.
## List of related functions [#list-of-related-functions]
* [array\_slice](/apl/scalar-functions/array-functions/array-slice): Extracts a subset of elements from an array.
* [array\_concat](/apl/scalar-functions/array-functions/array-concat): Combines multiple arrays.
* [array\_rotate\_right](/apl/scalar-functions/array-functions/array-rotate-right): Rotates array elements to the right by a specified number of positions.
## Other query languages [#other-query-languages]
In Splunk SPL, array manipulation based on conditions typically requires using conditional functions or eval expressions. APL’s `array_iff` function lets you directly select elements from one array or another based on a condition, offering more streamlined array manipulation.
```sql Splunk example
eval selected_array=if(condition, array1, array2)
```
```kusto APL equivalent
array_iff(condition_array, array1, array2)
```
In ANSI SQL, conditionally selecting elements from arrays often requires complex `CASE` statements or functions. With APL’s `array_iff` function, you can directly compare arrays and conditionally populate them, simplifying array-based operations.
```sql SQL example
CASE WHEN condition THEN array1 ELSE array2 END
```
```kusto APL equivalent
array_iff(condition_array, array1, array2)
```
---
# array_index_of
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-index-of
The `array_index_of` function in APL returns the zero-based index of the first occurrence of a specified value within an array. If the value isn’t found, the function returns `-1`. Use this function when you need to identify the position of a specific item within an array, such as finding the location of an error code in a sequence of logs or pinpointing a particular value within telemetry data arrays.
## Usage [#usage]
### Syntax [#syntax]
```kusto
array_index_of(array, lookup_value, [start], [length], [occurrence])
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| array | array | Yes | Input array to search. |
| lookup\_value | scalar | Yes | Scalar value to search for in the array. Accepted data types: long, integer, double, datetime, timespan, or string. |
| start\_index | number | No | The index where to start the search. A negative value offsets the starting search value from the end of the array by `abs(start_index)` steps. |
| length | number | No | Number of values to examine. A value of `-1` means unlimited length. |
| occurrence | number | No | The number of the occurrence. By default `1`. |
### Returns [#returns]
`array_index_of` returns the zero-based index of the first occurrence of the specified `lookup_value` in `array`. If `lookup_value` doesn’t exist in the array, it returns `-1`.
## Use case examples [#use-case-examples]
You can use `array_index_of` to find the position of a specific HTTP status code within an array of codes in your log analysis.
**Query**
```kusto
['sample-http-logs']
| take 50
| summarize status_array = make_list(status)
| extend index_500 = array_index_of(status_array, '500')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20take%2050%20%7C%20summarize%20status_array%20%3D%20make_list\(status\)%20%7C%20extend%20index_500%20%3D%20array_index_of\(status_array%2C%20'500'\)%22%7D)
**Output**
| status\_array | index\_500 |
| ---------------------- | ---------- |
| \["200", "404", "500"] | 2 |
This query creates an array of `status` codes and identifies the position of the first occurrence of the `500` status.
In OpenTelemetry traces, you can find the position of a specific `service.name` within an array of service names to detect when a particular service appears.
**Query**
```kusto
['otel-demo-traces']
| take 50
| summarize service_array = make_list(['service.name'])
| extend frontend_index = array_index_of(service_array, 'frontend')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20%20service_array%20%3D%20make_list\(%5B'service.name'%5D\)%20%7C%20extend%20frontend_index%20%3D%20array_index_of\(service_array%2C%20'frontend'\)%22%7D)
**Output**
| service\_array | frontend\_index |
| ---------------------------- | --------------- |
| \["frontend", "cartservice"] | 0 |
This query collects the array of services and determines where the `frontend` service first appears.
When working with security logs, `array_index_of` can help identify the index of a particular error or status code, such as `500`, within an array of `status` codes.
**Query**
```kusto
['sample-http-logs']
| take 50
| summarize status_array = make_list(status)
| extend index_500 = array_index_of(status_array, '500')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20take%2050%20%7C%20summarize%20status_array%20%3D%20make_list\(status\)%20%7C%20extend%20index_500%20%3D%20array_index_of\(status_array%2C%20'500'\)%22%7D)
**Output**
| status\_array | index\_500 |
| ---------------------- | ---------- |
| \["200", "404", "500"] | 2 |
This query helps identify at what index the `500` status code appears.
## List of related functions [#list-of-related-functions]
* [array\_concat](/apl/scalar-functions/array-functions/array-concat): Combines multiple arrays.
* [array\_rotate\_right](/apl/scalar-functions/array-functions/array-rotate-right): Rotates array elements to the right by a specified number of positions.
* [array\_rotate\_left](/apl/scalar-functions/array-functions/array-rotate-left): Rotates elements of an array to the left.
## Other query languages [#other-query-languages]
In Splunk SPL, the `mvfind` function retrieves the position of an element within an array, similar to how `array_index_of` operates in APL. However, note that APL uses a zero-based index for results, while SPL is one-based.
```splunk Splunk example
| eval index=mvfind(array, "value")
```
```kusto APL equivalent
let index = array_index_of(array, 'value')
```
ANSI SQL doesn’t have a direct equivalent for finding the index of an element within an array. Typically, you would use a combination of array and search functions if supported by your SQL variant.
```sql SQL example
SELECT POSITION('value' IN ARRAY[...])
```
```kusto APL equivalent
let index = array_index_of(array, 'value')
```
---
# array_length
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-length
The `array_length` function in APL (Axiom Processing Language) returns the length of an array. You can use this function to analyze and filter data by array size, such as identifying log entries with specific numbers of entries or events with multiple tags. This function is useful for analyzing structured data fields that contain arrays, such as lists of error codes, tags, or IP addresses.
## Usage [#usage]
### Syntax [#syntax]
```kusto
array_length(array_expression)
```
### Parameters [#parameters]
* array\_expression: An expression representing the array to measure.
### Returns [#returns]
The function returns an integer representing the number of elements in the specified array.
## Use case example [#use-case-example]
In OpenTelemetry traces, `array_length` can reveal the number of events associated with a span.
**Query**
```kusto
['otel-demo-traces']
| take 50
| extend event_count = array_length(events)
| where event_count > 2
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20take%2050%20%7C%20extend%20event_count%20%3D%20array_length\(events\)%20%7C%20where%20event_count%20%3E%202%22%7D)
**Output**
| \_time | trace\_id | span\_id | service.name | event\_count |
| ------------------- | ------------- | --------- | ------------ | ------------ |
| 2024-10-28T12:30:00 | trace\_abc123 | span\_001 | frontend | 3 |
This query finds spans associated with at least three events.
## List of related functions [#list-of-related-functions]
* [array\_slice](/apl/scalar-functions/array-functions/array-slice): Extracts a subset of elements from an array.
* [array\_concat](/apl/scalar-functions/array-functions/array-concat): Combines multiple arrays.
* [array\_shift\_left](/apl/scalar-functions/array-functions/array-shift-left): Shifts array elements one position to the left, moving the first element to the last position.
## Other query languages [#other-query-languages]
In Splunk SPL, you might use the `mvcount` function to determine the length of a multivalue field. In APL, `array_length` serves the same purpose by returning the size of an array within a column.
```sql Splunk example
| eval array_size = mvcount(array_field)
```
```kusto APL equivalent
['sample-http-logs']
| extend array_size = array_length(array_field)
```
In ANSI SQL, you would use functions such as `CARDINALITY` or `ARRAY_LENGTH` (in databases that support arrays) to get the length of an array. In APL, the `array_length` function is straightforward and works directly with array fields in any dataset.
```sql SQL example
SELECT CARDINALITY(array_field) AS array_size
FROM sample_table
```
```kusto APL equivalent
['sample-http-logs']
| extend array_size = array_length(array_field)
```
---
# array_reverse
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-reverse
Use the `array_reverse` function in APL to reverse the order of elements in an array. This function is useful when you need to transform data where the sequence matters, such as reversing a list of events for chronological analysis or processing lists in descending order.
## Usage [#usage]
### Syntax [#syntax]
```kusto
array_reverse(array_expression)
```
### Parameters [#parameters]
* `array_expression`: The array you want to reverse. This array must be of a dynamic type.
### Returns [#returns]
Returns the input array with its elements in reverse order.
## Use case examples [#use-case-examples]
Use `array_reverse` to inspect the sequence of actions in log entries, reversing the order to understand the initial steps of a user's session.
**Query**
```kusto
['sample-http-logs']
| summarize paths = make_list(uri) by id
| project id, reversed_paths = array_reverse(paths)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20paths%20%3D%20make_list\(uri\)%20by%20id%20%7C%20project%20id%2C%20reversed_paths%20%3D%20array_reverse\(paths\)%22%7D)
**Output**
| id | reversed\_paths |
| ----- | ------------------------------------ |
| U1234 | \['/home', '/cart', '/product', '/'] |
| U5678 | \['/login', '/search', '/'] |
This example identifies a user’s navigation sequence in reverse, showing their entry point into the system.
Use `array_reverse` to analyze trace data by reversing the sequence of span events for each trace, allowing you to trace back the sequence of service calls.
**Query**
```kusto
['otel-demo-traces']
| summarize spans = make_list(span_id) by trace_id
| project trace_id, reversed_spans = array_reverse(spans)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20spans%20%3D%20make_list\(span_id\)%20by%20trace_id%20%7C%20project%20trace_id%2C%20reversed_spans%20%3D%20array_reverse\(spans\)%22%7D)
**Output**
| trace\_id | reversed\_spans |
| --------- | ------------------------- |
| T12345 | \['S4', 'S3', 'S2', 'S1'] |
| T67890 | \['S7', 'S6', 'S5'] |
This example reveals the order in which service calls were made in a trace, but in reverse, aiding in backtracking issues.
Apply `array_reverse` to examine security events, like login attempts or permission checks, in reverse order to identify unusual access patterns or last actions.
**Query**
```kusto
['sample-http-logs']
| where status == '403'
| summarize blocked_uris = make_list(uri) by id
| project id, reversed_blocked_uris = array_reverse(blocked_uris)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20%3D%3D%20'403'%20%7C%20summarize%20blocked_uris%20%3D%20make_list\(uri\)%20by%20id%20%7C%20project%20id%2C%20reversed_blocked_uris%20%3D%20array_reverse\(blocked_uris\)%22%7D)
**Output**
| id | reversed\_blocked\_uris |
| ----- | ------------------------------------- |
| U1234 | \['/admin', '/settings', '/login'] |
| U5678 | \['/account', '/dashboard', '/login'] |
This example helps identify the sequence of unauthorized access attempts by each user.
## List of related functions [#list-of-related-functions]
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the number of elements in an array.
* [array\_shift\_right](/apl/scalar-functions/array-functions/array-shift-right): Shifts array elements to the right.
* [array\_shift\_left](/apl/scalar-functions/array-functions/array-shift-left): Shifts array elements one position to the left, moving the first element to the last position.
## Other query languages [#other-query-languages]
In Splunk, reversing an array isn’t a built-in function, so you typically manipulate the data manually or use workarounds. In APL, `array_reverse` simplifies this process by reversing the array directly.
```sql Splunk example
# SPL doesn’t have a direct array_reverse equivalent.
```
```kusto APL equivalent
let arr = dynamic([1, 2, 3, 4, 5]);
print reversed_arr = array_reverse(arr)
```
Standard ANSI SQL lacks an explicit function to reverse an array; you generally need to create a custom solution. APL’s `array_reverse` makes reversing an array straightforward.
```sql SQL example
-- ANSI SQL lacks a built-in array reverse function.
```
```kusto APL equivalent
let arr = dynamic([1, 2, 3, 4, 5]);
print reversed_arr = array_reverse(arr)
```
---
# array_rotate_left
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-rotate-left
The `array_rotate_left` function in Axiom Processing Language (APL) rotates the elements of an array to the left by a specified number of positions. It’s useful when you want to reorder elements in a fixed-length array, shifting elements to the left while moving the leftmost elements to the end. For instance, this function can help analyze sequences where relative order matters but the starting position doesn’t, such as rotating network logs, error codes, or numeric arrays in data for pattern identification.
## Usage [#usage]
### Syntax [#syntax]
```kusto
array_rotate_left(array, positions)
```
### Parameters [#parameters]
* `array`: The array to be rotated. Use a dynamic data type.
* `positions`: An integer specifying the number of positions to rotate the array to the left.
### Returns [#returns]
A new array where the elements have been rotated to the left by the specified number of positions.
## Use case example [#use-case-example]
Analyze traces by rotating the field order for visualization or pattern matching.
**Query**
```kusto
['otel-demo-traces']
| extend rotated_sequence = array_rotate_left(events, 1)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20rotated_sequence%20%3D%20array_rotate_left\(events%2C%201\)%22%7D)
**Output**
```json events
[
{
"name": "Enqueued",
"timestamp": 1733997117722909000
},
{
"timestamp": 1733997117722911700,
"name": "Sent"
},
{
"name": "ResponseReceived",
"timestamp": 1733997117723591400
}
]
```
```json rotated_sequence
[
{
"timestamp": 1733997117722911700,
"name": "Sent"
},
{
"name": "ResponseReceived",
"timestamp": 1733997117723591400
},
{
"timestamp": 1733997117722909000,
"name": "Enqueued"
}
]
```
This example rotates trace-related fields, which can help to identify variations in trace data when visualized differently.
## List of related functions [#list-of-related-functions]
* [array\_slice](/apl/scalar-functions/array-functions/array-slice): Extracts a subset of elements from an array.
* [array\_rotate\_right](/apl/scalar-functions/array-functions/array-rotate-right): Rotates array elements to the right by a specified number of positions.
* [array\_reverse](/apl/scalar-functions/array-functions/array-reverse): Reverses the order of array elements.
## Other query languages [#other-query-languages]
In APL, `array_rotate_left` allows for direct rotation within the array. Splunk SPL doesn’t have a direct equivalent, so you may need to combine multiple SPL functions to achieve a similar rotation effect.
```sql Splunk example
| eval rotated_array = mvindex(array, 1) . "," . mvindex(array, 0)
```
```kusto APL equivalent
print rotated_array = array_rotate_left(dynamic([1,2,3,4]), 1)
```
ANSI SQL lacks a direct equivalent for array rotation within arrays. A similar transformation can be achieved using array functions if available or by restructuring the array through custom logic.
```sql SQL example
SELECT array_column[2], array_column[3], array_column[0], array_column[1] FROM table
```
```kusto APL equivalent
print rotated_array = array_rotate_left(dynamic([1,2,3,4]), 2)
```
---
# array_rotate_right
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-rotate-right
The `array_rotate_right` function in APL allows you to rotate the elements of an array to the right by a specified number of positions. This function is useful when you need to reorder data within arrays, either to shift recent events to the beginning, reorder log entries, or realign elements based on specific processing logic.
## Usage [#usage]
### Syntax [#syntax]
```kusto
array_rotate_right(array, count)
```
### Parameters [#parameters]
* `array`: An array to rotate.
* `count`: An integer specifying the number of positions to rotate the array to the right.
### Returns [#returns]
An array where the elements are rotated to the right by the specified `count`.
## Use case example [#use-case-example]
In OpenTelemetry traces, rotating an array of span details can help you reorder trace information for performance tracking or troubleshooting.
**Query**
```kusto
['otel-demo-traces']
| extend rotated_sequence = array_rotate_right(events, 1)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20rotated_sequence%20%3D%20array_rotate_right\(events%2C%201\)%22%7D)
**Output**
```json events
[
{
"attributes": null,
"name": "Enqueued",
"timestamp": 1733997421220380700
},
{
"name": "Sent",
"timestamp": 1733997421220390400,
"attributes": null
},
{
"attributes": null,
"name": "ResponseReceived",
"timestamp": 1733997421221118500
}
]
```
```json rotated_sequence
[
{
"attributes": null,
"name": "ResponseReceived",
"timestamp": 1733997421221118500
},
{
"attributes": null,
"name": "Enqueued",
"timestamp": 1733997421220380700
},
{
"name": "Sent",
"timestamp": 1733997421220390400,
"attributes": null
}
]
```
## List of related functions [#list-of-related-functions]
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the number of elements in an array.
* [array\_index\_of](/apl/scalar-functions/array-functions/array-index-of): Finds the index of an element in an array.
* [array\_rotate\_left](/apl/scalar-functions/array-functions/array-rotate-left): Rotates elements of an array to the left.
## Other query languages [#other-query-languages]
In APL, the `array_rotate_right` function provides functionality similar to the use of `mvindex` or specific SPL commands for reordering arrays. The rotation here shifts all elements by a set count to the right, maintaining their original order within the new positions.
```sql Splunk example
| eval rotated_array=mvindex(array, -3)
```
```kusto APL equivalent
| extend rotated_array = array_rotate_right(array, 3)
```
ANSI SQL lacks a direct function for rotating elements within arrays. In APL, the `array_rotate_right` function offers a straightforward way to accomplish this by specifying a rotation count, while SQL users typically require a more complex use of `CASE` statements or custom functions to achieve the same.
```sql SQL example
-- No direct ANSI SQL equivalent for array rotation
```
```kusto APL equivalent
| extend rotated_array = array_rotate_right(array_column, 3)
```
---
# array_select_dict
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-select-dict
The `array_select_dict` function in APL allows you to retrieve a dictionary from an array of dictionaries based on a specified key-value pair. This function is useful when you need to filter arrays and extract specific dictionaries for further processing. If no match exists, it returns `null`. Non-dictionary values in the input array are ignored.
## Usage [#usage]
### Syntax [#syntax]
```kusto
array_select_dict(array, key, value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | ------- | ------------------------------------- |
| array | dynamic | Input array of dictionaries. |
| key | string | Key to match in each dictionary. |
| value | scalar | Value to match for the specified key. |
### Returns [#returns]
The function returns the first dictionary in the array that matches the specified key-value pair. If no match exists, it returns `null`. Non-dictionary elements in the array are ignored.
## Use case example [#use-case-example]
This example demonstrates how to use `array_select_dict` to extract a dictionary where the key `service.name` has the value `frontend`.
**Query**
```kusto
['sample-http-logs']
| extend array = dynamic([{"service.name": "frontend", "status_code": "200"}, {"service.name": "backend", "status_code": "500"}])
| project selected = array_select_dict(array, "service.name", "frontend")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20array%20%3D%20dynamic\(%5B%7B'service.name'%3A%20'frontend'%2C%20'status_code'%3A%20'200'%7D%2C%20%7B'service.name'%3A%20'backend'%2C%20'status_code'%3A%20'500'%7D%5D\)%20%7C%20project%20selected%20%3D%20array_select_dict\(array%2C%20'service.name'%2C%20'frontend'\)%22%7D)
**Output**
`{"service.name": "frontend", "status_code": "200"}`
This query selects the first dictionary in the array where `service.name` equals `frontend` and returns it.
## List of related functions [#list-of-related-functions]
* [array\_index\_of](/apl/scalar-functions/array-functions/array-index-of): Finds the index of an element in an array.
* [array\_concat](/apl/scalar-functions/array-functions/array-concat): Combines multiple arrays.
* [array\_rotate\_right](/apl/scalar-functions/array-functions/array-rotate-right): Rotates array elements to the right by a specified number of positions.
## Other query languages [#other-query-languages]
The `array_select_dict` function in APL is similar to filtering objects in an array based on conditions in Splunk SPL. However, unlike Splunk, where filtering often applies directly to JSON structures, `array_select_dict` specifically targets arrays of dictionaries.
```sql Splunk example
| eval selected = mvfilter(array, 'key' == 5)
```
```kusto APL equivalent
| project selected = array_select_dict(array, "key", 5)
```
In ANSI SQL, filtering typically involves table rows rather than nested arrays. The APL `array_select_dict` function applies a similar concept to array elements, allowing you to extract dictionaries from arrays using a condition.
```sql SQL example
SELECT *
FROM my_table
WHERE JSON_CONTAINS(array_column, '{"key": 5}')
```
```kusto APL equivalent
| project selected = array_select_dict(array_column, "key", 5)
```
---
# array_shift_left
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-shift-left
The `array_shift_left` function in APL rotates the elements of an array to the left by a specified number of positions. If the shift exceeds the array length, it wraps around and continues from the beginning. This function is useful when you need to realign or reorder elements for pattern analysis, comparisons, or other array transformations.
For example, you can use `array_shift_left` to:
* Align time-series data for comparative analysis.
* Rotate log entries for cyclic pattern detection.
* Reorganize multi-dimensional datasets in your queries.
## Usage [#usage]
### Syntax [#syntax]
```kusto
['array_shift_left'](array, shift_amount)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| -------------- | ------- | ------------------------------------------------------ |
| `array` | Array | The array to shift. |
| `shift_amount` | Integer | The number of positions to shift elements to the left. |
### Returns [#returns]
An array with elements shifted to the left by the specified `shift_amount`. The function wraps the excess elements to the start of the array.
## Use case example [#use-case-example]
Reorganize span events to analyze dependencies in a different sequence.
**Query**
```kusto
['otel-demo-traces']
| take 50
| extend shifted_events = array_shift_left(events, 1)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20take%2050%20%7C%20extend%20shifted_events%20%3D%20array_shift_left\(events%2C%201\)%22%7D)
**Output**
```json events
[
{
"name": "Enqueued",
"timestamp": 1734001111273917000,
"attributes": null
},
{
"attributes": null,
"name": "Sent",
"timestamp": 1734001111273925400
},
{
"name": "ResponseReceived",
"timestamp": 1734001111274167300,
"attributes": null
}
]
```
```json shifted_events
[
{
"attributes": null,
"name": "Sent",
"timestamp": 1734001111273925400
},
{
"name": "ResponseReceived",
"timestamp": 1734001111274167300,
"attributes": null
},
null
]
```
This query shifts span events for `frontend` services to analyze the adjusted sequence.
## List of related functions [#list-of-related-functions]
* [array\_rotate\_right](/apl/scalar-functions/array-functions/array-rotate-right): Rotates array elements to the right by a specified number of positions.
* [array\_rotate\_left](/apl/scalar-functions/array-functions/array-rotate-left): Rotates elements of an array to the left.
* [array\_shift\_right](/apl/scalar-functions/array-functions/array-shift-right): Shifts array elements to the right.
## Other query languages [#other-query-languages]
In Splunk SPL, there is no direct equivalent to `array_shift_left`, but you can achieve similar results using custom code or by manipulating arrays manually. In APL, `array_shift_left` simplifies this operation by providing a built-in, efficient implementation.
```sql Splunk example
| eval rotated_array = mvindex(array, 1) . mvindex(array, 0)
```
```kusto APL equivalent
['array_shift_left'](array, 1)
```
ANSI SQL doesn’t have a native function equivalent to `array_shift_left`. Typically, you would use procedural SQL to write custom logic for this transformation. In APL, the `array_shift_left` function provides an elegant, concise solution.
```sql SQL example
-- Pseudo code in SQL
SELECT ARRAY_SHIFT_LEFT(array_column, shift_amount)
```
```kusto APL equivalent
['array_shift_left'](array_column, shift_amount)
```
---
# array_shift_right
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-shift-right
The `array_shift_right` function in Axiom Processing Language (APL) shifts the elements of an array one position to the right. The last element of the array wraps around and becomes the first element. You can use this function to reorder elements, manage time-series data in circular arrays, or preprocess arrays for specific analytical needs.
### When to use the function [#when-to-use-the-function]
* To manage and rotate data within arrays.
* To implement cyclic operations or transformations.
* To manipulate array data structures in log analysis or telemetry contexts.
## Usage [#usage]
### Syntax [#syntax]
```kusto
array_shift_right(array: array) : array
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ----- | ------------------------------------------ |
| `array` | array | The input array whose elements are shifted |
### Returns [#returns]
An array with its elements shifted one position to the right. The last element of the input array wraps around to the first position.
## Use case example [#use-case-example]
Reorganize span events in telemetry data for visualization or debugging.
**Query**
```kusto
['otel-demo-traces']
| take 50
| extend shifted_events = array_shift_right(events, 1)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20take%2050%20%7C%20extend%20shifted_events%20%3D%20array_shift_right\(events%2C%201\)%22%7D)
**Output**
```json events
[
{
"name": "Enqueued",
"timestamp": 1734001215487927300,
"attributes": null
},
{
"attributes": null,
"name": "Sent",
"timestamp": 1734001215487937000
},
{
"timestamp": 1734001215488191000,
"attributes": null,
"name": "ResponseReceived"
}
]
```
```json shifted_events
[
null,
{
"timestamp": 1734001215487927300,
"attributes": null,
"name": "Enqueued"
},
{
"attributes": null,
"name": "Sent",
"timestamp": 1734001215487937000
}
]
```
The query rotates span events for better trace debugging.
## List of related functions [#list-of-related-functions]
* [array\_rotate\_right](/apl/scalar-functions/array-functions/array-rotate-right): Rotates array elements to the right by a specified number of positions.
* [array\_rotate\_left](/apl/scalar-functions/array-functions/array-rotate-left): Rotates elements of an array to the left.
* [array\_shift\_left](/apl/scalar-functions/array-functions/array-shift-left): Shifts array elements one position to the left, moving the first element to the last position.
## Other query languages [#other-query-languages]
In Splunk SPL, similar functionality might be achieved using custom code to rotate array elements, as there is no direct equivalent to `array_shift_right`. APL provides this functionality natively, making it easier to work with arrays directly.
```sql Splunk example
| eval shifted_array=mvappend(mvindex(array,-1),mvindex(array,0,len(array)-1))
```
```kusto APL equivalent
['dataset.name']
| extend shifted_array = array_shift_right(array)
```
ANSI SQL doesn’t have a built-in function for shifting arrays. In SQL, achieving this would involve user-defined functions or complex subqueries. In APL, `array_shift_right` simplifies this operation significantly.
```sql SQL example
WITH shifted AS (
SELECT
array_column[ARRAY_LENGTH(array_column)] AS first_element,
array_column[1:ARRAY_LENGTH(array_column)-1] AS rest_of_elements
FROM table
)
SELECT ARRAY_APPEND(first_element, rest_of_elements) AS shifted_array
FROM shifted
```
```kusto APL equivalent
['dataset.name']
| extend shifted_array = array_shift_right(array)
```
---
# array_slice
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-slice
The `array_slice` function in APL extracts a subset of elements from an array, based on specified start and end indices. This function is useful when you want to analyze or transform a portion of data within arrays, such as trimming logs, filtering specific events, or working with trace data in OpenTelemetry logs.
## Usage [#usage]
### Syntax [#syntax]
```kusto
array_slice(array, start, end)
```
### Parameters [#parameters]
| Parameter | Description |
| --------- | ------------------------------------------------------------------------------------------------- |
| `array` | The input array to slice. |
| `start` | The starting index of the slice (inclusive). If negative, it’s counted from the end of the array. |
| `end` | The ending index of the slice (exclusive). If negative, it’s counted from the end of the array. |
### Returns [#returns]
An array containing the elements from the specified slice. If the indices are out of bounds, it adjusts to return valid elements without error.
## Use case example [#use-case-example]
Filter spans from trace data to analyze a specific range of events.
**Query**
```kusto
['otel-demo-traces']
| where array_length(events) > 4
| extend sliced_events = array_slice(events, -3, -1)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20array_length\(events\)%20%3E%204%20%7C%20extend%20sliced_events%20%3D%20array_slice\(events%2C%20-3%2C%20-1\)%22%7D)
**Output**
```json events
[
{
"timestamp": 1734001336443987200,
"attributes": null,
"name": "prepared"
},
{
"attributes": {
"feature_flag.provider_name": "flagd",
"feature_flag.variant": "off",
"feature_flag.key": "paymentServiceUnreachable"
},
"name": "feature_flag",
"timestamp": 1734001336444001800
},
{
"name": "charged",
"timestamp": 1734001336445970200,
"attributes": {
"custom": {
"app.payment.transaction.id": "49567406-21f4-41aa-bab2-69911c055753"
}
}
},
{
"name": "shipped",
"timestamp": 1734001336446488600,
"attributes": {
"custom": {
"app.shipping.tracking.id": "9a3b7a5c-aa41-4033-917f-50cb7360a2a4"
}
}
},
{
"attributes": {
"feature_flag.variant": "off",
"feature_flag.key": "kafkaQueueProblems",
"feature_flag.provider_name": "flagd"
},
"name": "feature_flag",
"timestamp": 1734001336461096700
}
]
```
```json sliced_events
[
{
"name": "charged",
"timestamp": 1734001336445970200,
"attributes": {
"custom": {
"app.payment.transaction.id": "49567406-21f4-41aa-bab2-69911c055753"
}
}
},
{
"name": "shipped",
"timestamp": 1734001336446488600,
"attributes": {
"custom": {
"app.shipping.tracking.id": "9a3b7a5c-aa41-4033-917f-50cb7360a2a4"
}
}
}
]
```
Slices the last three events from the `events` array, excluding the final one.
## List of related functions [#list-of-related-functions]
* [array\_concat](/apl/scalar-functions/array-functions/array-concat): Combines multiple arrays.
* [array\_reverse](/apl/scalar-functions/array-functions/array-reverse): Reverses the order of array elements.
* [array\_shift\_right](/apl/scalar-functions/array-functions/array-shift-right): Shifts array elements to the right.
## Other query languages [#other-query-languages]
In Splunk SPL, you can use `mvindex` to extract elements from an array. APL's `array_slice` is similar but more expressive, allowing you to specify slices with optional bounds.
```sql Splunk example
| eval sliced_array=mvindex(my_array, 1, 3)
```
```kusto APL equivalent
T | extend sliced_array = array_slice(my_array, 1, 3)
```
In ANSI SQL, arrays are often handled using JSON functions or window functions, requiring workarounds to slice arrays. In APL, `array_slice` directly handles arrays, making operations more concise.
```sql SQL example
SELECT JSON_EXTRACT(my_array, '$[1:3]') AS sliced_array FROM my_table
```
```kusto APL equivalent
T | extend sliced_array = array_slice(my_array, 1, 3)
```
---
# array_sort_asc
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-sort-asc
Use the `array_sort_asc` function to return a new array that contains all the elements of the input array, sorted in ascending order. This function is useful when you want to normalize the order of array elements for comparison, presentation, or further processing—such as identifying patterns, comparing sequences, or selecting boundary values.
You can apply `array_sort_asc` to arrays of numbers, strings, or dynamic objects, making it useful across many telemetry, logging, and security data scenarios.
## Usage [#usage]
### Syntax [#syntax]
```kusto
array_sort_asc(array)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------- | -------- | ------------------------------------------------------------------------- |
| array | dynamic | ✓ | An array of values to sort. Can be numbers, strings, or other primitives. |
### Returns [#returns]
A new array that contains the same elements as the input array, sorted in ascending order. If the input isn't an array, the function returns an empty array.
## Example [#example]
**Query**
```kusto
['sample-http-logs']
| project sort = array_sort_asc(dynamic(['x', 'a', 'm', 'o', 'i']))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20project%20sort%20%3D%20array_sort_asc\(dynamic\(%5B'x'%2C%20'a'%2C%20'm'%2C%20'o'%2C%20'i'%5D\)\)%22%7D)
**Output**
```json
[
[
"a",
"i",
"m",
"o",
"x"
]
]
```
## List of related functions [#list-of-related-functions]
* [array\_index\_of](/apl/scalar-functions/array-functions/array-index-of): Returns the position of an element in an array. Use after sorting to find where values fall.
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the number of elements in an array. Use to understand array size before or after sorting.
* [array\_slice](/apl/scalar-functions/array-functions/array-slice): Returns a subrange of the array. Useful after sorting to get top-N or bottom-N elements.
* [array\_sort\_desc](/apl/scalar-functions/array-functions/array-sort-desc): Sorts array elements in descending order. Use when you need reverse ordering.
## Other query languages [#other-query-languages]
In Splunk SPL, arrays are typically handled through `mvsort`, which sorts multivalue fields in ascending order. In APL, `array_sort_asc` provides similar functionality but works on dynamic arrays and returns a new sorted array.
```sql Splunk example
| eval sorted_values = mvsort(multivalue_field)
```
```kusto APL equivalent
datatable(arr: dynamic)
[
dynamic([4, 2, 5])
]
| extend sorted_arr = array_sort_asc(arr)
```
ANSI SQL does not directly support array data types or array sorting. You typically normalize arrays with `UNNEST` and sort the results using `ORDER BY`. In APL, you can sort arrays inline using `array_sort_asc`, which is more concise and expressive.
```sql SQL example
SELECT val
FROM UNNEST([4, 2, 5]) AS val
ORDER BY val ASC
```
```kusto APL equivalent
print sorted_arr = array_sort_asc(dynamic([4, 2, 5]))
```
---
# array_sort_desc
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-sort-desc
Use the `array_sort_desc` function in APL to sort the elements of an array in descending order. This function is especially useful when working with numerical data or categorical data where you want to prioritize higher values first—such as showing the longest durations, highest response times, or most severe error codes at the top of an array.
You can use `array_sort_desc` in scenarios where ordering matters within grouped aggregations, such as collecting response times per user or span durations per trace, and then sorting them to identify the highest or most impactful values.
## Usage [#usage]
### Syntax [#syntax]
```kusto
array_sort_desc(array)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ----- | -------- | ----------------------------------------------------- |
| array | array | ✓ | The input array whose elements are sorted descending. |
### Returns [#returns]
If the input is a valid array, the function returns a new array with its elements sorted in descending order. If the array is empty or contains incompatible types, it returns an empty array.
## Example [#example]
**Query**
```kusto
['sample-http-logs']
| project sort = array_sort_desc(dynamic(['x', 'a', 'm', 'o', 'i']))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20project%20sort%20%3D%20array_sort_desc\(dynamic\(%5B'x'%2C%20'a'%2C%20'm'%2C%20'o'%2C%20'i'%5D\)\)%22%7D)
**Output**
```json
[
[
"x",
"o",
"m",
"i",
"a"
]
]
```
## List of related functions [#list-of-related-functions]
* [array\_index\_of](/apl/scalar-functions/array-functions/array-index-of): Returns the index of a value in an array. Useful after sorting to locate specific elements.
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the number of elements in an array. Useful for measuring the size of arrays before or after sorting.
* [array\_slice](/apl/scalar-functions/array-functions/array-slice): Extracts a range of elements from an array. Use it after sorting to get the top N or bottom N values.
* [array\_sort\_asc](/apl/scalar-functions/array-functions/array-sort-asc): Sorts an array in ascending order. Use this when you want to prioritize smaller values first.
## Other query languages [#other-query-languages]
Splunk doesn’t have a direct equivalent to `array_sort_desc`, but similar outcomes can be achieved using `mvsort` with a custom sort order (and sometimes `reverse`). In APL, `array_sort_desc` explicitly performs a descending sort on array elements, making it more straightforward.
```sql Splunk example
... | stats list(duration) as durations by id
... | eval durations=reverse(mvsort(durations))
```
```kusto APL equivalent
['otel-demo-traces']
| summarize durations=make_list(duration) by trace_id
| extend durations=array_sort_desc(durations)
```
ANSI SQL does not support arrays or array functions natively. You typically use window functions or subqueries to order values. In APL, you can work with arrays directly and apply `array_sort_desc` to sort them.
```sql SQL example
SELECT trace_id, ARRAY_AGG(duration ORDER BY duration DESC) AS durations
FROM traces
GROUP BY trace_id;
```
```kusto APL equivalent
['otel-demo-traces']
| summarize durations=make_list(duration) by trace_id
| extend durations=array_sort_desc(durations)
```
---
# array_split
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-split
The `array_split` function in APL splits an array into smaller subarrays based on specified split indices and packs the generated subarrays into a dynamic array. This function is useful when you want to partition data for analysis, batch processing, or distributing workloads across smaller units.
You can use `array_split` to:
* Divide large datasets into manageable chunks for processing.
* Create segments for detailed analysis or visualization.
* Handle nested data structures for targeted processing.
## Usage [#usage]
### Syntax [#syntax]
```kusto
array_split(array, index)
```
### Parameters [#parameters]
| Parameter | Description | Type |
| --------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| `array` | The array to split. | Dynamic |
| `index` | An integer or dynamic array of integers. These zero-based split indices indicate the location at which to split the array. | Integer or Dynamic |
### Returns [#returns]
Returns a dynamic array containing N+1 arrays where N is the number of input indices. The original array is split at the input indices.
## Use case examples [#use-case-examples]
### Single split index [#single-split-index]
Split large event arrays into manageable chunks for analysis.
```kusto
['otel-demo-traces']
| where array_length(events) == 3
| extend split_events = array_split(events, 2)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20array_length\(events\)%20%3D%3D%203%20%7C%20extend%20span_chunks%20%3D%20array_split\(events%2C%202\)%22%7D)
**Output**
```json events
[
{
"timestamp": 1734033733465219300,
"name": "Enqueued"
},
{
"name": "Sent",
"timestamp": 1734033733465228500
},
{
"timestamp": 1734033733465455900,
"name": "ResponseReceived"
}
]
```
```json split_events
[
[
{
"timestamp": 1734033733465219300,
"name": "Enqueued"
},
{
"name": "Sent",
"timestamp": 1734033733465228500
}
],
[
{
"timestamp": 1734033733465455900,
"name": "ResponseReceived"
}
]
]
```
This query splits the `events` array at index `2` into two subarrays for further processing.
### Multiple split indeces [#multiple-split-indeces]
Divide traces into fixed-size segments for better debugging.
**Query**
```kusto
['otel-demo-traces']
| where array_length(events) == 3
| extend split_events = array_split(events, dynamic([1,2]))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20array_length\(events\)%20%3D%3D%203%20%7C%20extend%20span_chunks%20%3D%20array_split\(events%2C%20dynamic\(%5B1%2C2%5D\)\)%22%7D)
**Output**
```json events
[
{
"attributes": null,
"name": "Enqueued",
"timestamp": 1734034755085206000
},
{
"name": "Sent",
"timestamp": 1734034755085215500,
"attributes": null
},
{
"attributes": null,
"name": "ResponseReceived",
"timestamp": 1734034755085424000
}
]
```
```json split_events
[
[
{
"timestamp": 1734034755085206000,
"attributes": null,
"name": "Enqueued"
}
],
[
{
"timestamp": 1734034755085215500,
"attributes": null,
"name": "Sent"
}
],
[
{
"attributes": null,
"name": "ResponseReceived",
"timestamp": 1734034755085424000
}
]
]
```
This query splits the `events` array into three subarrays based on the indices `[1,2]`.
## List of related functions [#list-of-related-functions]
* [array\_index\_of](/apl/scalar-functions/array-functions/array-index-of): Finds the index of an element in an array.
* [array\_rotate\_right](/apl/scalar-functions/array-functions/array-rotate-right): Rotates array elements to the right by a specified number of positions.
* [array\_shift\_left](/apl/scalar-functions/array-functions/array-shift-left): Shifts array elements one position to the left, moving the first element to the last position.
## Other query languages [#other-query-languages]
In Splunk SPL, array manipulation is achieved through functions like `mvzip` and `mvfilter`, but there is no direct equivalent to `array_split`. APL provides a more explicit approach for splitting arrays.
```sql Splunk example
| eval split_array = mvzip(array_field, "2")
```
```kusto APL equivalent
['otel-demo-traces']
| extend split_array = array_split(events, 2)
```
ANSI SQL doesn’t have built-in functions for directly splitting arrays. APL provides this capability natively, making it easier to handle array operations within queries.
```sql SQL example
-- SQL typically requires custom functions or JSON manipulation.
SELECT * FROM dataset WHERE JSON_ARRAY_LENGTH(array_field) > 0;
```
```kusto APL equivalent
['otel-demo-traces']
| extend split_array = array_split(events, 2)
```
---
# array_sum
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-sum
The `array_sum` function in APL computes the sum of all numerical elements in an array. This function is particularly useful when you want to aggregate numerical values stored in an array field, such as durations, counts, or measurements, across events or records. Use `array_sum` when your dataset includes array-type fields, and you need to quickly compute their total.
## Usage [#usage]
### Syntax [#syntax]
```kusto
array_sum(array_expression)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| ------------------ | ----- | ------------------------------------------ |
| `array_expression` | array | An array of numerical values to be summed. |
### Returns [#returns]
The function returns the sum of all numerical values in the array. If the array is empty or contains no numerical values, the result is `null`.
## Use case example [#use-case-example]
Summing the duration of all events in an array field.
**Query**
```kusto
['otel-demo-traces']
| summarize event_duration = make_list(duration) by ['service.name']
| extend total_event_duration = array_sum(event_duration)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20event_duration%20%3D%20make_list\(duration\)%20by%20%5B'service.name'%5D%20%7C%20extend%20total_event_duration%20%3D%20array_sum\(event_duration\)%22%7D)
**Output**
| service.name | total\_event\_duration |
| --------------- | ---------------------- |
| frontend | 1667269530000 |
| checkoutservice | 3801404276900 |
The query calculates the total duration of all events for each service.
## List of related functions [#list-of-related-functions]
* [array\_rotate\_right](/apl/scalar-functions/array-functions/array-rotate-right): Rotates array elements to the right by a specified number of positions.
* [array\_reverse](/apl/scalar-functions/array-functions/array-reverse): Reverses the order of array elements.
* [array\_shift\_left](/apl/scalar-functions/array-functions/array-shift-left): Shifts array elements one position to the left, moving the first element to the last position.
## Other query languages [#other-query-languages]
In Splunk SPL, you might need to use commands or functions such as `mvsum` for similar operations. In APL, `array_sum` provides a direct method to compute the sum of numerical arrays.
```sql Splunk example
| eval total_duration = mvsum(duration_array)
```
```kusto APL equivalent
['dataset.name']
| extend total_duration = array_sum(duration_array)
```
ANSI SQL doesn’t natively support array operations like summing array elements. However, you can achieve similar results with `UNNEST` and `SUM`. In APL, `array_sum` simplifies this by handling array summation directly.
```sql SQL example
SELECT SUM(value) AS total_duration
FROM UNNEST(duration_array) AS value;
```
```kusto APL equivalent
['dataset.name']
| extend total_duration = array_sum(duration_array)
```
---
# bag_has_key
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/bag-has-key
Use the `bag_has_key` function in APL to check whether a dynamic property bag contains a specific key. This is helpful when your data includes semi-structured or nested fields encoded as dynamic objects, such as JSON-formatted logs or telemetry metadata.
You often encounter property bags in observability data where log entries, spans, or alerts carry key–value metadata. Use `bag_has_key` to filter, conditionally process, or join such records based on the existence of specific keys, without needing to extract the values themselves.
## Usage [#usage]
### Syntax [#syntax]
```kusto
bag_has_key(bag: dynamic, key: string)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | --------- | ----------------------------------------------------------------------- |
| `bag` | `dynamic` | A dynamic value representing a property bag (for example, JSON object). |
| `key` | `string` | The key to check for within the property bag. |
### Returns [#returns]
Returns a `bool` value:
* `true` if the specified key exists in the property bag
* `false` otherwise
## Use case examples [#use-case-examples]
Use `bag_has_key` to filter log entries that include a specific metadata key embedded in a dynamic object.
**Query**
```kusto
['sample-http-logs']
| extend metadata = bag_pack('source', 'cdn', 'env', 'prod')
| where bag_has_key(metadata, 'env')
| project _time, id, method, uri, status, metadata
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20metadata%20%3D%20bag_pack%28%27source%27%2C%20%27cdn%27%2C%20%27env%27%2C%20%27prod%27%29%20%7C%20where%20bag_has_key%28metadata%2C%20%27env%27%29%20%7C%20project%20_time%2C%20id%2C%20method%2C%20uri%2C%20status%2C%20metadata%22%7D)
**Output**
| \_time | id | method | uri | status | metadata |
| ----------------- | ---- | ------ | -------------- | ------ | ------------------------------ |
| 2025-05-27T12:30Z | u123 | GET | /login | 200 | \{'source':'cdn','env':'prod'} |
| 2025-05-27T12:31Z | u124 | POST | /cart/checkout | 500 | \{'source':'cdn','env':'prod'} |
The query filters logs where the synthetic `metadata` bag includes the key `'env'`.
Use `bag_has_key` to filter spans that include specific dynamic span attributes.
**Query**
```kusto
['otel-demo-traces']
| extend attributes = bag_pack('user', 'alice', 'feature_flag', 'beta')
| where bag_has_key(attributes, 'feature_flag')
| project _time, trace_id, span_id, ['service.name'], kind, attributes
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20attributes%20%3D%20bag_pack%28%27user%27%2C%20%27alice%27%2C%20%27feature_flag%27%2C%20%27beta%27%29%20%7C%20where%20bag_has_key%28attributes%2C%20%27feature_flag%27%29%20%7C%20project%20_time%2C%20trace_id%2C%20span_id%2C%20%5B%27service.name%27%5D%2C%20kind%2C%20attributes%22%7D)
**Output**
| \_time | trace\_id | span\_id | \['service.name'] | kind | attributes |
| ----------------- | --------- | -------- | ----------------- | ------ | ---------------------------------------- |
| 2025-05-27T10:02Z | abc123 | span567 | frontend | client | \{'user':'alice','feature\_flag':'beta'} |
The query selects spans with dynamic `attributes` bags containing the `'feature_flag'` key.
Use `bag_has_key` to identify HTTP logs where the request metadata contains sensitive audit-related keys.
**Query**
```kusto
['sample-http-logs']
| extend audit_info = bag_pack('action', 'delete', 'reason', 'admin_override')
| where bag_has_key(audit_info, 'reason')
| project _time, id, uri, status, audit_info
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20audit_info%20%3D%20bag_pack%28%27action%27%2C%20%27delete%27%2C%20%27reason%27%2C%20%27admin_override%27%29%20%7C%20where%20bag_has_key%28audit_info%2C%20%27reason%27%29%20%7C%20project%20_time%2C%20id%2C%20uri%2C%20status%2C%20audit_info%22%7D)
**Output**
| \_time | id | uri | status | audit\_info |
| ----------------- | ---- | ------------- | ------ | ----------------------------------------------- |
| 2025-05-27T13:45Z | u999 | /admin/delete | 403 | \{'action':'delete','reason':'admin\_override'} |
The query returns only logs where the `audit_info` bag includes the `'reason'` key, indicating administrative override events.
## List of related functions [#list-of-related-functions]
* [bag\_keys](/apl/scalar-functions/array-functions/bag-keys): Returns all keys in a dynamic property bag. Use it when you need to enumerate available keys.
* [bag\_pack](/apl/scalar-functions/array-functions/bag-pack): Converts a list of key-value pairs to a dynamic property bag. Use when you need to build a bag.
## Other query languages [#other-query-languages]
In Splunk SPL, you often check whether a key exists in a JSON object using `spath` and conditional logic. APL simplifies this with `bag_has_key`, which returns a boolean directly and avoids explicit parsing.
```sql Splunk example
| eval hasKey=if(isnull(spath(data, "keyName")), false, true)
```
```kusto APL equivalent
['sample-http-logs']
| where bag_has_key(dynamic_field, 'keyName')
```
ANSI SQL doesn’t include native support for property bags or dynamic fields. You typically use JSON functions to access keys in JSON-formatted strings. In APL, dynamic fields are first-class, and `bag_has_key` provides direct support for key existence checks.
```sql SQL example
SELECT *
FROM logs
WHERE JSON_EXTRACT(json_column, '$.keyName') IS NOT NULL
```
```kusto APL equivalent
['sample-http-logs']
| where bag_has_key(dynamic_field, 'keyName')
```
---
# bag_keys
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/bag-keys
Use the `bag_keys` function in APL to extract the keys of a dynamic (bag) object as an array of strings. This is useful when you want to inspect or manipulate the structure of a dynamic field—such as JSON-like nested objects—without needing to know its exact schema in advance.
Use `bag_keys` when you’re working with semi-structured data and want to:
* Discover what properties are present in a dynamic object.
* Iterate over the keys programmatically using other array functions.
* Perform validation or debugging tasks to ensure all expected keys exist.
This function is especially helpful in log analytics, observability pipelines, and security auditing, where dynamic properties are often collected from various services or devices.
## Usage [#usage]
### Syntax [#syntax]
```kusto
bag_keys(bag)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | --------- | -------------------------------------------------- |
| `bag` | `dynamic` | The dynamic object whose keys you want to extract. |
### Returns [#returns]
An array of type `string[]` containing the names of the keys in the dynamic object. If the input isn’t a dynamic object, the function returns `null`.
## Use case examples [#use-case-examples]
Use `bag_keys` to audit dynamic metadata fields in HTTP logs where each record contains a nested object representing additional request attributes.
**Query**
```kusto
['sample-http-logs']
| extend metadata = dynamic({ 'os': 'Windows', 'browser': 'Firefox', 'device': 'Desktop' })
| extend key_list = bag_keys(metadata)
| project _time, uri, metadata, key_list
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20metadata%20%3D%20dynamic\(%7B%20'os'%3A%20'Windows'%2C%20'browser'%3A%20'Firefox'%2C%20'device'%3A%20'Desktop'%20%7D\)%20%7C%20extend%20key_list%20%3D%20bag_keys\(metadata\)%20%7C%20project%20_time%2C%20uri%2C%20metadata%2C%20key_list%22%7D)
**Output**
| \_time | uri | metadata | key\_list |
| ------------------- | ------ | ------------------------------------------------- | ---------------------------- |
| 2025-05-26 12:01:23 | /login | \{os: Windows, browser: Firefox, device: Desktop} | \[‘os’, ‘browser’, ‘device’] |
This query inspects a simulated metadata object and returns the list of its keys, helping you debug inconsistencies or missing fields.
Use `bag_keys` to examine custom span attributes encoded as dynamic fields within OpenTelemetry trace events.
**Query**
```kusto
['otel-demo-traces']
| extend attributes = dynamic({ 'user_id': 'abc123', 'feature_flag': 'enabled' })
| extend attribute_keys = bag_keys(attributes)
| project _time, ['service.name'], kind, attributes, attribute_keys
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20attributes%20%3D%20dynamic\(%7B%20'user_id'%3A%20'abc123'%2C%20'feature_flag'%3A%20'enabled'%20%7D\)%20%7C%20extend%20attribute_keys%20%3D%20bag_keys\(attributes\)%20%7C%20project%20_time%2C%20%5B'service.name'%5D%2C%20kind%2C%20attributes%2C%20attribute_keys%22%7D)
**Output**
| \_time | \['service.name'] | kind | attributes | attribute\_keys |
| ------------------- | ----------------- | ------ | ------------------------------------------- | ------------------------------ |
| 2025-05-26 13:14:01 | frontend | client | \{user\_id: abc123, feature\_flag: enabled} | \[‘user\_id’, ‘feature\_flag’] |
This query inspects the custom span-level attributes and extracts their keys to verify attribute coverage or completeness.
Use `bag_keys` to list all security-related fields captured dynamically during request monitoring for auditing or compliance.
**Query**
```kusto
['sample-http-logs']
| extend security_context = dynamic({ 'auth_status': 'success', 'role': 'admin', 'ip': '192.168.1.5' })
| extend fields = bag_keys(security_context)
| project _time, status, ['geo.country'], security_context, fields
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20security_context%20%3D%20dynamic\(%7B%20'auth_status'%3A%20'success'%2C%20'role'%3A%20'admin'%2C%20'ip'%3A%20'192.168.1.5'%20%7D\)%20%7C%20extend%20fields%20%3D%20bag_keys\(security_context\)%20%7C%20project%20_time%2C%20status%2C%20%5B'geo.country'%5D%2C%20security_context%2C%20fields%22%7D)
**Output**
| \_time | status | \['geo.country'] | security\_context | fields |
| ------------------- | ------ | ---------------- | ------------------------------------------------------ | ------------------------------- |
| 2025-05-26 15:32:10 | 200 | US | \{auth\_status: success, role: admin, ip: 192.168.1.5} | \[‘auth\_status’, ‘role’, ‘ip’] |
This helps you audit security metadata in requests and ensure key fields are present across records.
## List of related functions [#list-of-related-functions]
* [bag\_pack](/apl/scalar-functions/array-functions/bag-pack): Converts a list of key-value pairs to a dynamic property bag. Use when you need to build a bag.
* [bag\_has\_key](/apl/scalar-functions/array-functions/bag-has-key): Checks whether a dynamic property bag contains a specific key.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically interact with JSON-like fields using the `spath` command or use `keys(_raw)` to retrieve field names. In APL, `bag_keys` serves a similar purpose by returning an array of keys from a dynamic object.
```sql Splunk example
| eval key_list=keys(data_field)
```
```kusto APL equivalent
datatable(data: dynamic)
[
dynamic({ "ip": "127.0.0.1", "status": "200", "method": "GET" })
]
| extend keys = bag_keys(data)
```
ANSI SQL doesn’t have native support for dynamic objects or JSON key introspection in the same way. However, some SQL dialects (like PostgreSQL or BigQuery) provide JSON-specific functions for extracting keys. `bag_keys` is the APL equivalent for dynamically introspecting JSON objects.
```sql SQL example
SELECT JSON_OBJECT_KEYS(data) FROM logs;
```
```kusto APL equivalent
datatable(data: dynamic)
[
dynamic({ "ip": "127.0.0.1", "status": "200", "method": "GET" })
]
| extend keys = bag_keys(data)
```
---
# bag_pack
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/bag-pack
Use the `bag_pack` function in APL to construct a dynamic property bag from a list of key-value pairs. A property bag is a flexible data structure where keys are strings and values are dynamic types. This function is useful when you want to combine multiple values into a single dynamic object, often to simplify downstream processing or export.
You typically use `bag_pack` in projection scenarios to consolidate structured data—for example, packing related request metadata into one field, or grouping trace data by contextual attributes. This makes it easier to output, filter, or transform nested information.
The `pack` and `bag_pack` functions are equivalent in APL.
A common use is `bag_pack(*)` that gets all fields of your dataset as a bag. The wildcard `*` is useful to match all fields in the current row, but it increases query complexity and decreases stability and performance.
## Usage [#usage]
### Syntax [#syntax]
```kusto
bag_pack(key1, value1, key2, value2, ...)
```
### Parameters [#parameters]
| Name | Type | Description |
| --------------------- | -------- | ------------------------------------------------------------------------ |
| `key1, key2, ...` | `string` | The names of the fields to include in the property bag. |
| `value1, value2, ...` | `scalar` | The corresponding values for the keys. Values can be of any scalar type. |
The number of keys must equal the number of values. Keys must be string literals or string expressions.
### Returns [#returns]
A `dynamic` value representing a property bag (dictionary) where keys are strings and values are the corresponding values.
## Use case examples [#use-case-examples]
Use `bag_pack` to create a structured object that captures key request attributes for easier inspection or export.
**Query**
```kusto
['sample-http-logs']
| where status == '500'
| project _time, error_context = bag_pack('uri', uri, 'method', method, 'duration_ms', req_duration_ms)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20%3D%3D%20'500'%20%7C%20project%20_time%2C%20error_context%20%3D%20bag_pack%28'uri'%2C%20uri%2C%20'method'%2C%20method%2C%20'duration_ms'%2C%20req_duration_ms%29%22%7D)
**Output**
| \_time | error\_context |
| -------------------- | -------------------------------------------------------------- |
| 2025-05-27T10:00:00Z | `{ "uri": "/api/data", "method": "GET", "duration_ms": 342 }` |
| 2025-05-27T10:05:00Z | `{ "uri": "/api/auth", "method": "POST", "duration_ms": 879 }` |
The query filters HTTP logs to 500 errors and consolidates key request fields into a single dynamic column named `error_context`.
Use `bag_pack` to enrich trace summaries with service metadata for each span.
**Query**
```kusto
['otel-demo-traces']
| where ['service.name'] == 'checkout'
| project trace_id, span_id, span_info = bag_pack('kind', kind, 'duration', duration, 'status_code', status_code)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20%5B'service.name'%5D%20%3D%3D%20'checkout'%20%7C%20project%20trace_id%2C%20span_id%2C%20span_info%20%3D%20bag_pack%28'kind'%2C%20kind%2C%20'duration'%2C%20duration%2C%20'status_code'%2C%20status_code%29%22%7D)
**Output**
| trace\_id | span\_id | span\_info |
| --------- | -------- | ------------------------------------------------------------------------------ |
| a1b2... | f9c3... | `{ "kind": "server", "duration": "00:00:00.1240000", "status_code": "OK" }` |
| c3d4... | h7e2... | `{ "kind": "client", "duration": "00:00:00.0470000", "status_code": "ERROR" }` |
The query targets spans from the `checkout` and combines attributes into a single object per span.
Use `bag_pack` to create a compact event summary combining user ID and geographic info for anomaly detection.
**Query**
```kusto
['sample-http-logs']
| project _time, id, geo_summary = bag_pack('city', ['geo.city'], 'country', ['geo.country'])
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20project%20_time%2C%20id%2C%20geo_summary%20%3D%20bag_pack\('city'%2C%20%5B'geo.city'%5D%2C%20'country'%2C%20%5B'geo.country'%5D\)%22%7D)
**Output**
| \_time | id | geo\_summary |
| -------------------- | -------- | --------------------------------------- |
| 2025-05-27T12:00:00Z | user\_01 | `{ "city": "Berlin", "country": "DE" }` |
| 2025-05-27T12:01:00Z | user\_02 | `{ "city": "Paris", "country": "FR" }` |
The query helps identify patterns in failed access attempts by summarizing location data per event.
## List of related functions [#list-of-related-functions]
* [bag\_keys](/apl/scalar-functions/array-functions/bag-keys): Returns all keys in a dynamic property bag. Use it when you need to enumerate available keys.
* [bag\_has\_key](/apl/scalar-functions/array-functions/bag-has-key): Checks whether a dynamic property bag contains a specific key.
## Other query languages [#other-query-languages]
In Splunk, you can use `mvzip` and `eval` to create key-value mappings, or use `spath` to interpret JSON data. However, packing data into a true key-value structure for export or downstream use requires JSON manipulation. APL’s `bag_pack` provides a native and type-safe way to do this.
```sql Splunk example
| eval metadata=tojson({"status": status, "duration": req_duration_ms})
```
```kusto APL equivalent
project metadata = bag_pack('status', status, 'duration', req_duration_ms)
```
SQL doesn’t have a direct built-in function like `bag_pack`. To achieve similar behavior, you typically construct JSON objects using functions like `JSON_OBJECT` or use user-defined types. In APL, `bag_pack` is the idiomatic way to construct dynamic objects with labeled fields.
```sql SQL example
SELECT JSON_OBJECT('status' VALUE status, 'duration' VALUE req_duration_ms) AS metadata FROM logs;
```
```kusto APL equivalent
project metadata = bag_pack('status', status, 'duration', req_duration_ms)
```
---
# bag_zip
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/bag-zip
Use the `bag_zip` function in APL to combine two arrays—one containing keys and another containing values—into a single dynamic property bag (dictionary). This is useful when you have parallel arrays that you want to merge into a structured key-value object for easier manipulation or output.
You typically use `bag_zip` when parsing data that arrives as separate lists of keys and values, or when transforming array-based structures into more readable dictionary formats. This function is especially helpful in log analysis, data transformation pipelines, and when preparing data for downstream systems that expect key-value pairs.
## Usage [#usage]
### Syntax [#syntax]
```kusto
bag_zip(keys, values)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | --------- | ------------------------------------------------------------------------- |
| `keys` | `dynamic` | An array of strings representing the keys for the resulting property bag. |
| `values` | `dynamic` | An array of values corresponding to the keys. Can contain any data type. |
### Returns [#returns]
A `dynamic` property bag (dictionary) where each key from the `keys` array is paired with the corresponding value from the `values` array. If the arrays have different lengths, the function pairs elements up to the length of the shorter array and ignores any extra elements.
## Use case examples [#use-case-examples]
Use `bag_zip` to combine metadata keys and values extracted from HTTP logs into structured objects for easier analysis.
**Query**
```kusto
['sample-http-logs']
| extend metadata_keys = dynamic(['status_code', 'request_method', 'city'])
| extend metadata_values = pack_array(status, method, ['geo.city'])
| extend request_metadata = bag_zip(metadata_keys, metadata_values)
| project _time, uri, request_metadata
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20metadata_keys%20%3D%20dynamic\(%5B'status_code'%2C%20'request_method'%2C%20'city'%5D\)%20%7C%20extend%20metadata_values%20%3D%20pack_array\(status%2C%20method%2C%20%5B'geo.city'%5D\)%20%7C%20extend%20request_metadata%20%3D%20bag_zip\(metadata_keys%2C%20metadata_values\)%20%7C%20project%20_time%2C%20uri%2C%20request_metadata%20%7C%20take%205%22%7D)
**Output**
| \_time | uri | request\_metadata |
| ------------------- | --------- | ----------------------------------------------------------- |
| 2025-05-26 10:15:30 | /api/user | \{status\_code: 200, request\_method: GET, city: Seattle} |
| 2025-05-26 10:16:45 | /api/data | \{status\_code: 404, request\_method: POST, city: Portland} |
This query creates structured metadata objects by zipping together field names and their corresponding values, making the data easier to export or process downstream.
Use `bag_zip` to construct custom span attributes from separate attribute name and value arrays in OpenTelemetry traces.
**Query**
```kusto
['otel-demo-traces']
| extend attr_keys = dynamic(['service', 'span_kind', 'status'])
| extend attr_values = pack_array(['service.name'], kind, status_code)
| extend span_attributes = bag_zip(attr_keys, attr_values)
| project _time, span_id, trace_id, span_attributes
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20attr_keys%20%3D%20dynamic\(%5B'service'%2C%20'span_kind'%2C%20'status'%5D\)%20%7C%20extend%20attr_values%20%3D%20pack_array\(%5B'service.name'%5D%2C%20kind%2C%20status_code\)%20%7C%20extend%20span_attributes%20%3D%20bag_zip\(attr_keys%2C%20attr_values\)%20%7C%20project%20_time%2C%20span_id%2C%20trace_id%2C%20span_attributes%20%7C%20take%205%22%7D)
**Output**
| \_time | span\_id | trace\_id | span\_attributes |
| ------------------- | ------------ | ------------ | ---------------------------------------------------- |
| 2025-05-26 11:20:15 | a1b2c3d4e5f6 | xyz123abc456 | \{service: frontend, span\_kind: server, status: OK} |
| 2025-05-26 11:21:30 | f6e5d4c3b2a1 | def789ghi012 | \{service: cart, span\_kind: client, status: OK} |
This query consolidates span-level metadata into structured attribute dictionaries, simplifying trace analysis and visualization.
Use `bag_zip` to create structured security context objects from arrays of security-related field names and values.
**Query**
```kusto
['sample-http-logs']
| extend security_keys = dynamic(['client_ip', 'country', 'http_status'])
| extend security_values = pack_array(id, ['geo.country'], status)
| extend security_context = bag_zip(security_keys, security_values)
| where status != '200'
| project _time, uri, method, security_context
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20security_keys%20%3D%20dynamic\(%5B'client_ip'%2C%20'country'%2C%20'http_status'%5D\)%20%7C%20extend%20security_values%20%3D%20pack_array\(id%2C%20%5B'geo.country'%5D%2C%20status\)%20%7C%20extend%20security_context%20%3D%20bag_zip\(security_keys%2C%20security_values\)%20%7C%20where%20status%20!%3D%20'200'%20%7C%20project%20_time%2C%20uri%2C%20method%2C%20security_context%20%7C%20take%205%22%7D)
**Output**
| \_time | uri | method | security\_context |
| ------------------- | ------------ | ------ | ------------------------------------------------------ |
| 2025-05-26 12:30:00 | /admin/panel | POST | \{client\_ip: user123, country: CN, http\_status: 403} |
| 2025-05-26 12:31:15 | /api/delete | DELETE | \{client\_ip: user456, country: RU, http\_status: 401} |
This query creates structured security context for failed requests, making it easier to analyze security incidents and audit access patterns.
## List of related functions [#list-of-related-functions]
* [bag\_pack](/apl/scalar-functions/array-functions/bag-pack): Use `bag_pack` when you have key-value pairs as separate arguments rather than arrays. Use `bag_zip` when working with parallel arrays.
* [bag\_keys](/apl/scalar-functions/array-functions/bag-keys): Use `bag_keys` to extract all keys from an existing property bag. Use `bag_zip` to create a new property bag from separate key and value arrays.
* [pack\_dictionary](/apl/scalar-functions/array-functions/pack-dictionary): Similar to `bag_zip`, but `pack_dictionary` takes alternating key-value arguments. Use `bag_zip` for array-based inputs.
* [todynamic](/apl/scalar-functions/conversion-functions/todynamic): Use `todynamic` to parse JSON strings into dynamic objects. Use `bag_zip` to construct dynamic objects programmatically from arrays.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `mvzip` to combine two multi-value fields into paired elements. APL's `bag_zip` goes further by creating a true dictionary object from separate key and value arrays.
```sql Splunk example
| eval zipped=mvzip(keys, values, '=')
```
```kusto APL equivalent
['sample-http-logs']
| extend keys = dynamic(['status', 'method', 'city'])
| extend values = dynamic(['200', 'GET', 'Seattle'])
| extend result = bag_zip(keys, values)
```
ANSI SQL doesn't have native support for combining arrays into key-value dictionaries. You typically need to use JSON functions or complex `CASE` statements to achieve similar results. APL's `bag_zip` provides a direct and type-safe way to perform this operation.
```sql SQL example
SELECT JSON_OBJECT('key1', value1, 'key2', value2)
FROM table_name
```
```kusto APL equivalent
['sample-http-logs']
| extend keys = dynamic(['key1', 'key2'])
| extend values = dynamic([value1, value2])
| extend result = bag_zip(keys, values)
```
---
# isarray
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/isarray
The `isarray` function in APL checks whether a specified value is an array. Use this function to validate input data, handle dynamic schemas, or filter for records where a field is explicitly an array. It’s particularly useful when working with data that contains fields with mixed data types or optional nested arrays.
## Usage [#usage]
### Syntax [#syntax]
```kusto
isarray(value)
```
### Parameters [#parameters]
| Parameter | Description |
| --------- | ------------------------------------ |
| `value` | The value to check if it’s an array. |
### Returns [#returns]
A boolean value:
* `true` if the specified value is an array.
* `false` otherwise.
## Use case example [#use-case-example]
Filter for records where the `events` field contains an array.
**Query**
```kusto
['otel-demo-traces']
| take 50
| summarize events_array = make_list(events)
| extend is_array = isarray(events_array)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20take%2050%20%7C%20summarize%20events_array%20%3D%20make_list\(events\)%20%7C%20extend%20is_array%20%3D%20isarray\(events_array\)%22%7D)
**Output**
| is\_array |
| --------- |
| true |
## List of related functions [#list-of-related-functions]
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the number of elements in an array.
* [array\_index\_of](/apl/scalar-functions/array-functions/array-index-of): Finds the index of an element in an array.
* [array\_slice](/apl/scalar-functions/array-functions/array-slice): Extracts a subset of elements from an array.
## Other query languages [#other-query-languages]
In Splunk SPL, similar functionality is achieved by analyzing the data structure manually, as SPL doesn’t have a direct equivalent to `isarray`. APL simplifies this task by providing the `isarray` function to directly evaluate whether a value is an array.
```sql Splunk example
| eval is_array=if(isnotnull(mvcount(field)), "true", "false")
```
```kusto APL equivalent
['dataset.name']
| extend is_array=isarray(field)
```
In ANSI SQL, there is no built-in function for directly checking if a value is an array. You might need to rely on JSON functions or structural parsing. APL provides the `isarray` function as a more straightforward solution.
```sql SQL example
SELECT CASE
WHEN JSON_TYPE(field) = 'ARRAY' THEN TRUE
ELSE FALSE
END AS is_array
FROM dataset_name;
```
```kusto APL equivalent
['dataset.name']
| extend is_array=isarray(field)
```
---
# len
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/len
Use the `len` function in APL (Axiom Processing Language) to determine the length of a string or the number of elements in an array. This function is useful when you want to filter, sort, or analyze data based on the size of a value—whether that’s the number of characters in a request URL or the number of cities associated with a user.
Use `len` when you need to:
* Measure string lengths (for example, long request URIs).
* Count elements in dynamic arrays (such as tags or multi-value fields).
* Create conditional expressions based on the length of values.
## Usage [#usage]
### Syntax [#syntax]
```kusto
len(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | --------------- | ------------------------------------------------- |
| value | string or array | The input to measure—either a string or an array. |
### Returns [#returns]
* If `value` is a string, returns the number of characters.
* If `value` is an array, returns the number of elements.
* Returns `null` if the input is `null`.
## Use case examples [#use-case-examples]
Use `len` to find requests with long URIs, which might indicate poorly designed endpoints or potential abuse.
**Query**
```kusto
['sample-http-logs']
| extend uri_length = len(uri)
| where uri_length > 100
| project _time, id, uri, uri_length
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20uri_length%20%3D%20len\(uri\)%20%7C%20where%20uri_length%20%3E%20100%20%7C%20project%20_time%2C%20id%2C%20uri%2C%20uri_length%22%7D)
**Output**
| \_time | id | uri | uri\_length |
| -------------------- | ------- | --------------------------------- | ----------- |
| 2025-06-18T12:34:00Z | user123 | /api/products/search?query=... | 132 |
| 2025-06-18T12:35:00Z | user456 | /download/file/very/long/path/... | 141 |
The query filters logs for URIs longer than 100 characters and displays their lengths.
Use `len` to identify traces with IDs of unexpected length, which might indicate instrumentation issues or data inconsistencies.
**Query**
```kusto
['otel-demo-traces']
| extend trace_id_length = len(trace_id)
| summarize count() by trace_id_length
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%3Dmake_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20sorted%3Dsort_array\(durations%2C%20'desc'\)%20%7C%20extend%20top_3%3Darray_extract\(sorted%2C%200%2C%203\)%22%7D)
**Output**
| trace\_id\_length | count |
| ----------------- | ----- |
| 32 | 4987 |
| 16 | 12 |
The query summarizes trace IDs by their lengths to find unexpected values.
Use `len` to analyze request methods and flag unusually short ones (e.g., malformed logs or attack vectors).
**Query**
```kusto
['sample-http-logs']
| extend method_length = len(method)
| where method_length < 3
| project _time, id, method, method_length
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20method_length%20%3D%20len\(method\)%20%7C%20where%20method_length%20%3C%203%20%7C%20project%20_time%2C%20id%2C%20method%2C%20method_length%22%7D)
**Output**
| \_time | id | method | method\_length |
| -------------------- | ------- | ------ | -------------- |
| 2025-06-18T13:10:00Z | user789 | P | 1 |
| 2025-06-18T13:12:00Z | user222 | G | 1 |
The query finds suspicious or malformed request methods that are unusually short.
## List of related functions [#list-of-related-functions]
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the number of elements in an array. Use this when working specifically with arrays.
* [array\_slice](/apl/scalar-functions/array-functions/array-slice): Returns a subarray like `array_extract`, but supports negative indexing.
* [array\_concat](/apl/scalar-functions/array-functions/array-concat): Joins arrays end-to-end. Use before or after slicing arrays with `array_extract`.
## Other query languages [#other-query-languages]
In Splunk SPL, you often use the `len` function within `eval` or `where` expressions to determine string length or array size. In APL, `len` works similarly, but is used as a standalone scalar function.
```sql Splunk example
... | eval uri_length=len(uri)
```
```kusto APL equivalent
['sample-http-logs']
| extend uri_length = len(uri)
```
In ANSI SQL, you use `LENGTH()` for strings and `CARDINALITY()` for arrays. In APL, `len` handles both cases—string and array—depending on the input type.
```sql SQL example
SELECT LENGTH(uri) AS uri_length FROM http_logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend uri_length = len(uri)
```
---
# pack_array
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/pack-array
The `pack_array` function in APL creates an array from individual values or expressions. You can use this function to group related data into a single field, which can simplify handling and querying of data collections. It’s especially useful when working with nested data structures or aggregating data into arrays for further processing.
## Usage [#usage]
### Syntax [#syntax]
```kusto
pack_array(value1, value2, ..., valueN)
```
### Parameters [#parameters]
| Parameter | Description |
| --------- | ------------------------------------------ |
| `value1` | The first value to include in the array. |
| `value2` | The second value to include in the array. |
| `...` | Additional values to include in the array. |
| `valueN` | The last value to include in the array. |
The wildcard `*` in `pack_array(*)` is useful to pack all values from the current row into an array, but it increases query complexity and decreases stability and performance.
`pack_array(*)` packs only the values, not the field names. For key-value pairs, use `bag_pack(*)` instead.
### Returns [#returns]
An array containing the specified values in the order they're provided.
## Use case example [#use-case-example]
Use `pack_array` to consolidate span data into an array for a trace summary.
**Query**
```kusto
['otel-demo-traces']
| extend span_summary = pack_array(['service.name'], kind, duration)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20span_summary%20%3D%20pack_array\(%5B'service.name'%5D%2C%20kind%2C%20duration\)%22%7D)
**Output**
| service.name | kind | duration | span\_summary |
| ------------ | ------ | -------- | --------------------------------- |
| frontend | server | `123ms` | `["frontend", "server", "123ms"]` |
This query creates a concise representation of span details.
## List of related functions [#list-of-related-functions]
* [array\_slice](/apl/scalar-functions/array-functions/array-slice): Extracts a subset of elements from an array.
* [array\_concat](/apl/scalar-functions/array-functions/array-concat): Combines multiple arrays.
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the number of elements in an array.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use functions like `mvappend` to create multi-value fields. In APL, the `pack_array` function serves a similar purpose by combining values into an array.
```sql Splunk example
| eval array_field = mvappend(value1, value2, value3)
```
```kusto APL equivalent
| extend array_field = pack_array(value1, value2, value3)
```
In ANSI SQL, arrays are often constructed using functions like `ARRAY`. The `pack_array` function in APL performs a similar operation, creating an array from specified values.
```sql SQL example
SELECT ARRAY[value1, value2, value3] AS array_field;
```
```kusto APL equivalent
| extend array_field = pack_array(value1, value2, value3)
```
---
# pack_dictionary
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/pack-dictionary
Use the `pack_dictionary` function in APL to construct a dynamic property bag (dictionary) from a list of keys and values. The resulting dictionary maps each specified key to its corresponding value and allows you to store key-value pairs in a single column for downstream operations like serialization, custom grouping, or structured export.
`pack_dictionary` is especially useful when you want to:
* Create flexible data structures for export or transformation.
* Group dynamic sets of key-value metrics or attributes into a single column.
* Combine multiple scalar fields into a single dictionary for post-processing or output.
## Usage [#usage]
### Syntax [#syntax]
```kusto
pack_dictionary(key1, value1, key2, value2, ...)
```
### Parameters [#parameters]
| Name | Type | Description |
| ------ | -------- | ------------------------------------------------------- |
| keyN | `string` | A constant string that represents a dictionary key. |
| valueN | `scalar` | A scalar value to associate with the corresponding key. |
* The number of arguments must be even.
* Keys must be constant strings.
* Values can be any scalar type.
### Returns [#returns]
A dynamic object that represents a dictionary where each key maps to its associated value.
## Use case examples [#use-case-examples]
Use `pack_dictionary` to store request metadata in a compact format for structured inspection or export.
**Query**
```kusto
['sample-http-logs']
| extend request_info = pack_dictionary(
'method', method,
'uri', uri,
'status', status,
'duration', req_duration_ms
)
| project _time, id, request_info
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20request_info%20%3D%20pack_dictionary\(%20'method'%2C%20method%2C%20'uri'%2C%20uri%2C%20'status'%2C%20status%2C%20'duration'%2C%20req_duration_ms%20\)%20%7C%20project%20_time%2C%20id%2C%20request_info%22%7D)
**Output**
| \_time | id | request\_info |
| -------------------- | ------ | ---------------------------------------------------------------------- |
| 2025-06-18T14:35:00Z | user42 | `{ "method": "GET", "uri": "/home", "status": "200", "duration": 82 }` |
This example creates a single `request_info` column that contains key HTTP request data as a dictionary, simplifying downstream analysis or visualization.
Use `pack_dictionary` to consolidate trace metadata into a structured format for export or debugging.
**Query**
```kusto
['otel-demo-traces']
| extend trace_metadata = pack_dictionary(
'trace_id', trace_id,
'span_id', span_id,
'service', ['service.name'],
'kind', kind,
'status_code', status_code
)
| project _time, duration, trace_metadata
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20trace_metadata%20%3D%20pack_dictionary\(%20'trace_id'%2C%20trace_id%2C%20'span_id'%2C%20span_id%2C%20'service'%2C%20%5B'service.name'%5D%2C%20'kind'%2C%20kind%2C%20'status_code'%2C%20status_code%20\)%20%7C%20project%20_time%2C%20duration%2C%20trace_metadata%22%7D)
**Output**
| \_time | duration | trace\_metadata |
| -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| 2025-06-18T14:40:00Z | 00:00:01 | `{ "trace_id": "abc123", "span_id": "def456", "service": "checkoutservice", "kind": "server", "status_code": "OK" }` |
This query generates a `trace_metadata` column that organizes important trace identifiers and status into a single dynamic field.
Use `pack_dictionary` to package request metadata along with geographic information for audit logging or incident forensics.
**Query**
```kusto
['sample-http-logs']
| extend geo_info = pack_dictionary(
'city', ['geo.city'],
'country', ['geo.country']
)
| extend request_info = pack_dictionary(
'method', method,
'uri', uri,
'status', status,
'geo', geo_info
)
| project _time, id, request_info
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20geo_info%20%3D%20pack_dictionary\(%20'city'%2C%20%5B'geo.city'%5D%2C%20'country'%2C%20%5B'geo.country'%5D%20\)%20%7C%20extend%20request_info%20%3D%20pack_dictionary\(%20'method'%2C%20method%2C%20'uri'%2C%20uri%2C%20'status'%2C%20status%2C%20'geo'%2C%20geo_info%20\)%20%7C%20project%20_time%2C%20id%2C%20request_info%22%7D)
**Output**
| \_time | id | request\_info |
| -------------------- | ------ | ------------------------------------------------------------------------------------------------------ |
| 2025-06-18T14:20:00Z | user88 | `{ "method": "POST", "uri": "/login", "status": "403", "geo": { "city": "Berlin", "country": "DE" } }` |
This example nests geographic context inside the main dictionary to create a structured log suitable for security investigations.
## List of related functions [#list-of-related-functions]
* [pack\_array](/apl/scalar-functions/array-functions/pack-array): Use this to combine scalar values into an array. Use `pack_array` when you don’t need named keys and want positional data instead.
* [bag\_keys](/apl/scalar-functions/array-functions/bag-keys): Returns the list of keys in a dynamic dictionary. Use this to inspect or filter contents created by `pack_dictionary`.
* [bag\_pack](/apl/scalar-functions/array-functions/bag-pack): Expands a dictionary into multiple columns. Use it to revert the packing performed by `pack_dictionary`.
## Other query languages [#other-query-languages]
While SPL doesn’t have a direct equivalent of `pack_dictionary`, you can simulate similar behavior using the `eval` command and `mvzip` or `mvmap` to construct composite objects. In APL, `pack_dictionary` is a simpler and more declarative way to produce key-value structures inline.
```sql Splunk example
| eval dict=mvmap("key1", value1, "key2", value2)
```
```kusto APL equivalent
| extend dict = pack_dictionary('key1', value1, 'key2', value2)
```
ANSI SQL lacks built-in support for dynamic dictionaries. You typically achieve similar functionality by manually assembling JSON strings or using vendor-specific extensions (like PostgreSQL’s `jsonb_build_object`). In contrast, APL provides a native and type-safe way to construct dictionaries using `pack_dictionary`.
```sql SQL example
SELECT '{"key1":' || value1 || ',"key2":' || value2 || '}' AS dict FROM my_table;
```
```kusto APL equivalent
| extend dict = pack_dictionary('key1', value1, 'key2', value2)
```
---
# strcat_array
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/strcat-array
The `strcat_array` function in Axiom Processing Language (APL) allows you to concatenate the elements of an array into a single string, with an optional delimiter separating each element. This function is useful when you need to transform a set of values into a readable or exportable format, such as combining multiple log entries, tracing IDs, or security alerts into a single output for further analysis or reporting.
## Usage [#usage]
### Syntax [#syntax]
```kusto
strcat_array(array, delimiter)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| ----------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `array` | dynamic | The array of values to concatenate. |
| `delimiter` | string | The string used to separate each element in the concatenated result. Optional. Defaults to an empty string if not specified. |
### Returns [#returns]
A single concatenated string with the array’s elements separated by the specified delimiter.
## Use case example [#use-case-example]
You can use `strcat_array` to combine HTTP methods and URLs for a quick summary of unique request paths.
**Query**
```kusto
['sample-http-logs']
| take 50
| extend combined_requests = strcat_delim(' ', method, uri)
| summarize requests_list = make_list(combined_requests)
| extend paths = strcat_array(requests_list, ', ')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20take%2050%20%7C%20extend%20combined_requests%20%3D%20strcat_delim\('%20'%2C%20method%2C%20uri\)%20%7C%20summarize%20requests_list%20%3D%20make_list\(combined_requests\)%20%7C%20extend%20paths%20%3D%20strcat_array\(requests_list%2C%20'%2C%20'\)%22%7D)
**Output**
| paths |
| ------------------------------------ |
| GET /index, POST /submit, GET /about |
This query summarizes unique HTTP method and URL combinations into a single, readable string.
## List of related functions [#list-of-related-functions]
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the number of elements in an array.
* [array\_index\_of](/apl/scalar-functions/array-functions/array-index-of): Finds the index of an element in an array.
* [array\_concat](/apl/scalar-functions/array-functions/array-concat): Combines multiple arrays.
## Other query languages [#other-query-languages]
In Splunk SPL, concatenation typically involves transforming fields into a string using the `eval` command with the `+` operator or `mvjoin()` for arrays. In APL, `strcat_array` simplifies array concatenation by natively supporting array input with a delimiter.
```sql Splunk example
| eval concatenated=mvjoin(array_field, ", ")
```
```kusto APL equivalent
dataset
| extend concatenated = strcat_array(array_field, ', ')
```
In ANSI SQL, concatenation involves functions like `STRING_AGG()` or manual string building using `CONCAT()`. APL’s `strcat_array` is similar to `STRING_AGG()`, but focuses on array input directly with a customizable delimiter.
```sql SQL example
SELECT STRING_AGG(column_name, ', ') AS concatenated FROM table;
```
```kusto APL equivalent
dataset
| summarize concatenated = strcat_array(column_name, ', ')
```
---
# dynamic_to_json
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/dynamic-to-json
Use the `dynamic_to_json` function to convert a dynamic-typed value—such as a property bag, array, or nested JSON structure—into a canonical JSON string representation. This is helpful when you want to serialize dynamic data for storage, transmission, or further string manipulation.
You typically use `dynamic_to_json` when working with semi-structured data that you need to convert to a string format, especially when exporting data or passing dynamic values to functions that expect string input.
## Usage [#usage]
### Syntax [#syntax]
```kusto
dynamic_to_json(dynamic)
```
### Parameters [#parameters]
| Name | Type | Description |
| ------- | ------- | ---------------------------------------------- |
| dynamic | dynamic | The dynamic value to convert to a JSON string. |
### Returns [#returns]
Returns a canonical JSON string representation of the input according to the following rules:
* If the input is a scalar value of type other than `dynamic`, the output is the result of applying `tostring()` to that value.
* If the input is an array of values, the output is composed of the characters `[`, `,`, and `]` interspersed with the canonical representation of each array element.
* If the input is a property bag, the output is composed of the characters `{`, `,`, and `}` interspersed with colon (`:`)-delimited name/value pairs of the properties. The pairs are sorted by the names, and the values are in the canonical representation described here.
## Use case examples [#use-case-examples]
Convert a dynamic object containing request metadata to a JSON string for logging or export purposes.
**Query**
```kusto
['sample-http-logs']
| extend metadata = bag_pack('method', method, 'status', status, 'duration', req_duration_ms)
| extend json_metadata = dynamic_to_json(metadata)
| project _time, ['uri'], json_metadata
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20metadata%20%3D%20bag_pack\('method'%2C%20method%2C%20'status'%2C%20status%2C%20'duration'%2C%20req_duration_ms\)%20%7C%20extend%20json_metadata%20%3D%20dynamic_to_json\(metadata\)%20%7C%20project%20_time%2C%20%5B'uri'%5D%2C%20json_metadata%22%7D)
**Output**
| \_time | uri | json\_metadata |
| ---------------- | ---------- | ----------------------------------------------- |
| Jun 24, 09:28:10 | /api/users | \{"duration":150,"method":"GET","status":"200"} |
This example creates a dynamic object from log fields and converts it to a JSON string, which you can use for exporting structured data or passing to string manipulation functions.
Serialize trace attributes stored as dynamic values into JSON strings for analysis or export.
**Query**
```kusto
['otel-demo-traces']
| extend trace_attributes = bag_pack('service', ['service.name'], 'kind', ['kind'], 'status', ['status_code'])
| extend json_attributes = dynamic_to_json(trace_attributes)
| project _time, ['trace_id'], json_attributes
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20trace_attributes%20%3D%20bag_pack\('service'%2C%20%5B'service.name'%5D%2C%20'kind'%2C%20%5B'kind'%5D%2C%20'status'%2C%20%5B'status_code'%5D\)%20%7C%20extend%20json_attributes%20%3D%20dynamic_to_json\(trace_attributes\)%20%7C%20project%20_time%2C%20%5B'trace_id'%5D%2C%20json_attributes%22%7D)
**Output**
| \_time | trace\_id | json\_attributes |
| ---------------- | --------- | ------------------------------------------------------ |
| Jun 24, 09:28:10 | abc123 | \{"kind":"server","service":"frontend","status":"200"} |
This example converts trace attributes from dynamic format to JSON strings, making it easier to export or analyze trace metadata as structured text.
Convert dynamic security event data to JSON strings for reporting or integration with external systems.
**Query**
```kusto
['sample-http-logs']
| extend security_event = bag_pack('timestamp', _time, 'status', ['status'], 'uri', ['uri'], 'location', strcat(['geo.city'], ', ', ['geo.country']))
| extend event_json = dynamic_to_json(security_event)
| project _time, event_json
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20security_event%20%3D%20bag_pack\('timestamp'%2C%20_time%2C%20'status'%2C%20%5B'status'%5D%2C%20'uri'%2C%20%5B'uri'%5D%2C%20'location'%2C%20strcat\(%5B'geo.city'%5D%2C%20'%2C%20'%2C%20%5B'geo.country'%5D\)\)%20%7C%20extend%20event_json%20%3D%20dynamic_to_json\(security_event\)%20%7C%20project%20_time%2C%20event_json%22%7D)
**Output**
| \_time | event\_json |
| ---------------- | --------------------------------------------------------------------------------------------- |
| Jun 24, 09:28:10 | \{"location":"New York, US","status":"403","timestamp":"2024-06-24T09:28:10Z","uri":"/admin"} |
This example creates a structured security event as a dynamic object and converts it to JSON, which you can use for alerting systems or security information and event management (SIEM) integrations.
## List of related functions [#list-of-related-functions]
* [todynamic](/apl/scalar-functions/conversion-functions/todynamic): Converts a JSON string to a dynamic value. Use `todynamic` to parse JSON strings, and `dynamic_to_json` to serialize dynamic values back to strings.
* [tostring](/apl/scalar-functions/conversion-functions/tostring): Converts any scalar value to a string. Use `tostring` for simple scalar conversions, and `dynamic_to_json` when you need canonical JSON formatting for dynamic values.
* [parse\_json](/apl/scalar-functions/string-functions/parse-json): Parses a JSON string into a dynamic value. Use `parse_json` to create dynamic values from JSON strings, and `dynamic_to_json` to convert them back.
* [toarray](/apl/scalar-functions/conversion-functions/toarray): Converts a dynamic value to an array. Use `toarray` when you need array operations, and `dynamic_to_json` when you need string output.
## Other query languages [#other-query-languages]
In Splunk, you typically use `tojson` or `mvjoin` with JSON formatting to convert structured data to JSON strings. In APL, `dynamic_to_json` provides a similar conversion from dynamic values to canonical JSON strings.
```sql Splunk example
... | eval json_output = tojson({"key": "value"})
```
```kusto APL equivalent
print dynamic_value = bag_pack("key", "value")
| extend json_output = dynamic_to_json(dynamic_value)
```
In standard SQL, you use `JSON_OBJECT` or `JSON_ARRAY` functions to create JSON strings, or `CAST(... AS JSON)` to convert values. In APL, `dynamic_to_json` converts dynamic values (which can be created from JSON strings using `todynamic`) back into canonical JSON string representations.
```sql SQL example
SELECT JSON_OBJECT('key' VALUE 'value') AS json_output FROM dual;
```
```kusto APL equivalent
print dynamic_value = bag_pack("key", "value")
| extend json_output = dynamic_to_json(dynamic_value)
```
---
# ensure_field
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/ensure-field
Use the `ensure_field` function to safely access a field that may or may not exist in your data. The function returns the field’s value if it exists, or a typed nil if it doesn’t. This helps you write queries that work even when fields are missing, making your queries more robust and future-proof.
You typically use `ensure_field` when working with schemaless or evolving data where fields might be absent, or when you want to write queries that handle missing fields gracefully without errors.
## Usage [#usage]
### Syntax [#syntax]
```kusto
ensure_field(field_name, field_type)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----------- | ------ | ------------------------------------------------------------------------------------------------------ |
| field\_name | string | The name of the field to ensure exists. |
| field\_type | type | The type of the field. See [scalar data types](/apl/data-types/scalar-data-types) for supported types. |
### Returns [#returns]
This function returns the value of the specified field if it exists, otherwise it returns a typed nil that matches the specified type.
## Use case examples [#use-case-examples]
Handle missing fields gracefully when analyzing HTTP logs where some fields might not be present in all records.
**Query**
```kusto
['sample-http-logs']
| extend user_agent = ensure_field('user_agent', typeof(string))
| extend referer = ensure_field('referer', typeof(string))
| where isnotnull(user_agent) or isnotnull(referer)
| project _time, ['uri'], user_agent, referer
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20user_agent%20%3D%20ensure_field\('user_agent'%2C%20typeof\(string\)\)%20%7C%20extend%20referer%20%3D%20ensure_field\('referer'%2C%20typeof\(string\)\)%20%7C%20where%20isnotnull\(user_agent\)%20or%20isnotnull\(referer\)%20%7C%20project%20_time%2C%20%5B'uri'%5D%2C%20user_agent%2C%20referer%22%7D)
**Output**
| \_time | uri | user\_agent | referer |
| ---------------- | ---------- | ----------- | ------------------------------------------ |
| Jun 24, 09:28:10 | /api/users | Mozilla/5.0 | [https://example.com](https://example.com) |
This example safely accesses optional fields that may not exist in all log records, allowing the query to run successfully even when some fields are missing.
Access optional trace attributes that might not be present in all spans.
**Query**
```kusto
['otel-demo-traces']
| extend http_method = ensure_field('http.method', typeof(string))
| extend http_path = ensure_field('http.path', typeof(string))
| where ['kind'] == 'server'
| project _time, ['trace_id'], ['service.name'], http_method, http_path
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20http_method%20%3D%20ensure_field\('http.method'%2C%20typeof\(string\)\)%20%7C%20extend%20http_path%20%3D%20ensure_field\('http.path'%2C%20typeof\(string\)\)%20%7C%20where%20%5B'kind'%5D%20%3D%3D%20'server'%20%7C%20project%20_time%2C%20%5B'trace_id'%5D%2C%20%5B'service.name'%5D%2C%20http_method%2C%20http_path%22%7D)
**Output**
| \_time | trace\_id | service.name | http\_method | http\_path |
| ---------------- | --------- | ------------ | ------------ | ---------- |
| Jun 24, 09:28:10 | abc123 | frontend | GET | /api/users |
This example safely accesses optional HTTP attributes in trace data, ensuring the query works even when these attributes are not present in all spans.
## List of related functions [#list-of-related-functions]
* [isnull](/apl/scalar-functions/string-functions/isnull): Checks if a value is null. Use `isnull` to test the result of `ensure_field` to determine if a field exists.
* [isnotnull](/apl/scalar-functions/string-functions/isnotnull): Checks if a value isn't null. Use `isnotnull` to verify that `ensure_field` successfully retrieved a field value.
* [coalesce](/apl/scalar-functions/string-functions/coalesce): Returns the first non-null value from a list. Use `coalesce` with `ensure_field` to provide default values when fields are missing.
## Other query languages [#other-query-languages]
In Splunk, you can use `if` expressions with `isnull` or check field existence with `isnull(field)`. In APL, `ensure_field` provides a more type-safe way to handle missing fields by returning a typed nil that matches the expected field type.
```sql Splunk example
... | eval status = if(isnull(status_code), null(), status_code)
```
```kusto APL equivalent
... | extend status = ensure_field('status_code', typeof(string))
```
In standard SQL, you use `COALESCE` or `ISNULL` functions to handle missing values, but these don't check for field existence. In APL, `ensure_field` checks if a field exists and returns a typed nil if it doesn't, allowing you to write queries that work with optional fields.
```sql SQL example
SELECT COALESCE(optional_field, NULL) AS field_value FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend field_value = ensure_field('optional_field', typeof(string))
```
---
# isbool
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/isbool
Use the `isbool` function to check whether an expression evaluates to a boolean value. This is helpful when you need to validate data types, filter boolean values, or handle type checking in conditional logic.
You typically use `isbool` when working with dynamic or mixed-type data where you need to verify that a value is actually a boolean before performing boolean operations or conversions.
## Usage [#usage]
### Syntax [#syntax]
```kusto
isbool(expression)
```
### Parameters [#parameters]
| Name | Type | Description |
| ---------- | ------- | ----------------------------------------- |
| expression | dynamic | The expression to check for boolean type. |
### Returns [#returns]
Returns `true` if the expression value is a boolean, `false` otherwise.
## Use case examples [#use-case-examples]
Validate that a field contains boolean values before using it in boolean operations or filters.
**Query**
```kusto
['sample-http-logs']
| extend is_cached = case(
['status'] == '200', true,
['status'] == '304', true,
1 == 1, false
)
| where isbool(is_cached)
| extend cache_hit = is_cached == true
| project _time, ['uri'], ['status'], is_cached, cache_hit
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20is_cached%20%3D%20case\(%5B'status'%5D%20%3D%3D%20'200'%2C%20true%2C%20%5B'status'%5D%20%3D%3D%20'304'%2C%20true%2C%201%20%3D%3D%201%2C%20false\)%20%7C%20where%20isbool\(is_cached\)%20%7C%20extend%20cache_hit%20%3D%20is_cached%20%3D%3D%20true%20%7C%20project%20_time%2C%20%5B'uri'%5D%2C%20%5B'status'%5D%2C%20is_cached%2C%20cache_hit%22%7D)
**Output**
| \_time | uri | status | is\_cached | cache\_hit |
| ---------------- | ---------- | ------ | ---------- | ---------- |
| Jun 24, 09:28:10 | /api/users | 200 | true | true |
This example creates a boolean field and validates it using `isbool` before using it in further boolean operations, ensuring type safety in your queries.
Check if trace attributes contain boolean values before performing boolean logic on them.
**Query**
```kusto
['otel-demo-traces']
| extend is_error = ['status_code'] >= '400'
| where isbool(is_error)
| extend error_occurred = is_error == true
| project _time, ['trace_id'], ['service.name'], is_error, error_occurred
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20is_error%20%3D%20%5B'status_code'%5D%20%3E%3D%20'400'%20%7C%20where%20isbool\(is_error\)%20%7C%20extend%20error_occurred%20%3D%20is_error%20%3D%3D%20true%20%7C%20project%20_time%2C%20%5B'trace_id'%5D%2C%20%5B'service.name'%5D%2C%20is_error%2C%20error_occurred%22%7D)
**Output**
| \_time | trace\_id | service.name | is\_error | error\_occurred |
| ---------------- | --------- | ------------ | --------- | --------------- |
| Jun 24, 09:28:10 | abc123 | frontend | false | false |
This example validates that a computed boolean value is actually a boolean type before using it in conditional logic, preventing type-related errors.
Validate boolean flags in security events to ensure they contain proper boolean values before filtering or alerting.
**Query**
```kusto
['sample-http-logs']
| extend is_suspicious = ['status'] == '403' or ['status'] == '401'
| extend is_high_risk = ['req_duration_ms'] > 5000
| where isbool(is_suspicious) and isbool(is_high_risk)
| extend security_alert = is_suspicious == true and is_high_risk == true
| project _time, ['uri'], ['status'], is_suspicious, is_high_risk, security_alert
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20is_suspicious%20%3D%20%5B'status'%5D%20%3D%3D%20'403'%20or%20%5B'status'%5D%20%3D%3D%20'401'%20%7C%20extend%20is_high_risk%20%3D%20%5B'req_duration_ms'%5D%20%3E%205000%20%7C%20where%20isbool\(is_suspicious\)%20and%20isbool\(is_high_risk\)%20%7C%20extend%20security_alert%20%3D%20is_suspicious%20%3D%3D%20true%20and%20is_high_risk%20%3D%3D%20true%20%7C%20project%20_time%2C%20%5B'uri'%5D%2C%20%5B'status'%5D%2C%20is_suspicious%2C%20is_high_risk%2C%20security_alert%22%7D)
**Output**
| \_time | uri | status | is\_suspicious | is\_high\_risk | security\_alert |
| ---------------- | ------ | ------ | -------------- | -------------- | --------------- |
| Jun 24, 09:28:10 | /admin | 403 | true | false | false |
This example validates multiple boolean flags before combining them in security logic, ensuring that only properly typed boolean values are used in alert conditions.
## List of related functions [#list-of-related-functions]
* [tobool](/apl/scalar-functions/conversion-functions/tobool): Converts a value to boolean. Use `tobool` to convert values to boolean, and `isbool` to check if a value is already a boolean.
* [gettype](/apl/scalar-functions/string-functions/gettype): Returns the type of a value as a string. Use `gettype` when you need to check for multiple types, and `isbool` when you only need to check for boolean.
* [isnull](/apl/scalar-functions/string-functions/isnull): Checks if a value is null. Use `isnull` to check for null values, and `isbool` to check for boolean type.
## Other query languages [#other-query-languages]
In Splunk, you can use `isbool()` function or check if a field contains boolean values. In APL, `isbool` provides a direct way to check if an expression is a boolean type.
```sql Splunk example
... | eval is_boolean = if(isbool(field), 1, 0)
```
```kusto APL equivalent
... | extend is_boolean = isbool(field)
```
In standard SQL, you use `CASE` statements with type checking or `IS NULL` checks, but there's no direct boolean type checker. In APL, `isbool` provides a straightforward way to check if a value is a boolean.
```sql SQL example
SELECT CASE WHEN field IN (0, 1, TRUE, FALSE) THEN 1 ELSE 0 END AS is_boolean FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend is_boolean = isbool(field)
```
---
# toarray
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/toarray
Use the `toarray` function in APL to convert a dynamic-typed input—such as a bag, property bag, or JSON array—into a regular array. This is helpful when you want to process the elements individually with array functions like `array_length` or `array_index_of`.
You typically use `toarray` when working with semi-structured data, especially after parsing JSON from log fields or external sources. It lets you access and manipulate nested collections using standard array operations.
## Usage [#usage]
### Syntax [#syntax]
```kusto
toarray(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | ------- | ---------------------------------------- |
| value | dynamic | A JSON array, property bag, or bag value |
### Returns [#returns]
An array containing the elements of the dynamic input. If the input is already an array, the result is identical. If the input is a property bag, it returns an array of values. If the input isn’t coercible to an array, the result is an empty array.
## Example [#example]
You want to convert a string to an array because you want to pass the result to a function that accepts arrays, such as `array_concat`.
**Query**
```kusto
['otel-demo-traces']
| extend service_list = toarray('123')
| extend json_list = parse_json('["frontend", "cartservice", "checkoutservice"]')
| extend combined_list = array_concat(service_list, json_list)
| project _time, combined_list
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20service_list%20%3D%20toarray\('123'\)%20%7C%20extend%20json_list%20%3D%20parse_json\('%5B%5C"frontend%5C"%2C%20%5C"cartservice%5C"%2C%20%5C"checkoutservice%5C"%5D'\)%20%7C%20extend%20combined_list%20%3D%20array_concat\(service_list%2C%20json_list\)%20%7C%20project%20_time%2C%20combined_list%22%7D)
**Output**
| \_time | combined\_list |
| ---------------- | ------------------------------------------------------- |
| Jun 24, 09:28:10 | `["123", "frontend", "cartservice", "checkoutservice"]` |
| Jun 24, 09:28:10 | `["123", "frontend", "cartservice", "checkoutservice"]` |
| Jun 24, 09:28:10 | `["123", "frontend", "cartservice", "checkoutservice"]` |
## List of related functions [#list-of-related-functions]
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the number of elements in an array. Useful before applying `array_extract`.
* [array\_index\_of](/apl/scalar-functions/array-functions/array-index-of): Finds the position of an element in an array, which can help set the `startIndex` for `array_extract`.
* [pack\_array](/apl/scalar-functions/array-functions/pack-array): Use this to combine scalar values into an array. Use `pack_array` when you don’t need named keys and want positional data instead.
* [bag\_keys](/apl/scalar-functions/array-functions/bag-keys): Returns the list of keys in a dynamic dictionary. Use this to inspect or filter contents created by `pack_dictionary`.
* [bag\_pack](/apl/scalar-functions/array-functions/bag-pack): Expands a dictionary into multiple columns. Use it to revert the packing performed by `pack_dictionary`.
## Other query languages [#other-query-languages]
In Splunk, multivalue fields are native, and many SPL commands like `mvexpand`, `mvindex`, and `mvcount` operate directly on them. In APL, dynamic fields can also contain multivalue data, but you need to explicitly convert them to arrays using `toarray` before applying array functions.
```sql Splunk example
... | eval methods=split("GET,POST,PUT", ",") | mvcount(methods)
```
```kusto APL equivalent
print methods = dynamic(["GET", "POST", "PUT"])
| extend method_count = array_length(toarray(methods))
```
ANSI SQL doesn’t support arrays natively. You typically store lists as JSON and use JSON functions to manipulate them. In APL, you can parse JSON into dynamic values and use `toarray` to convert those into arrays for further processing.
```sql SQL example
SELECT JSON_ARRAY_LENGTH('["GET","POST","PUT"]')
```
```kusto APL equivalent
print methods = dynamic(["GET", "POST", "PUT"])
| extend method_count = array_length(toarray(methods))
```
---
# tobool
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/tobool
Use the `tobool` function to convert various data types to a boolean value. This is helpful when you need to normalize values from different sources into boolean format for conditional logic, filtering, or boolean operations.
You typically use `tobool` when working with data that represents boolean values as strings (like "true"/"false" or "1"/"0"), numbers, or other types that need to be converted to proper boolean values.
## Usage [#usage]
### Syntax [#syntax]
```kusto
tobool(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | ------- | -------------------------------- |
| value | dynamic | The value to convert to boolean. |
### Returns [#returns]
If conversion is successful, the result is a boolean. If conversion isn’t successful, the result is `false`.
### Conversion behavior [#conversion-behavior]
The `tobool` function converts values based on their type:
* **Boolean**: Returns the value unchanged.
* **Integer/Float/Duration**: Returns `true` if the value isn't equal to 0, otherwise `false`.
* **Datetime**: Returns `true` if the value is anything other than epoch (zero time), otherwise `false`.
* **String**: Returns `true` if the value equals `"1"`, `"t"`, `"T"`, `"TRUE"`, `"true"`, or `"True"`. Returns `false` if the value equals `"0"`, `"f"`, `"F"`, `"FALSE"`, `"false"`, or `"False"`. Returns `null` for any other string value.
## Example [#example]
Convert string representations of Boolean values to actual Boolean types for filtering and analysis.
**Query**
```kusto
['sample-http-logs']
| extend is_active = tobool(is_active)
| extend is_enabled = tobool(enabled)
| where is_active == true or is_enabled == true
| project _time, uri, status, is_active, enabled, is_active, is_enabled
```
**Output**
| \_time | uri | status | is\_active | enabled | is\_active | is\_enabled |
| ---------------- | ---------- | ------ | ---------- | ------- | ---------- | ----------- |
| Jun 24, 09:28:10 | /api/users | 200 | "true" | "1" | true | true |
This example converts string representations of boolean values (like `"true"` and `"1"`) to actual boolean types, enabling proper boolean logic and filtering.
## List of related functions [#list-of-related-functions]
* [isbool](/apl/scalar-functions/conversion-functions/isbool): Checks if a value is a boolean. Use `isbool` to validate types, and `tobool` to convert values to boolean.
* [case](/apl/scalar-functions/conditional-function#case): Evaluates conditions and returns values. Use `case` for complex conditional logic, and `tobool` for simple conversions.
* [iff](/apl/scalar-functions/conditional-function#iff): Returns one of two values based on a predicate. Use `iff` for conditional assignment, and `tobool` for type conversion.
## Other query languages [#other-query-languages]
In Splunk, you use `if` expressions or `tonumber` with comparisons to convert values to boolean-like results. In APL, `tobool` provides a direct conversion function that handles various input types.
```sql Splunk example
... | eval is_active = if(field == "true" OR field == 1, 1, 0)
```
```kusto APL equivalent
... | extend is_active = tobool(field)
```
In standard SQL, you use `CASE` statements or `CAST` to convert values to boolean. In APL, `tobool` provides a simpler way to convert various types to boolean values.
```sql SQL example
SELECT CASE WHEN status = 'active' THEN TRUE ELSE FALSE END AS is_active FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend is_active = tobool(is_active)
```
---
# todatetime
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/todatetime
Use the `todatetime` function to convert various data types to a datetime value. This is helpful when you need to normalize date and time values from different formats or sources into a standard datetime format for comparison, filtering, or time-based analysis.
You typically use `todatetime` when working with date strings, timestamps, or other time representations that need to be converted to datetime format for time-based operations.
## Usage [#usage]
### Syntax [#syntax]
```kusto
todatetime(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | ------- | --------------------------------- |
| value | dynamic | The value to convert to datetime. |
### Returns [#returns]
If the conversion is successful, the result is a datetime value. If the conversion isn't successful, the result is `null`.
### Conversion behavior [#conversion-behavior]
The `todatetime` function converts values based on their type:
* **Integer/Float**: Assumed to be nanoseconds since epoch.
* **String**: Parsed using the [`dateparse` package](https://github.com/araddon/dateparse), which accepts many common date and time formats. See the [upstream examples](https://raw.githubusercontent.com/araddon/dateparse/master/example/main.go) for supported formats.
## Use case example [#use-case-example]
Convert date strings from log fields to datetime values for time-based filtering and analysis.
**Query**
```kusto
['sample-http-logs']
| extend log_date = todatetime('2024-06-24')
| extend is_recent = _time >= log_date
| where is_recent == true
| project _time, ['uri'], ['status'], log_date
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20log_date%20%3D%20todatetime\('2024-06-24'\)%20%7C%20extend%20is_recent%20%3D%20_time%20%3E%3D%20log_date%20%7C%20where%20is_recent%20%3D%3D%20true%20%7C%20project%20_time%2C%20%5B'uri'%5D%2C%20%5B'status'%5D%2C%20log_date%22%7D)
**Output**
| \_time | uri | status | log\_date |
| ---------------- | ---------- | ------ | -------------------- |
| Jun 24, 09:28:10 | /api/users | 200 | 2024-06-24T00:00:00Z |
This example converts a date string to a datetime value and uses it for time-based comparisons, enabling precise date filtering in your queries.
## List of related functions [#list-of-related-functions]
* [totimespan](/apl/scalar-functions/conversion-functions/totimespan): Converts input to timespan. Use `totimespan` for duration values, and `todatetime` for absolute time points.å
## Other query languages [#other-query-languages]
In Splunk, you use `strptime` or `strftime` functions to parse date strings, or `eval` with time functions. In APL, `todatetime` provides a direct conversion function that handles various date and time formats.
```sql Splunk example
... | eval timestamp = strptime(date_field, "%Y-%m-%d %H:%M:%S")
```
```kusto APL equivalent
... | extend timestamp = todatetime(date_field)
```
In standard SQL, you use `CAST(... AS DATETIME)` or `TO_DATE` functions to convert strings to datetime. In APL, `todatetime` provides a simpler way to convert various types to datetime values.
```sql SQL example
SELECT CAST('2022-11-13' AS DATETIME) AS date_value FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend date_value = todatetime('2022-11-13')
```
---
# todouble, toreal
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/todouble
Use the `todouble` function (or its synonym `toreal`) to convert various data types to a real (floating-point) number. This is helpful when you need to normalize numeric values from different sources into decimal format for mathematical operations, comparisons, or aggregations.
You typically use `todouble` when working with numeric strings, integers, or other types that need to be converted to floating-point numbers for precise calculations or when decimal precision is required.
## Usage [#usage]
### Syntax [#syntax]
```kusto
todouble(value)
toreal(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | ------- | ----------------------------- |
| value | dynamic | The value to convert to real. |
### Returns [#returns]
If conversion is successful, the result is a value of type `real`. If conversion isn't successful, the result is `null`.
### Conversion behavior [#conversion-behavior]
The `todouble` function converts values based on their type:
* **Integer**: Converted to float. For example, `1` becomes `1.0`, `-1` becomes `-1.0`.
* **String**: Parsed as a 64-bit float using [Go floating-point literal syntax](https://go.dev/ref/spec#Floating-point_literals), which supports scientific notation. For example, `"1e3"` becomes `1000.0`.
* **Boolean**: `true` becomes `1.0`, `false` becomes `0.0`
* **Datetime**: Converted to nanoseconds since epoch as a float
* **Duration**: Converted to float nanoseconds
## Use case examples [#use-case-examples]
Convert string representations of numeric values to real numbers for mathematical calculations and aggregations.
**Query**
```kusto
['sample-http-logs']
| extend duration_seconds = todouble(['req_duration_ms']) / 1000.0
| extend is_slow = duration_seconds > 0.001
| where is_slow == true
| project _time, ['uri'], ['req_duration_ms'], duration_seconds, is_slow
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20duration_seconds%20%3D%20todouble\(%5B'req_duration_ms'%5D\)%20/%201000.0%20%7C%20extend%20is_slow%20%3D%20duration_seconds%20%3E%201.0%20%7C%20where%20is_slow%20%3D%3D%20true%20%7C%20project%20_time%2C%20%5B'uri'%5D%2C%20%5B'req_duration_ms'%5D%2C%20duration_seconds%2C%20is_slow%22%7D)
**Output**
| \_time | uri | req\_duration\_ms | duration\_seconds | is\_slow |
| ---------------- | ---------- | ----------------- | ----------------- | -------- |
| Jun 24, 09:28:10 | /api/users | 1500 | 1.5 | true |
This example converts milliseconds to seconds using `todouble` to ensure decimal precision in the calculation, enabling accurate time-based analysis.
Convert trace duration values to real numbers for precise duration calculations and percentile analysis.
**Query**
```kusto
['otel-demo-traces']
| extend duration_ms = todouble(['duration']) / 1000000.0
| extend is_slow_span = duration_ms > 100.0
| where is_slow_span == true
| project _time, ['trace_id'], ['service.name'], ['duration'], duration_ms, is_slow_span
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20duration_ms%20%3D%20todouble\(%5B'duration'%5D\)%20/%201000000.0%20%7C%20extend%20is_slow_span%20%3D%20duration_ms%20%3E%20100.0%20%7C%20where%20is_slow_span%20%3D%3D%20true%20%7C%20project%20_time%2C%20%5B'trace_id'%5D%2C%20%5B'service.name'%5D%2C%20%5B'duration'%5D%2C%20duration_ms%2C%20is_slow_span%22%7D)
**Output**
| \_time | trace\_id | service.name | duration | duration\_ms | is\_slow\_span |
| ---------------- | --------- | ------------ | --------- | ------------ | -------------- |
| Jun 24, 09:28:10 | abc123 | frontend | 150000000 | 150.0 | true |
This example converts nanosecond durations to milliseconds using `todouble` to maintain decimal precision, enabling accurate performance analysis of trace spans.
Convert numeric security metrics to real numbers for threshold-based security analysis and alerting.
**Query**
```kusto
['sample-http-logs']
| extend risk_score = todouble(['req_duration_ms']) / 100.0
| extend is_high_risk = risk_score > 0.01
| where is_high_risk == true
| project _time, ['uri'], ['status'], ['req_duration_ms'], risk_score, is_high_risk
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20risk_score%20%3D%20todouble\(%5B'req_duration_ms'%5D\)%20/%20100.0%20%7C%20extend%20is_high_risk%20%3D%20risk_score%20%3E%2050.0%20%7C%20where%20\(%5B'status'%5D%20%3D%3D%20'403'%20or%20%5B'status'%5D%20%3D%3D%20'401'\)%20and%20is_high_risk%20%3D%3D%20true%20%7C%20project%20_time%2C%20%5B'uri'%5D%2C%20%5B'status'%5D%2C%20%5B'req_duration_ms'%5D%2C%20risk_score%2C%20is_high_risk%22%7D)
**Output**
| \_time | uri | status | req\_duration\_ms | risk\_score | is\_high\_risk |
| ---------------- | ------ | ------ | ----------------- | ----------- | -------------- |
| Jun 24, 09:28:10 | /admin | 403 | 5500 | 55.0 | true |
This example converts request duration to a risk score using `todouble` to enable precise threshold-based security analysis with decimal precision.
## List of related functions [#list-of-related-functions]
* [toreal](/apl/scalar-functions/conversion-functions/todouble): Synonym for `todouble`. Both functions convert values to real numbers.
* [toint](/apl/scalar-functions/conversion-functions/toint): Converts input to integer. Use `toint` when you need whole numbers, and `todouble` when you need decimal precision.
## Other query languages [#other-query-languages]
In Splunk, you use `tonumber` to convert values to numbers, which handles both integers and decimals. In APL, `todouble` specifically converts to floating-point numbers, while `toint` or `tolong` handle integers.
```sql Splunk example
... | eval price = tonumber(price_string)
```
```kusto APL equivalent
... | extend price = todouble(price_string)
```
In standard SQL, you use `CAST(... AS DOUBLE)` or `CAST(... AS REAL)` to convert values to floating-point numbers. In APL, `todouble` and `toreal` are synonyms that provide a simpler way to convert to real numbers.
```sql SQL example
SELECT CAST('1567.89' AS DOUBLE) AS price FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend price = todouble('1567.89')
```
---
# todynamic
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/todynamic
Use the `todynamic` function to parse a string as a dynamic value, such as a JSON object or array. This function is especially useful when your dataset contains structured data in string format and you want to access nested elements, iterate over arrays, or use dynamic-aware functions.
You often find `todynamic` helpful when working with logs, telemetry, or security events that encode rich metadata or nested attributes in stringified JSON. By converting these strings into dynamic values, you can query, filter, and transform the nested fields using APL’s built-in support for dynamic types.
## Usage [#usage]
### Syntax [#syntax]
```kusto
todynamic(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | ------ | ----------------------------------------------------- |
| value | string | A string representing a JSON-encoded object or array. |
### Returns [#returns]
A dynamic value. If the input isn’t a valid JSON string, the function returns `null`.
## Example [#example]
You want to find events that match certain criteria such as URI and status code. The criteria are stored in a stringified dictionary.
**Query**
```kusto
['sample-http-logs']
| extend criteria = '{"uri": "/api/v1/customer/services", "status": "200"}'
| extend metadata = todynamic(criteria)
| where uri == metadata.uri and status == metadata.status
| project _time, id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20criteria%20%3D%20'%7B%5C"uri%5C"%3A%20%5C"%2Fapi%2Fv1%2Fcustomer%2Fservices%5C"%2C%20%5C"status%5C"%3A%20%5C"200%5C"%7D'%20%7C%20extend%20metadata%20%3D%20todynamic\(criteria\)%20%7C%20where%20uri%20%3D%3D%20metadata.uri%20and%20status%20%3D%3D%20metadata.status%20%7C%20project%20_time%2C%20id%22%7D)
**Output**
| \_time | id |
| ---------------- | ------------------------------------ |
| Jun 24, 09:28:10 | 2f2e5c40-1094-4237-a124-ec50fab7e726 |
| Jun 24, 09:28:10 | 0f9724cb-fa9a-4a2f-bdf6-5c32b2f22efd |
| Jun 24, 09:28:10 | a516c4e9-2ed9-4fb9-a191-94e2844e9b2a |
## List of related functions [#list-of-related-functions]
* [pack\_array](/apl/scalar-functions/array-functions/pack-array): Use this to combine scalar values into an array. Use `pack_array` when you don’t need named keys and want positional data instead.
* [bag\_keys](/apl/scalar-functions/array-functions/bag-keys): Returns the list of keys in a dynamic dictionary. Use this to inspect or filter contents created by `pack_dictionary`.
* [bag\_pack](/apl/scalar-functions/array-functions/bag-pack): Expands a dictionary into multiple columns. Use it to revert the packing performed by `pack_dictionary`.
## Other query languages [#other-query-languages]
Splunk automatically interprets structured JSON data and allows you to use dot notation directly on fields, without explicit conversion. In APL, you need to explicitly cast a JSON string into a dynamic value using `todynamic`.
```sql Splunk example
... | eval json_field = json_extract(raw_field, "$.key")
```
```kusto APL equivalent
... | extend json_field = todynamic(raw_field).key
```
In standard SQL, you typically use `JSON_VALUE`, `JSON_QUERY`, or `CAST(... AS JSON)` to access structured content in string format. In APL, use `todynamic` to convert a string to a dynamic value that supports dot notation and further manipulation.
```sql SQL example
SELECT JSON_VALUE(raw_column, '$.key') AS value FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend value = todynamic(raw_column).key
```
---
# tohex
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/tohex
Use the `tohex` function to convert integer or long values to hexadecimal string representation. This is helpful when you need to display numeric values in hexadecimal format, work with memory addresses, or convert identifiers to hex format for compatibility with other systems.
You typically use `tohex` when working with numeric identifiers, memory addresses, or when you need hexadecimal representation for debugging, logging, or system integration purposes.
## Usage [#usage]
### Syntax [#syntax]
```kusto
tohex(value [, minLength])
```
### Parameters [#parameters]
| Name | Type | Description |
| --------- | ---- | ------------------------------------------------------------------- |
| value | int | The integer or long value to convert to hexadecimal string. |
| minLength | int | Optional. Minimum length of the resulting hex string. Default is 0. |
### Returns [#returns]
If conversion is successful, the result is a string value representing the hexadecimal representation. If conversion isn't successful, the result is `null`.
## Example [#example]
Convert numeric identifiers to hexadecimal format for display or integration with systems that use hex identifiers.
**Query**
```kusto
['sample-http-logs']
| extend id_hex = tohex(id, 8)
| project _time, uri, id, id_hex
```
**Output**
| \_time | uri | id | id\_hex |
| ---------------- | ---------- | ----- | -------- |
| Jun 24, 09:28:10 | /api/users | 12345 | 00003039 |
This example converts numeric identifiers to hexadecimal format, making them more readable and compatible with systems that use hex identifiers.
## List of related functions [#list-of-related-functions]
* [toint](/apl/scalar-functions/conversion-functions/toint): Converts input to integer. Use `toint` for smaller integers before converting to hex.
* [tostring](/apl/scalar-functions/conversion-functions/tostring): Converts input to string. Use `tostring` for general string conversion, and `tohex` specifically for hexadecimal representation.
## Other query languages [#other-query-languages]
In Splunk, you use `printf` with format specifiers like `%x` or `%X` to convert numbers to hexadecimal. In APL, `tohex` provides a direct function for this conversion.
```sql Splunk example
... | eval hex_value = printf("%x", numeric_field)
```
```kusto APL equivalent
... | extend hex_value = tohex(numeric_field)
```
In standard SQL, you use `TO_HEX` or `HEX` functions in some databases, or `FORMAT` functions with hex specifiers. In APL, `tohex` provides a straightforward way to convert integers to hexadecimal strings.
```sql SQL example
SELECT TO_HEX(546) AS hex_value FROM dual;
```
```kusto APL equivalent
['sample-http-logs']
| extend hex_value = tohex(546)
```
---
# toint, tolong
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/toint
Use the `toint` function (or its synonym `tolong`) to convert various data types to an integer (signed 64-bit) value. This is helpful when you need to normalize numeric values from different sources into integer format for mathematical operations, comparisons, or when decimal precision isn’t required.
You typically use `toint` when working with numeric strings, floating-point numbers, or other types that need to be converted to integers for whole-number calculations or when working with integer-based identifiers.
## Usage [#usage]
### Syntax [#syntax]
```kusto
toint(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | ------- | -------------------------------- |
| value | dynamic | The value to convert to integer. |
### Returns [#returns]
If the conversion is successful, the result is an integer. If the conversion isn't successful, the result is `null`.
### Conversion behavior [#conversion-behavior]
The `toint` function converts values based on their type:
* **Integer**: Returns the value unchanged.
* **Float**: Truncates to integer.
* **Boolean**: `true` becomes `1`, `false` becomes `0`.
* **Datetime**: Converts to nanoseconds since epoch as an integer.
* **Duration**: Converts to integer nanoseconds.
* **String**: Parses as a strict integer format (`"+"` or `"-"`). Hex values and other formats aren’t supported.
## Use case example [#use-case-example]
Convert string representations of HTTP status codes to integers for numeric comparisons and filtering.
**Query**
```kusto
['sample-http-logs']
| extend status_int = toint(status)
| extend is_error = status_int >= 400
| where is_error == true
| project _time, uri, status, status_int, is_error
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20status_int%20%3D%20toint\(status\)%20%7C%20extend%20is_error%20%3D%20status_int%20%3E%3D%20400%20%7C%20where%20is_error%20%3D%3D%20true%20%7C%20project%20_time%2C%20uri%2C%20status%2C%20status_int%2C%20is_error%22%7D)
**Output**
| \_time | uri | status | status\_int | is\_error |
| ---------------- | ---------- | ------ | ----------- | --------- |
| Jun 24, 09:28:10 | /api/users | 404 | 404 | true |
This example converts string status codes to integers, enabling numeric range comparisons to identify error responses.
## List of related functions [#list-of-related-functions]
* [todouble](/apl/scalar-functions/conversion-functions/todouble): Converts input to real number. Use `todouble` when you need decimal precision, and `toint` when you need whole numbers.
* [tostring](/apl/scalar-functions/conversion-functions/tostring): Converts input to string. Use `tostring` to convert integers back to strings when needed.
## Other query languages [#other-query-languages]
In Splunk, you use `tonumber` to convert values to numbers, which can be integers or decimals. In APL, `toint` specifically converts to integers, truncating decimal values.
```sql Splunk example
... | eval status_code = tonumber(status_string)
```
```kusto APL equivalent
... | extend status_code = toint(status_string)
```
In standard SQL, you use `CAST(... AS INT)` or `CAST(... AS INTEGER)` to convert values to integers. In APL, `toint` provides a simpler way to convert to integer values.
```sql SQL example
SELECT CAST('456' AS INT) AS status_code FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend status_code = toint('456')
```
---
# tostring
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/tostring
Use the `tostring` function to convert various data types to a string representation. This is helpful when you need to normalize values from different sources into string format for string operations, concatenation, or display purposes.
You typically use `tostring` when working with numeric values, booleans, or other types that need to be converted to strings for string manipulation, formatting, or when combining values in string operations.
## Usage [#usage]
### Syntax [#syntax]
```kusto
tostring(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | ------- | ------------------------------- |
| value | dynamic | The value to convert to string. |
### Returns [#returns]
If the expression value is non-null, the result is a string representation of the expression. If the expression value is null, the result is an empty string.
### Conversion behavior [#conversion-behavior]
The `tostring` function converts values based on their type:
* **Integer**: Formatted as base-10 string.
* **Float**: Formatted using Go’s `FormatFloat` function with precision `-1`. For more information, see the [Go documentation](https://pkg.go.dev/strconv#FormatFloat).
* **Boolean**: Returns `"true"` or `"false"`. Nil Boolean values return `"false"`.
* **Duration**: Returns duration-formatted strings. For example, `"1m20s"`.
* **Datetime**: Returns RFC3339Nano formatted datetime string.
## Use case example [#use-case-example]
Convert trace attributes to strings for string-based analysis and reporting.
**Query**
```kusto
['otel-demo-traces']
| where isnotempty(['attributes.http.response.status_code'])
| extend status_str = tostring(['attributes.http.response.status_code'])
| extend duration_str = tostring(duration)
| extend trace_summary = strcat('Service: ', ['service.name'], ', Status: ', status_str, ', Duration: ', duration_str)
| project _time, trace_id, ['service.name'], status_str, trace_summary
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20isnotempty\(%5B'attributes.http.response.status_code'%5D\)%20%7C%20extend%20status_str%20%3D%20tostring\(%5B'attributes.http.response.status_code'%5D\)%20%7C%20extend%20duration_str%20%3D%20tostring\(duration\)%20%7C%20extend%20trace_summary%20%3D%20strcat\('Service%3A%20'%2C%20%5B'service.name'%5D%2C%20'%2C%20Status%3A%20'%2C%20status_str%2C%20'%2C%20Duration%3A%20'%2C%20duration_str\)%20%7C%20project%20_time%2C%20trace_id%2C%20%5B'service.name'%5D%2C%20status_str%2C%20trace_summary%22%7D)
**Output**
| \_time | trace\_id | service.name | status\_str | trace\_summary |
| ---------------- | --------- | ------------ | ----------- | --------------------------------------------------- |
| Jun 24, 09:28:10 | abc123 | frontend | 200 | Service: frontend, Status: 200, Duration: 150000000 |
This example converts trace attributes to strings and creates a summary message, enabling formatted trace reporting and analysis.
## List of related functions [#list-of-related-functions]
* [strcat](/apl/scalar-functions/string-functions/strcat): Concatenates strings. Use `strcat` with `tostring` to combine converted values into formatted strings.
* [strcat-delim](/apl/scalar-functions/string-functions/strcat-delim): Concatenates strings with a delimiter. Use `strcat-delim` with `tostring` to join converted values with separators.
* [toint](/apl/scalar-functions/conversion-functions/toint): Converts input to integer. Use `toint` to convert strings back to integers when needed.
## Other query languages [#other-query-languages]
In Splunk, you use `tostring` or `tostring()` function to convert values to strings. In APL, `tostring` provides similar functionality for converting various types to string format.
```sql Splunk example
... | eval status_str = tostring(status_code)
```
```kusto APL equivalent
... | extend status_str = tostring(status_code)
```
In standard SQL, you use `CAST(... AS VARCHAR)` or `TO_CHAR` functions to convert values to strings. In APL, `tostring` provides a simpler way to convert various types to string values.
```sql SQL example
SELECT CAST(123 AS VARCHAR) AS status_str FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend status_str = tostring(123)
```
---
# totimespan
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/totimespan
Use the `totimespan` function to convert various data types to a timespan value representing a duration. This is helpful when you need to normalize duration values from different sources into timespan format for time-based calculations, comparisons, or aggregations.
You typically use `totimespan` when working with duration strings, numeric values representing time intervals, or other types that need to be converted to timespan format for duration calculations.
## Usage [#usage]
### Syntax [#syntax]
```kusto
totimespan(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | ------- | --------------------------------- |
| value | dynamic | The value to convert to timespan. |
### Returns [#returns]
If conversion is successful, the result is a timespan value. If conversion isn't successful, the result is `null`.
### Conversion behavior [#conversion-behavior]
The `totimespan` function converts values based on their type:
* **Integer/Float**: Interpreted as nanoseconds. For example, `1000000000` represents one second.
* **String**: Parsed as a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as `"300ms"`, `"-1.5h"`, or `"2h45m"`. Valid time units are `"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`, `"m"`, `"h"`.
## Use case examples [#use-case-examples]
Convert numeric duration values to timespan format for duration-based analysis and filtering.
**Query**
```kusto
['sample-http-logs']
| extend duration_span = totimespan(['req_duration_ms'] * 1000000)
| extend is_slow = duration_span > totimespan('1ms')
| where is_slow == true
| project _time, ['uri'], ['req_duration_ms'], duration_span, is_slow
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20duration_span%20%3D%20totimespan\(%5B'req_duration_ms'%5D%20*%201000000\)%20%7C%20extend%20is_slow%20%3D%20duration_span%20%3E%20totimespan\('1ms'\)%20%7C%20where%20is_slow%20%3D%3D%20true%20%7C%20project%20_time%2C%20%5B'uri'%5D%2C%20%5B'req_duration_ms'%5D%2C%20duration_span%2C%20is_slow%22%7D)
**Output**
| \_time | uri | req\_duration\_ms | duration\_span | is\_slow |
| ---------------- | ---------- | ----------------- | ---------------- | -------- |
| Jun 24, 09:28:10 | /api/users | 1500 | 00:00:01.5000000 | true |
This example converts millisecond durations to timespan format and compares them to a threshold, enabling precise duration-based filtering and analysis.
Convert trace duration values to timespan format for duration analysis and percentile calculations.
**Query**
```kusto
['otel-demo-traces']
| extend span_duration = totimespan(['duration'])
| extend is_slow_span = span_duration > totimespan('100ms')
| where is_slow_span == true
| project _time, ['trace_id'], ['service.name'], ['duration'], span_duration, is_slow_span
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20span_duration%20%3D%20totimespan\(%5B'duration'%5D\)%20%7C%20extend%20is_slow_span%20%3D%20span_duration%20%3E%20totimespan\('100ms'\)%20%7C%20where%20is_slow_span%20%3D%3D%20true%20%7C%20project%20_time%2C%20%5B'trace_id'%5D%2C%20%5B'service.name'%5D%2C%20%5B'duration'%5D%2C%20span_duration%2C%20is_slow_span%22%7D)
**Output**
| \_time | trace\_id | service.name | duration | span\_duration | is\_slow\_span |
| ---------------- | --------- | ------------ | --------- | ---------------- | -------------- |
| Jun 24, 09:28:10 | abc123 | frontend | 150000000 | 00:00:00.1500000 | true |
This example converts trace durations to timespan format and identifies slow spans, enabling duration-based performance analysis of trace data.
Convert security event duration metrics to timespan format for time-based security analysis.
**Query**
```kusto
['sample-http-logs']
| extend request_duration = totimespan(['req_duration_ms'] * 1000000)
| extend is_suspicious_duration = request_duration > totimespan('5ms')
| where is_suspicious_duration == true
| project _time, ['uri'], ['status'], ['req_duration_ms'], request_duration, is_suspicious_duration
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20request_duration%20%3D%20totimespan\(%5B'req_duration_ms'%5D%20*%201000000\)%20%7C%20extend%20is_suspicious_duration%20%3D%20request_duration%20%3E%20totimespan\('5ms'\)%20%7C%20where%20is_suspicious_duration%20%3D%3D%20true%20%7C%20project%20_time%2C%20%5B'uri'%5D%2C%20%5B'status'%5D%2C%20%5B'req_duration_ms'%5D%2C%20request_duration%2C%20is_suspicious_duration%22%7D)
**Output**
| \_time | uri | status | req\_duration\_ms | request\_duration | is\_suspicious\_duration |
| ---------------- | ------ | ------ | ----------------- | ----------------- | ------------------------ |
| Jun 24, 09:28:10 | /admin | 403 | 5500 | 00:00:05.5000000 | true |
This example converts request durations to timespan format and identifies suspiciously long security events, enabling duration-based security analysis and alerting.
## List of related functions [#list-of-related-functions]
* [todatetime](/apl/scalar-functions/conversion-functions/todatetime): Converts input to datetime. Use `todatetime` for absolute time points, and `totimespan` for duration values.
## Other query languages [#other-query-languages]
In Splunk, you use time functions or duration calculations with numeric values. In APL, `totimespan` provides a direct way to convert values to timespan format for duration operations.
```sql Splunk example
... | eval duration = duration_field
```
```kusto APL equivalent
... | extend duration = totimespan(duration_field)
```
In standard SQL, you use `INTERVAL` types or duration functions to work with time spans. In APL, `totimespan` provides a simpler way to convert values to timespan format.
```sql SQL example
SELECT INTERVAL '1' DAY AS duration FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend duration = totimespan('24h')
```
---
# case
Source: https://axiom.co/docs/apl/scalar-functions/conditional-function/case
## Introduction [#introduction]
The `case` function evaluates a sequence of condition-result pairs and returns the value of the first condition that evaluates to `true`. Use it to map raw values to human-readable labels, define alert severity tiers, or apply multi-way branching in a single expression instead of chaining multiple `iff` calls.
`case` is particularly useful when you need to classify log events into categories, route spans into latency buckets, or assign risk scores to requests based on several attributes at once.
## Usage [#usage]
### Syntax [#syntax]
```kusto
case(condition1, result1 [, condition2, result2, ...], nothingMatchedResult)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------- |
| condition*n* | bool | Yes | Expression to evaluate. APL tests conditions in order and returns the result paired with the first `true` condition. |
| result*n* | scalar | Yes | Value returned when the preceding condition is the first to evaluate to `true`. All result expressions must be of the same type. |
| nothingMatchedResult | scalar | Yes | Value returned when no condition evaluates to `true`. Must be the same type as the result expressions. |
### Returns [#returns]
The value paired with the first condition that evaluates to `true`, or `nothingMatchedResult` if no condition is `true`.
## Use case examples [#use-case-examples]
Classify HTTP responses by status code to summarize request outcomes.
**Query**
```kusto
['sample-http-logs']
| extend severity = case(
status == '200', 'success',
status == '404', 'not found',
status == '500', 'server error',
'other'
)
| summarize count() by severity
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20severity%20%3D%20case%28status%20%3D%3D%20%27200%27%2C%20%27success%27%2C%20status%20%3D%3D%20%27404%27%2C%20%27not%20found%27%2C%20status%20%3D%3D%20%27500%27%2C%20%27server%20error%27%2C%20%27other%27%29%20%7C%20summarize%20count%28%29%20by%20severity%22%7D)
**Output**
| severity | count\_ |
| ------------ | ------- |
| success | 8412 |
| other | 1203 |
| not found | 534 |
| server error | 182 |
The query assigns a human-readable label to each request based on its HTTP status code, then counts how many requests fall into each category.
Classify span durations into latency tiers to surface the slowest services.
**Query**
```kusto
['otel-demo-traces']
| extend priority = case(
duration > 1s, 'critical',
duration > 500ms, 'high',
duration > 100ms, 'medium',
'low'
)
| summarize count() by priority, ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20priority%20%3D%20case%28duration%20%3E%201s%2C%20%27critical%27%2C%20duration%20%3E%20500ms%2C%20%27high%27%2C%20duration%20%3E%20100ms%2C%20%27medium%27%2C%20%27low%27%29%20%7C%20summarize%20count%28%29%20by%20priority%2C%20%5B%27service.name%27%5D%22%7D)
**Output**
| priority | service.name | count\_ |
| -------- | --------------- | ------- |
| low | frontend | 4210 |
| medium | checkout | 823 |
| high | cart | 144 |
| critical | product-catalog | 38 |
The query buckets spans into four latency tiers and shows how many spans each service contributes to each tier.
Assign risk levels to requests based on HTTP status codes and methods to prioritize investigation.
**Query**
```kusto
['sample-http-logs']
| extend risk_level = case(
status == '401', 'unauthorized',
status == '403', 'forbidden',
status == '500', 'server error',
method == 'DELETE', 'destructive',
'normal'
)
| summarize count() by risk_level
| sort by count_ desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20risk_level%20%3D%20case%28status%20%3D%3D%20%27401%27%2C%20%27unauthorized%27%2C%20status%20%3D%3D%20%27403%27%2C%20%27forbidden%27%2C%20status%20%3D%3D%20%27500%27%2C%20%27server%20error%27%2C%20method%20%3D%3D%20%27DELETE%27%2C%20%27destructive%27%2C%20%27normal%27%29%20%7C%20summarize%20count%28%29%20by%20risk_level%20%7C%20sort%20by%20count_%20desc%22%7D)
**Output**
| risk\_level | count\_ |
| ------------ | ------- |
| normal | 9100 |
| unauthorized | 430 |
| forbidden | 312 |
| server error | 182 |
| destructive | 71 |
The query flags requests that may indicate security issues and summarizes them by risk category so you can see which types of events occur most frequently.
## List of related functions [#list-of-related-functions]
* [iff](/apl/scalar-functions/conditional-function/iff): Returns one of two values based on a single Boolean predicate. Use `iff` for binary decisions and `case` when you have three or more outcomes.
* [coalesce](/apl/scalar-functions/string-functions/coalesce): Returns the first non-null value from a list of expressions. Use `coalesce` to handle missing values rather than branching on conditions.
## Other query languages [#other-query-languages]
In Splunk SPL, the `case()` function inside `eval` takes alternating condition-value pairs. APL's `case` works the same way: provide pairs of `(condition, value)` followed by a fallback value.
```sql Splunk example
... | eval severity = case(status==200, "success", status==404, "not found", "other")
```
```kusto APL equivalent
['sample-http-logs']
| extend severity = case(status == '200', 'success', status == '404', 'not found', 'other')
```
SQL uses `CASE WHEN condition THEN value ... ELSE fallback END`. APL's `case` is functionally equivalent but uses a compact function-call syntax. The last argument serves as the `ELSE` value.
```sql SQL example
SELECT
CASE
WHEN status = '200' THEN 'success'
WHEN status = '404' THEN 'not found'
ELSE 'other'
END AS severity
FROM sample_http_logs
```
```kusto APL equivalent
['sample-http-logs']
| extend severity = case(status == '200', 'success', status == '404', 'not found', 'other')
```
---
# iff
Source: https://axiom.co/docs/apl/scalar-functions/conditional-function/iff
## Introduction [#introduction]
The `iff` function evaluates a single Boolean predicate and returns one of two values depending on the result. Use it to add binary flag columns, choose between two computed expressions, or conditionally override a value in one step.
The `iif` function is an alias for `iff` and behaves identically. For three or more branches, use [`case`](/apl/scalar-functions/conditional-function/case) instead.
## Usage [#usage]
### Syntax [#syntax]
```kusto
iff(predicate, ifTrue, ifFalse)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------------------------------------------------------ |
| predicate | bool | Yes | Expression that evaluates to `true` or `false`. |
| ifTrue | scalar | Yes | Value returned when `predicate` is `true`. |
| ifFalse | scalar | Yes | Value returned when `predicate` is `false`. Must be the same type as `ifTrue`. |
### Returns [#returns]
The value of `ifTrue` when `predicate` evaluates to `true`, or `ifFalse` otherwise.
To return a null value from `iff`, use `dynamic(null)`.
```kusto
iff(condition, dynamic(null), value)
```
## Use case examples [#use-case-examples]
Flag requests that take longer than one second to identify slow endpoints.
**Query**
```kusto
['sample-http-logs']
| extend is_slow = iff(req_duration_ms > 1000, 'slow', 'fast')
| summarize count() by is_slow
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20is_slow%20%3D%20iff%28req_duration_ms%20%3E%201000%2C%20%27slow%27%2C%20%27fast%27%29%20%7C%20summarize%20count%28%29%20by%20is_slow%22%7D)
**Output**
| is\_slow | count\_ |
| -------- | ------- |
| fast | 9630 |
| slow | 501 |
The query adds a `is_slow` column to each request and then counts how many fall into each category.
Label spans as long or short based on their duration to get a quick overview of latency distribution per service.
**Query**
```kusto
['otel-demo-traces']
| extend is_long = iff(duration > 500ms, 'long', 'short')
| summarize count() by is_long, ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20is_long%20%3D%20iff%28duration%20%3E%20500ms%2C%20%27long%27%2C%20%27short%27%29%20%7C%20summarize%20count%28%29%20by%20is_long%2C%20%5B%27service.name%27%5D%22%7D)
**Output**
| is\_long | service.name | count\_ |
| -------- | --------------- | ------- |
| short | frontend | 4210 |
| short | cart | 1830 |
| long | checkout | 423 |
| long | product-catalog | 182 |
The query shows how many spans per service exceed the 500 ms threshold.
## List of related functions [#list-of-related-functions]
* [case](/apl/scalar-functions/conditional-function/case): Multi-branch conditional that evaluates a list of conditions and returns the first matching result. Use `case` when you have three or more outcomes.
* [coalesce](/apl/scalar-functions/string-functions/coalesce): Returns the first non-null value from a list of expressions. Use `coalesce` when you want to fall back from null rather than branch on a condition.
## Other query languages [#other-query-languages]
Splunk SPL uses `if(condition, value_if_true, value_if_false)` inside an `eval` command. APL's `iff` takes the same three arguments in the same order.
```sql Splunk example
... | eval speed = if(req_duration_ms > 1000, "slow", "fast")
```
```kusto APL equivalent
['sample-http-logs']
| extend speed = iff(req_duration_ms > 1000, 'slow', 'fast')
```
SQL Server provides `IIF(condition, value_if_true, value_if_false)`, which maps directly to APL's `iff`. In ANSI SQL you can also write `CASE WHEN condition THEN value_if_true ELSE value_if_false END`, which is equivalent.
```sql SQL example
SELECT IIF(req_duration_ms > 1000, 'slow', 'fast') AS speed
FROM sample_http_logs
```
```kusto APL equivalent
['sample-http-logs']
| extend speed = iff(req_duration_ms > 1000, 'slow', 'fast')
```
---
# ago
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/ago
Use the `ago` function in APL to subtract a given timespan from the current UTC clock time. The function returns a `datetime` value equal to `now() - timespan`.
You can use `ago` to create relative time filters that adapt automatically to the current time. This is especially useful for dashboards, alerts, and ad-hoc investigations where you want to focus on recent activity without hardcoding timestamps.
Use it when you want to:
* Filter events that occurred within a recent time window.
* Create dynamic time-based thresholds for alerting or anomaly detection.
* Compare current activity against a rolling baseline period.
## Usage [#usage]
### Syntax [#syntax]
```kusto
ago(timespan)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | --------------------------------------------------- |
| timespan | `timespan` | The timespan to subtract from the current UTC time. |
### Returns [#returns]
A `datetime` value equal to `now() - timespan`.
## Use case examples [#use-case-examples]
Filter HTTP logs from the last 6 hours and count requests by status code.
**Query**
```kusto
['sample-http-logs']
| where _time > ago(6h)
| summarize count() by status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20_time%20%3E%20ago\(6h\)%20%7C%20summarize%20count\(\)%20by%20status%22%7D)
**Output**
| status | count\_ |
| ------ | ------- |
| 200 | 1523 |
| 404 | 87 |
| 500 | 34 |
This query filters log entries to the last 6 hours and groups them by HTTP status code to give a quick overview of recent traffic health.
Find slow traces from the last day and count them by service name.
**Query**
```kusto
['otel-demo-traces']
| where _time > ago(1d)
| where duration > 1s
| summarize count() by ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20_time%20%3E%20ago\(1d\)%20%7C%20where%20duration%20%3E%201s%20%7C%20summarize%20count\(\)%20by%20%5B'service.name'%5D%22%7D)
**Output**
| \['service.name'] | count\_ |
| ----------------- | ------- |
| frontend | 42 |
| checkout | 15 |
| cart | 8 |
This query identifies services with slow spans (over 1 second) in the last 24 hours, helping you pinpoint performance bottlenecks.
Detect high error rates in the last 12 hours by counting client and server errors per hour.
**Query**
```kusto
['sample-http-logs']
| where _time > ago(12h)
| where toint(status) >= 400
| summarize error_count = count() by bin(_time, 1h)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20_time%20%3E%20ago\(12h\)%20%7C%20where%20toint\(status\)%20%3E%3D%20400%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20bin\(_time%2C%201h\)%22%7D)
**Output**
| \_time | error\_count |
| -------------------- | ------------ |
| 2025-01-15T00:00:00Z | 12 |
| 2025-01-15T01:00:00Z | 45 |
| 2025-01-15T02:00:00Z | 9 |
This query bins error responses into hourly buckets over the last 12 hours, making it easy to spot sudden spikes in failures.
## List of related functions [#list-of-related-functions]
* [now](/apl/scalar-functions/datetime-functions/now): Returns the current UTC time. Use `now` when you need the absolute current time rather than a relative offset.
* [datetime\_add](/apl/scalar-functions/datetime-functions/datetime-add): Adds a specified number of date parts to a datetime. Use when you need to shift a datetime forward or backward by a specific calendar unit.
* [datetime\_diff](/apl/scalar-functions/datetime-functions/datetime-diff): Calculates the difference between two datetime values. Use when you need to measure elapsed time between events.
* [startofday](/apl/scalar-functions/datetime-functions/startofday): Returns the start of the day for a datetime, useful for day-level binning.
* [endofday](/apl/scalar-functions/datetime-functions/endofday): Returns the end of the day for a datetime.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use time modifiers such as `earliest=-6h` or `relative_time(now(), "-6h@h")` to filter events by relative time. In APL, the `ago` function directly subtracts a timespan from the current UTC time and returns a `datetime` you can use in filters.
```sql Splunk example
... | where _time > relative_time(now(), "-6h@h")
```
```kusto APL equivalent
... | where _time > ago(6h)
```
In ANSI SQL, you typically subtract an interval from the current timestamp using expressions such as `CURRENT_TIMESTAMP - INTERVAL '6' HOUR` or `DATEADD(HOUR, -6, GETDATE())`. In APL, the `ago` function achieves the same result with a concise syntax.
```sql SQL example
SELECT * FROM events WHERE timestamp_column > CURRENT_TIMESTAMP - INTERVAL '6' HOUR;
```
```kusto APL equivalent
['dataset']
| where _time > ago(6h)
```
---
# datetime_add
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/datetime-add
Use the `datetime_add` function in APL to calculate a new datetime by adding a specified number of date parts to a base datetime value. You can add years, months, weeks, days, hours, minutes, seconds, or smaller units. Use negative values to subtract.
You can use `datetime_add` to shift timestamps forward or backward for time-window comparisons, expiration calculations, and timezone adjustments.
Use it when you want to:
* Project future or past timestamps relative to an event.
* Define time ranges around a known incident or deadline.
* Shift trace or log timestamps for timezone normalization.
## Usage [#usage]
### Syntax [#syntax]
```kusto
datetime_add(part, value, datetime)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| part | `string` | The unit of time to add: `'year'`, `'quarter'`, `'month'`, `'week'`, `'day'`, `'hour'`, `'minute'`, `'second'`, `'millisecond'`, `'microsecond'`. |
| value | `int` | The number of units to add. Use a negative value to subtract. |
| datetime | `datetime` | The base datetime value. |
### Returns [#returns]
A `datetime` value after adding the specified interval to the base datetime.
## Use case examples [#use-case-examples]
Project what the time is 1 hour after each request to estimate cache expiration windows.
**Query**
```kusto
['sample-http-logs']
| extend future_time = datetime_add('hour', 1, _time)
| project _time, future_time, method, status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20future_time%20%3D%20datetime_add\('hour'%2C%201%2C%20_time\)%20%7C%20project%20_time%2C%20future_time%2C%20method%2C%20status%22%7D)
**Output**
| \_time | future\_time | method | status |
| -------------------- | -------------------- | ------ | ------ |
| 2025-01-15T10:00:00Z | 2025-01-15T11:00:00Z | GET | 200 |
| 2025-01-15T10:05:00Z | 2025-01-15T11:05:00Z | POST | 201 |
| 2025-01-15T10:12:00Z | 2025-01-15T11:12:00Z | GET | 404 |
This query adds 1 hour to each request timestamp, which is useful for estimating when cached responses expire.
Shift trace timestamps forward by 30 minutes to simulate a timezone adjustment.
**Query**
```kusto
['otel-demo-traces']
| extend adjusted_time = datetime_add('minute', 30, _time)
| project _time, adjusted_time, ['service.name'], duration
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20adjusted_time%20%3D%20datetime_add\('minute'%2C%2030%2C%20_time\)%20%7C%20project%20_time%2C%20adjusted_time%2C%20%5B'service.name'%5D%2C%20duration%22%7D)
**Output**
| \_time | adjusted\_time | \['service.name'] | duration |
| -------------------- | -------------------- | ----------------- | ---------------- |
| 2025-01-15T08:00:00Z | 2025-01-15T08:30:00Z | frontend | 00:00:01.2340000 |
| 2025-01-15T08:01:00Z | 2025-01-15T08:31:00Z | cart | 00:00:00.5670000 |
| 2025-01-15T08:02:00Z | 2025-01-15T08:32:00Z | checkout | 00:00:02.1000000 |
This query shifts each trace timestamp forward by 30 minutes, useful for aligning traces from systems that report in different time offsets.
Find requests that occurred within 1 day before a known incident time.
**Query**
```kusto
['sample-http-logs']
| where _time between (datetime_add('day', -1, datetime(2025-01-15)) .. datetime(2025-01-15))
| summarize count() by status, method
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20_time%20between%20\(datetime_add\('day'%2C%20-1%2C%20datetime\(2025-01-15\)\)%20..%20datetime\(2025-01-15\)\)%20%7C%20summarize%20count\(\)%20by%20status%2C%20method%22%7D)
**Output**
| status | method | count\_ |
| ------ | ------ | ------- |
| 200 | GET | 1204 |
| 500 | POST | 37 |
| 404 | GET | 89 |
This query uses `datetime_add` to define a 1-day window before a known incident, helping you investigate the activity that preceded it.
## List of related functions [#list-of-related-functions]
* [datetime\_diff](/apl/scalar-functions/datetime-functions/datetime-diff): Calculates the difference between two datetime values. Use when you need to measure elapsed time rather than shift a timestamp.
* [ago](/apl/scalar-functions/datetime-functions/ago): Subtracts a timespan from the current UTC time. Use for simple relative time filters based on `now()`.
* [now](/apl/scalar-functions/datetime-functions/now): Returns the current UTC time.
* [startofmonth](/apl/scalar-functions/datetime-functions/startofmonth): Returns the start of the month for a datetime, useful for month-boundary calculations.
* [endofmonth](/apl/scalar-functions/datetime-functions/endofmonth): Returns the end of the month for a datetime.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use `relative_time(_time, "+1mon")` to shift a timestamp by a calendar unit. In APL, the `datetime_add` function takes the date part as a string, the number of units, and the base datetime as separate arguments.
```sql Splunk example
... | eval new_time=relative_time(_time, "+1mon")
```
```kusto APL equivalent
... | extend new_time = datetime_add('month', 1, _time)
```
In ANSI SQL, you typically use `DATEADD(month, 1, timestamp_column)` or equivalent interval arithmetic to shift a timestamp. In APL, `datetime_add` uses the same conceptual pattern with a string-based part name.
```sql SQL example
SELECT DATEADD(month, 1, timestamp_column) AS new_time FROM events;
```
```kusto APL equivalent
['dataset']
| extend new_time = datetime_add('month', 1, _time)
```
---
# datetime_diff
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/datetime-diff
Use the `datetime_diff` function in APL to calculate the calendarian difference between two datetime values in a specified unit. The function computes `datetime1 - datetime2` and returns the result as a count of the specified date part.
You can use `datetime_diff` to measure elapsed time between events, calculate how long ago something occurred, or compare timestamps across records.
Use it when you want to:
* Calculate the number of hours, days, or minutes between two events.
* Measure how long ago a request or trace occurred relative to the current time.
* Compare event timestamps to detect delays or gaps in processing.
## Usage [#usage]
### Syntax [#syntax]
```kusto
datetime_diff(part, datetime1, datetime2)
```
### Parameters [#parameters]
| Name | Type | Description |
| --------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| part | `string` | The unit for the result: `'year'`, `'quarter'`, `'month'`, `'week'`, `'day'`, `'hour'`, `'minute'`, `'second'`, `'millisecond'`, `'microsecond'`, `'nanosecond'`. |
| datetime1 | `datetime` | The later datetime value (left side of subtraction). |
| datetime2 | `datetime` | The earlier datetime value (right side of subtraction). |
### Returns [#returns]
A `long` representing the number of periods of the specified unit in the result of `datetime1 - datetime2`.
## Use case examples [#use-case-examples]
Calculate how many hours ago each request occurred.
**Query**
```kusto
['sample-http-logs']
| extend hours_ago = datetime_diff('hour', now(), _time)
| project _time, hours_ago, method, status
| take 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20hours_ago%20%3D%20datetime_diff\('hour'%2C%20now\(\)%2C%20_time\)%20%7C%20project%20_time%2C%20hours_ago%2C%20method%2C%20status%20%7C%20take%2010%22%7D)
**Output**
| \_time | hours\_ago | method | status |
| -------------------- | ---------- | ------ | ------ |
| 2025-01-15T08:00:00Z | 26 | GET | 200 |
| 2025-01-15T09:30:00Z | 25 | POST | 201 |
| 2025-01-15T10:15:00Z | 24 | GET | 404 |
This query computes the number of hours between each request and the current time, giving you a quick sense of how recent each event is.
Measure the number of minutes since each trace for the frontend service.
**Query**
```kusto
['otel-demo-traces']
| extend minutes_since = datetime_diff('minute', now(), _time)
| where ['service.name'] == 'frontend'
| project _time, minutes_since, trace_id, duration
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20minutes_since%20%3D%20datetime_diff\('minute'%2C%20now\(\)%2C%20_time\)%20%7C%20where%20%5B'service.name'%5D%20%3D%3D%20'frontend'%20%7C%20project%20_time%2C%20minutes_since%2C%20trace_id%2C%20duration%22%7D)
**Output**
| \_time | minutes\_since | trace\_id | duration |
| -------------------- | -------------- | --------- | ---------------- |
| 2025-01-15T09:00:00Z | 1560 | abc123 | 00:00:01.2340000 |
| 2025-01-15T09:05:00Z | 1555 | def456 | 00:00:00.8910000 |
| 2025-01-15T09:10:00Z | 1550 | ghi789 | 00:00:02.0050000 |
This query calculates how many minutes have elapsed since each frontend trace, useful for understanding the age of trace data.
Count error requests by how many days ago they occurred.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 400
| extend days_ago = datetime_diff('day', now(), _time)
| summarize error_count = count() by days_ago
| sort by days_ago asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20400%20%7C%20extend%20days_ago%20%3D%20datetime_diff\('day'%2C%20now\(\)%2C%20_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20days_ago%20%7C%20sort%20by%20days_ago%20asc%22%7D)
**Output**
| days\_ago | error\_count |
| --------- | ------------ |
| 0 | 23 |
| 1 | 45 |
| 2 | 31 |
This query groups error responses by how many days ago they occurred, making it easy to spot whether error rates are increasing or decreasing over recent days.
## List of related functions [#list-of-related-functions]
* [datetime\_add](/apl/scalar-functions/datetime-functions/datetime-add): Adds a specified number of date parts to a datetime. Use when you need to shift a timestamp rather than measure the gap between two.
* [ago](/apl/scalar-functions/datetime-functions/ago): Subtracts a timespan from the current UTC time. Use for simple relative time filters.
* [now](/apl/scalar-functions/datetime-functions/now): Returns the current UTC time.
* [todatetime](/apl/scalar-functions/conversion-functions/todatetime): Converts a value to a datetime. Use to parse strings into datetime values before computing differences.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically calculate time differences using arithmetic on epoch timestamps, such as `eval diff=round((_time - relative_time(now(), "-1d@d")) / 3600)`. In APL, the `datetime_diff` function directly computes the difference between two datetime values in a specified unit.
```sql Splunk example
... | eval hours_diff=round((_time - relative_time(now(), "-1d@d")) / 3600)
```
```kusto APL equivalent
... | extend hours_diff = datetime_diff('hour', now(), _time)
```
In ANSI SQL, you typically use `DATEDIFF(hour, start_time, end_time)` or `TIMESTAMPDIFF(HOUR, start_time, end_time)` to compute the difference between timestamps. In APL, `datetime_diff` follows a similar pattern with the unit as the first argument.
```sql SQL example
SELECT DATEDIFF(hour, start_time, end_time) AS hours_diff FROM events;
```
```kusto APL equivalent
['dataset']
| extend hours_diff = datetime_diff('hour', end_time, start_time)
```
---
# datetime_part
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/datetime-part
Use the `datetime_part` function in APL to extract a specific date part from a datetime value as an integer. You can extract components such as the year, month, day, hour, minute, second, and more.
You can use `datetime_part` to break down timestamps into individual components for grouping, filtering, or analysis. This is especially useful for time-of-day analysis, seasonal patterns, and partitioning data by calendar units.
Use it when you want to:
* Group events by hour of day to identify peak traffic periods.
* Extract the month or quarter for seasonal trend analysis.
* Partition data by year or day for reporting and aggregation.
## Usage [#usage]
### Syntax [#syntax]
```kusto
datetime_part(part, datetime)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| part | `string` | The date part to extract: `'year'`, `'quarter'`, `'month'`, `'week_of_year'`, `'day'`, `'dayOfYear'`, `'hour'`, `'minute'`, `'second'`, `'millisecond'`, `'microsecond'`, `'nanosecond'`. |
| datetime | `datetime` | The datetime value to extract the part from. |
### Returns [#returns]
An `int` representing the value of the extracted date part.
## Use case examples [#use-case-examples]
Analyze request volume by hour of day to find peak traffic periods.
**Query**
```kusto
['sample-http-logs']
| extend hour = datetime_part('hour', _time)
| summarize request_count = count() by hour
| sort by hour asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20hour%20%3D%20datetime_part\('hour'%2C%20_time\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20hour%20%7C%20sort%20by%20hour%20asc%22%7D)
**Output**
| hour | request\_count |
| ---- | -------------- |
| 0 | 312 |
| 1 | 287 |
| 2 | 198 |
This query extracts the hour from each request timestamp and counts requests per hour, revealing daily traffic patterns.
Break down trace counts by day of month and service name to identify daily patterns.
**Query**
```kusto
['otel-demo-traces']
| extend day_of_week = datetime_part('day', _time)
| summarize trace_count = count() by day_of_week, ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20day_of_week%20%3D%20datetime_part\('day'%2C%20_time\)%20%7C%20summarize%20trace_count%20%3D%20count\(\)%20by%20day_of_week%2C%20%5B'service.name'%5D%22%7D)
**Output**
| day\_of\_week | \['service.name'] | trace\_count |
| ------------- | ----------------- | ------------ |
| 1 | frontend | 1540 |
| 1 | cart | 870 |
| 2 | frontend | 1620 |
This query groups traces by the day of the month and service name, helping you spot services with uneven daily load.
Identify which months have the most server error responses.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 500
| extend month = datetime_part('month', _time)
| summarize error_count = count() by month
| sort by error_count desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20500%20%7C%20extend%20month%20%3D%20datetime_part\('month'%2C%20_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20month%20%7C%20sort%20by%20error_count%20desc%22%7D)
**Output**
| month | error\_count |
| ----- | ------------ |
| 3 | 142 |
| 7 | 118 |
| 11 | 97 |
This query extracts the month from each error event and ranks months by error frequency, useful for identifying seasonal reliability issues.
## List of related functions [#list-of-related-functions]
* [hourofday](/apl/scalar-functions/datetime-functions/hourofday): Returns the hour of the day from a datetime. Use as a shorthand when you only need the hour.
* [dayofmonth](/apl/scalar-functions/datetime-functions/dayofmonth): Returns the day of the month from a datetime.
* [dayofweek](/apl/scalar-functions/datetime-functions/dayofweek): Returns the day of the week as a timespan.
* [dayofyear](/apl/scalar-functions/datetime-functions/dayofyear): Returns the day of the year as an integer.
* [monthofyear](/apl/scalar-functions/datetime-functions/monthofyear): Returns the month number from a datetime.
* [getyear](/apl/scalar-functions/datetime-functions/getyear): Returns the year from a datetime.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use `strftime` with format specifiers such as `%H` for hour or `%m` for month to extract date parts. In APL, the `datetime_part` function takes a string-based part name and returns the corresponding integer directly.
```sql Splunk example
... | eval hour=strftime(_time, "%H")
```
```kusto APL equivalent
... | extend hour = datetime_part('hour', _time)
```
In ANSI SQL, you typically use `EXTRACT(HOUR FROM timestamp_column)` or `DATEPART(HOUR, timestamp_column)`. In APL, `datetime_part` follows a similar pattern with a string-based part name as the first argument.
```sql SQL example
SELECT EXTRACT(HOUR FROM timestamp_column) AS hour FROM events;
```
```kusto APL equivalent
['dataset']
| extend hour = datetime_part('hour', _time)
```
---
# dayofmonth
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/dayofmonth
Use the `dayofmonth` function in APL to extract the day number of the month from a datetime value. The function returns an integer from 1 to 31 representing the day within the month.
You can use `dayofmonth` to analyze patterns tied to specific days of the month, such as billing cycles, payroll processing windows, or recurring scheduled events.
Use it when you want to:
* Detect traffic or error patterns that repeat on specific days of the month.
* Group events by day of month for monthly trend analysis.
* Correlate activity spikes with known monthly schedules such as billing or report generation.
## Usage [#usage]
### Syntax [#syntax]
```kusto
dayofmonth(datetime)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | ------------------------- |
| datetime | `datetime` | The input datetime value. |
### Returns [#returns]
An `int` from 1 to 31 representing the day number of the month.
## Use case examples [#use-case-examples]
Count requests by day of month to find recurring traffic patterns.
**Query**
```kusto
['sample-http-logs']
| extend day = dayofmonth(_time)
| summarize request_count = count() by day
| sort by day asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20day%20%3D%20dayofmonth\(_time\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20day%20%7C%20sort%20by%20day%20asc%22%7D)
**Output**
| day | request\_count |
| --- | -------------- |
| 1 | 1450 |
| 2 | 1380 |
| 3 | 1520 |
This query groups HTTP requests by the day of the month, helping you identify days with consistently higher or lower traffic.
Find average span duration by day of month for each service.
**Query**
```kusto
['otel-demo-traces']
| extend day = dayofmonth(_time)
| summarize avg_duration = avg(duration) by day, ['service.name']
| sort by day asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20day%20%3D%20dayofmonth\(_time\)%20%7C%20summarize%20avg_duration%20%3D%20avg\(duration\)%20by%20day%2C%20%5B'service.name'%5D%20%7C%20sort%20by%20day%20asc%22%7D)
**Output**
| day | \['service.name'] | avg\_duration |
| --- | ----------------- | ---------------- |
| 1 | frontend | 00:00:01.1200000 |
| 1 | cart | 00:00:00.4500000 |
| 2 | frontend | 00:00:01.2500000 |
This query shows how average span duration varies by day of the month for each service, useful for detecting performance changes tied to monthly cycles.
Identify which days of the month have the most failed requests.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 400
| extend day = dayofmonth(_time)
| summarize error_count = count() by day
| sort by error_count desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20400%20%7C%20extend%20day%20%3D%20dayofmonth\(_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20day%20%7C%20sort%20by%20error_count%20desc%22%7D)
**Output**
| day | error\_count |
| --- | ------------ |
| 15 | 98 |
| 1 | 87 |
| 28 | 76 |
This query ranks days of the month by error frequency, helping you correlate failures with recurring monthly events such as billing runs or batch jobs.
## List of related functions [#list-of-related-functions]
* [dayofweek](/apl/scalar-functions/datetime-functions/dayofweek): Returns the day of the week as a timespan. Use for weekly pattern analysis rather than monthly.
* [dayofyear](/apl/scalar-functions/datetime-functions/dayofyear): Returns the day of the year as an integer. Use when you need to track position within the full year.
* [datetime\_part](/apl/scalar-functions/datetime-functions/datetime-part): Extracts a specific date part as an integer. Use when you need flexibility to extract different parts dynamically.
* [monthofyear](/apl/scalar-functions/datetime-functions/monthofyear): Returns the month number from a datetime. Use alongside `dayofmonth` for month-and-day breakdowns.
* [getmonth](/apl/scalar-functions/datetime-functions/getmonth): Returns the month from a datetime as an integer.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use `strftime(_time, "%d")` to extract the day of the month. In APL, the `dayofmonth` function directly returns the day number as an integer.
```sql Splunk example
... | eval day=strftime(_time, "%d")
```
```kusto APL equivalent
... | extend day = dayofmonth(_time)
```
In ANSI SQL, you typically use `EXTRACT(DAY FROM timestamp_column)` or `DAY(timestamp_column)` to get the day of the month. In APL, `dayofmonth` provides the same result with a single function call.
```sql SQL example
SELECT EXTRACT(DAY FROM timestamp_column) AS day FROM events;
```
```kusto APL equivalent
['dataset']
| extend day = dayofmonth(_time)
```
---
# dayofweek
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/dayofweek
Use the `dayofweek` function in APL to extract the day of the week from a datetime value as an integer. The function returns the number of days since the preceding Sunday, where 0 represents Sunday, 1 represents Monday, and so on up to 6 for Saturday.
You can use `dayofweek` to group and analyze records by the day of the week. This is especially useful for identifying weekday versus weekend patterns, scheduling-based filtering, and understanding cyclical behavior in your data.
Use it when you want to:
* Group events by day of the week to spot recurring patterns.
* Compare weekday and weekend activity in logs, traces, or security data.
* Filter records to specific days for schedule-based analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
dayofweek(datetime)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | ------------------------- |
| datetime | `datetime` | The input datetime value. |
### Returns [#returns]
An `int` representing the number of days since the preceding Sunday (0 for Sunday, 1 for Monday, through 6 for Saturday).
## Use case examples [#use-case-examples]
Analyze request volume by day of the week to identify peak traffic days.
**Query**
```kusto
['sample-http-logs']
| extend day = dayofweek(_time)
| summarize request_count = count() by day
| sort by day asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20day%20%3D%20dayofweek\(_time\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20day%20%7C%20sort%20by%20day%20asc%22%7D)
**Output**
| day | request\_count |
| --- | -------------- |
| 0 | 1204 |
| 1 | 1587 |
| 2 | 1632 |
This query groups HTTP log events by the day of the week and counts requests for each day, helping you spot which days see the most traffic.
Compare average span duration across weekdays for a specific service to detect day-of-week performance variations.
**Query**
```kusto
['otel-demo-traces']
| extend day = dayofweek(_time)
| summarize avg_duration = avg(duration) by day, ['service.name']
| sort by day asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20day%20%3D%20dayofweek\(_time\)%20%7C%20summarize%20avg_duration%20%3D%20avg\(duration\)%20by%20day%2C%20%5B'service.name'%5D%20%7C%20sort%20by%20day%20asc%22%7D)
**Output**
| day | service.name | avg\_duration |
| --- | ------------ | ---------------- |
| 0 | frontend | 00:00:01.3120000 |
| 1 | frontend | 00:00:01.1840000 |
| 2 | frontend | 00:00:01.2560000 |
This query reveals how average span duration varies by day of the week for each service, highlighting potential performance differences on specific days.
Detect anomalous error traffic on specific days of the week by counting client and server errors.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 400
| extend day = dayofweek(_time)
| summarize error_count = count() by day
| sort by day asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20400%20%7C%20extend%20day%20%3D%20dayofweek\(_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20day%20%7C%20sort%20by%20day%20asc%22%7D)
**Output**
| day | error\_count |
| --- | ------------ |
| 0 | 42 |
| 1 | 28 |
| 2 | 31 |
This query counts HTTP errors by day of the week, helping you identify whether certain days experience more failures than others.
## List of related functions [#list-of-related-functions]
* [dayofmonth](/apl/scalar-functions/datetime-functions/dayofmonth): Returns the day number within the month from a datetime.
* [dayofyear](/apl/scalar-functions/datetime-functions/dayofyear): Returns the day number within the year from a datetime.
* [datetime\_part](/apl/scalar-functions/datetime-functions/datetime-part): Extracts a specific date part (such as day or week) as an integer.
* [startofweek](/apl/scalar-functions/datetime-functions/startofweek): Returns the start of the week for a datetime, useful for binning events to week boundaries.
* [endofweek](/apl/scalar-functions/datetime-functions/endofweek): Returns the end of the week for a datetime.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use the `strftime` function with the `%w` specifier to extract the day of the week as an integer (0=Sunday). In APL, the `dayofweek` function returns the same convention directly from a datetime value.
```sql Splunk example
... | eval day=strftime(_time, "%w")
```
```kusto APL equivalent
... | extend day = dayofweek(_time)
```
In ANSI SQL, you often use `EXTRACT(DOW FROM timestamp)` or `DAYOFWEEK(timestamp)`. The exact numbering convention varies across platforms. APL's `dayofweek` returns 0 for Sunday through 6 for Saturday.
```sql SQL example
SELECT EXTRACT(DOW FROM timestamp_column) AS day FROM events;
```
```kusto APL equivalent
['dataset']
| extend day = dayofweek(_time)
```
---
# dayofyear
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/dayofyear
Use the `dayofyear` function in APL to extract the day number within the year from a datetime value. The function returns an integer from 1 to 365 (or 1 to 366 in a leap year) representing how far into the year the given date falls.
You can use `dayofyear` to perform year-over-year comparisons by day, analyze seasonal trends, and track how metrics evolve throughout the year. This is especially useful for time-series analysis where you want to align data across multiple years by day number.
Use it when you want to:
* Compare activity or metrics on the same day across different years.
* Identify seasonal trends and patterns in log, trace, or security data.
* Track progress through the year for cumulative reporting.
## Usage [#usage]
### Syntax [#syntax]
```kusto
dayofyear(datetime)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | ------------------------- |
| datetime | `datetime` | The input datetime value. |
### Returns [#returns]
An `int` from 1 to 366 representing the day number within the year.
## Use case examples [#use-case-examples]
Track daily request counts across the year to spot high-traffic and low-traffic periods.
**Query**
```kusto
['sample-http-logs']
| extend day_of_year = dayofyear(_time)
| summarize request_count = count() by day_of_year
| sort by day_of_year asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20day_of_year%20%3D%20dayofyear\(_time\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20day_of_year%20%7C%20sort%20by%20day_of_year%20asc%22%7D)
**Output**
| day\_of\_year | request\_count |
| ------------- | -------------- |
| 1 | 482 |
| 2 | 531 |
| 3 | 497 |
This query counts the total number of HTTP requests for each day of the year, helping you identify seasonal traffic patterns.
Monitor trace volume trends by day of the year for each service to detect seasonal performance shifts.
**Query**
```kusto
['otel-demo-traces']
| extend day_of_year = dayofyear(_time)
| summarize trace_count = count() by day_of_year, ['service.name']
| sort by day_of_year asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20day_of_year%20%3D%20dayofyear\(_time\)%20%7C%20summarize%20trace_count%20%3D%20count\(\)%20by%20day_of_year%2C%20%5B'service.name'%5D%20%7C%20sort%20by%20day_of_year%20asc%22%7D)
**Output**
| day\_of\_year | service.name | trace\_count |
| ------------- | ------------ | ------------ |
| 1 | frontend | 312 |
| 2 | frontend | 287 |
| 3 | frontend | 345 |
This query tracks how trace volume changes day by day throughout the year for each service, revealing seasonal trends.
Find the busiest days of the year for server errors to identify recurring problem periods.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 500
| extend day_of_year = dayofyear(_time)
| summarize error_count = count() by day_of_year
| sort by error_count desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20500%20%7C%20extend%20day_of_year%20%3D%20dayofyear\(_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20day_of_year%20%7C%20sort%20by%20error_count%20desc%22%7D)
**Output**
| day\_of\_year | error\_count |
| ------------- | ------------ |
| 142 | 87 |
| 98 | 64 |
| 215 | 53 |
This query ranks days of the year by server error count, helping you pinpoint recurring high-error periods.
## List of related functions [#list-of-related-functions]
* [dayofmonth](/apl/scalar-functions/datetime-functions/dayofmonth): Returns the day number within the month from a datetime.
* [dayofweek](/apl/scalar-functions/datetime-functions/dayofweek): Returns the day of the week as an integer, useful for weekday versus weekend analysis.
* [datetime\_part](/apl/scalar-functions/datetime-functions/datetime-part): Extracts a specific date part (such as day or year) as an integer.
* [monthofyear](/apl/scalar-functions/datetime-functions/monthofyear): Returns the month number from a datetime, useful for monthly grouping.
* [getyear](/apl/scalar-functions/datetime-functions/getyear): Returns the year from a datetime, useful for year-level aggregation.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use the `strftime` function with the `%j` specifier to extract the day of the year. In APL, the `dayofyear` function directly returns the day number within the year from a datetime value.
```sql Splunk example
... | eval doy=strftime(_time, "%j")
```
```kusto APL equivalent
... | extend day_of_year = dayofyear(_time)
```
In ANSI SQL, you often use `EXTRACT(DOY FROM timestamp)` or `DAYOFYEAR(timestamp)`. The APL `dayofyear` function provides the same result, returning an integer from 1 to 366.
```sql SQL example
SELECT EXTRACT(DOY FROM timestamp_column) AS day_of_year FROM events;
```
```kusto APL equivalent
['dataset']
| extend day_of_year = dayofyear(_time)
```
---
# endofday
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/endofday
Use the `endofday` function in APL to calculate the end of the day for a given datetime value. The function returns a datetime set to the last moment of the day (23:59:59.9999999), with an optional offset to shift forward or backward by a specified number of days.
You can use `endofday` to create daily time boundaries for aggregation, reporting, and filtering. This is especially useful when you need to bucket events into daily intervals or determine how much time remains until the end of a given day.
Use it when you want to:
* Define end-of-day boundaries for daily reports and dashboards.
* Aggregate events up to the end of each day.
* Calculate the remaining time in a day for each event.
## Usage [#usage]
### Syntax [#syntax]
```kusto
endofday(datetime [, offset])
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| datetime | `datetime` | The input datetime value. |
| offset | `long` | Optional: The number of days to offset from the input date. Use negative values for past dates and positive values for future dates. Default is 0. |
### Returns [#returns]
A `datetime` representing the end of the day (23:59:59.9999999) for the given date, shifted by the offset if specified.
## Use case examples [#use-case-examples]
Calculate how far each request is from the end of the day to understand the distribution of traffic within daily windows.
**Query**
```kusto
['sample-http-logs']
| extend end = endofday(_time)
| extend remaining_ms = datetime_diff('millisecond', end, _time)
| project _time, end, remaining_ms, method
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20end%20%3D%20endofday\(_time\)%20%7C%20extend%20remaining_ms%20%3D%20datetime_diff\('millisecond'%2C%20end%2C%20_time\)%20%7C%20project%20_time%2C%20end%2C%20remaining_ms%2C%20method%22%7D)
**Output**
| \_time | end | remaining\_ms | method |
| -------------------- | ---------------------------- | ------------- | ------ |
| 2024-11-14T10:22:31Z | 2024-11-14T23:59:59.9999999Z | 49048000 | GET |
| 2024-11-14T18:45:12Z | 2024-11-14T23:59:59.9999999Z | 18888000 | POST |
| 2024-11-14T23:01:44Z | 2024-11-14T23:59:59.9999999Z | 3496000 | GET |
This query computes the time remaining until the end of the day for each request, useful for understanding traffic distribution across daily windows.
Aggregate trace counts to end-of-day boundaries for each service to create daily summaries.
**Query**
```kusto
['otel-demo-traces']
| extend day_end = endofday(_time)
| summarize trace_count = count() by day_end, ['service.name']
| sort by day_end asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20day_end%20%3D%20endofday\(_time\)%20%7C%20summarize%20trace_count%20%3D%20count\(\)%20by%20day_end%2C%20%5B'service.name'%5D%20%7C%20sort%20by%20day_end%20asc%22%7D)
**Output**
| day\_end | service.name | trace\_count |
| ---------------------------- | ------------ | ------------ |
| 2024-11-14T23:59:59.9999999Z | frontend | 1523 |
| 2024-11-15T23:59:59.9999999Z | frontend | 1487 |
| 2024-11-16T23:59:59.9999999Z | frontend | 1601 |
This query groups traces by end-of-day boundaries for each service, giving you a daily count of trace activity.
Count error requests grouped by end-of-day boundaries to track daily error volumes.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 400
| extend day_end = endofday(_time)
| summarize error_count = count() by day_end
| sort by day_end asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20400%20%7C%20extend%20day_end%20%3D%20endofday\(_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20day_end%20%7C%20sort%20by%20day_end%20asc%22%7D)
**Output**
| day\_end | error\_count |
| ---------------------------- | ------------ |
| 2024-11-14T23:59:59.9999999Z | 67 |
| 2024-11-15T23:59:59.9999999Z | 43 |
| 2024-11-16T23:59:59.9999999Z | 89 |
This query counts HTTP errors by end-of-day boundaries, helping you monitor daily error trends and detect spikes.
## List of related functions [#list-of-related-functions]
* [startofday](/apl/scalar-functions/datetime-functions/startofday): Returns the start of the day for a datetime, useful for defining the beginning of daily intervals.
* [endofweek](/apl/scalar-functions/datetime-functions/endofweek): Returns the end of the week for a datetime.
* [endofmonth](/apl/scalar-functions/datetime-functions/endofmonth): Returns the end of the month for a datetime.
* [endofyear](/apl/scalar-functions/datetime-functions/endofyear): Returns the end of the year for a datetime.
* [now](/apl/scalar-functions/datetime-functions/now): Returns the current datetime, useful for calculating time until end of day.
## Other query languages [#other-query-languages]
In Splunk SPL, there is no direct equivalent to `endofday`. You typically use `relative_time` with snap-to syntax such as `@d+1d-1s` to approximate the end of the day. In APL, the `endofday` function handles this directly.
```sql Splunk example
... | eval end=relative_time(_time, "@d+1d-1s")
```
```kusto APL equivalent
... | extend end = endofday(_time)
```
In ANSI SQL, you typically combine `DATE_TRUNC` with interval arithmetic to get the end of the day. In APL, the `endofday` function provides this directly and supports an optional day offset.
```sql SQL example
SELECT DATE_TRUNC('day', timestamp_column) + INTERVAL '1 day' - INTERVAL '1 second' AS end_of_day FROM events;
```
```kusto APL equivalent
['dataset']
| extend end_of_day = endofday(_time)
```
---
# endofmonth
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/endofmonth
Use the `endofmonth` function in APL to calculate the end of the month for a given datetime value. The function returns a datetime set to the last moment of the final day of the month (23:59:59.9999999), with an optional offset to shift forward or backward by a specified number of months.
You can use `endofmonth` to create monthly time boundaries for aggregation, billing cycles, and reporting. This is especially useful when you need to bucket events into monthly intervals or define month-end deadlines.
Use it when you want to:
* Define end-of-month boundaries for monthly reports and dashboards.
* Aggregate events to monthly intervals for billing or usage analysis.
* Build monthly summaries across log, trace, or security datasets.
## Usage [#usage]
### Syntax [#syntax]
```kusto
endofmonth(datetime [, offset])
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | --------------------------------------------------------------------------- |
| datetime | `datetime` | The input datetime value. |
| offset | `long` | Optional: The number of months to offset from the input date. Default is 0. |
### Returns [#returns]
A `datetime` representing the last moment of the month for the given date, shifted by the offset if specified.
## Use case examples [#use-case-examples]
Count requests by month boundary to track monthly traffic volume.
**Query**
```kusto
['sample-http-logs']
| extend month_end = endofmonth(_time)
| summarize total_requests = count() by month_end
| sort by month_end asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20month_end%20%3D%20endofmonth\(_time\)%20%7C%20summarize%20total_requests%20%3D%20count\(\)%20by%20month_end%20%7C%20sort%20by%20month_end%20asc%22%7D)
**Output**
| month\_end | total\_requests |
| ---------------------------- | --------------- |
| 2024-10-31T23:59:59.9999999Z | 18432 |
| 2024-11-30T23:59:59.9999999Z | 19871 |
| 2024-12-31T23:59:59.9999999Z | 17654 |
This query groups HTTP log events by end-of-month boundaries and counts the total requests in each month.
Track monthly average trace durations for each service to identify long-term performance trends.
**Query**
```kusto
['otel-demo-traces']
| extend month_end = endofmonth(_time)
| summarize avg_duration = avg(duration) by month_end, ['service.name']
| sort by month_end asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20month_end%20%3D%20endofmonth\(_time\)%20%7C%20summarize%20avg_duration%20%3D%20avg\(duration\)%20by%20month_end%2C%20%5B'service.name'%5D%20%7C%20sort%20by%20month_end%20asc%22%7D)
**Output**
| month\_end | service.name | avg\_duration |
| ---------------------------- | ------------ | ---------------- |
| 2024-10-31T23:59:59.9999999Z | frontend | 00:00:01.2150000 |
| 2024-11-30T23:59:59.9999999Z | frontend | 00:00:01.2780000 |
| 2024-12-31T23:59:59.9999999Z | frontend | 00:00:01.1930000 |
This query shows how average span duration changes month by month for each service, helping you spot long-term performance shifts.
Identify monthly error spikes to detect months with elevated server failure rates.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 500
| extend month_end = endofmonth(_time)
| summarize error_count = count() by month_end
| sort by month_end asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20500%20%7C%20extend%20month_end%20%3D%20endofmonth\(_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20month_end%20%7C%20sort%20by%20month_end%20asc%22%7D)
**Output**
| month\_end | error\_count |
| ---------------------------- | ------------ |
| 2024-10-31T23:59:59.9999999Z | 187 |
| 2024-11-30T23:59:59.9999999Z | 234 |
| 2024-12-31T23:59:59.9999999Z | 162 |
This query counts server errors by month to help you identify months with unusually high failure rates.
## List of related functions [#list-of-related-functions]
* [startofmonth](/apl/scalar-functions/datetime-functions/startofmonth): Returns the start of the month for a datetime, useful for defining the beginning of monthly intervals.
* [endofday](/apl/scalar-functions/datetime-functions/endofday): Returns the end of the day for a datetime.
* [endofweek](/apl/scalar-functions/datetime-functions/endofweek): Returns the end of the week for a datetime.
* [endofyear](/apl/scalar-functions/datetime-functions/endofyear): Returns the end of the year for a datetime.
* [monthofyear](/apl/scalar-functions/datetime-functions/monthofyear): Returns the month number from a datetime, useful for month-based grouping.
## Other query languages [#other-query-languages]
In Splunk SPL, there is no direct equivalent to `endofmonth`. You typically use manual date math with `eval` and `relative_time` to calculate the last day of the month. In APL, the `endofmonth` function handles this directly and supports an optional month offset.
```sql Splunk example
... | eval month_end=relative_time(now(), "@mon+1mon-1d@d+86399")
```
```kusto APL equivalent
... | extend month_end = endofmonth(_time)
```
In ANSI SQL, you often use `LAST_DAY(timestamp)` or combine `DATE_TRUNC` with interval arithmetic to get the end of the month. In APL, the `endofmonth` function provides this directly and supports an optional month offset.
```sql SQL example
SELECT DATE_TRUNC('month', timestamp_column) + INTERVAL '1 month' - INTERVAL '1 second' AS month_end FROM events;
```
```kusto APL equivalent
['dataset']
| extend month_end = endofmonth(_time)
```
---
# endofweek
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/endofweek
Use the `endofweek` function in APL to calculate the end of the week for a given datetime value. The function returns a datetime set to the last moment of Saturday (23:59:59.9999999), with an optional offset to shift forward or backward by a specified number of weeks.
You can use `endofweek` to create weekly time boundaries for aggregation, reporting, and trend analysis. This is especially useful when you need to bucket events into weekly intervals or define weekly reporting windows.
Use it when you want to:
* Define end-of-week boundaries for weekly reports and dashboards.
* Aggregate events to weekly intervals for trend analysis.
* Build weekly summaries across log, trace, or security datasets.
## Usage [#usage]
### Syntax [#syntax]
```kusto
endofweek(datetime [, offset])
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | -------------------------------------------------------------------------- |
| datetime | `datetime` | The input datetime value. |
| offset | `long` | Optional: The number of weeks to offset from the input date. Default is 0. |
### Returns [#returns]
A `datetime` representing the end of the week (Saturday 23:59:59.9999999) for the given date, shifted by the offset if specified.
## Use case examples [#use-case-examples]
Summarize total requests per week to track weekly traffic volume.
**Query**
```kusto
['sample-http-logs']
| extend week_end = endofweek(_time)
| summarize total_requests = count() by week_end
| sort by week_end asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20week_end%20%3D%20endofweek\(_time\)%20%7C%20summarize%20total_requests%20%3D%20count\(\)%20by%20week_end%20%7C%20sort%20by%20week_end%20asc%22%7D)
**Output**
| week\_end | total\_requests |
| ---------------------------- | --------------- |
| 2024-11-16T23:59:59.9999999Z | 4521 |
| 2024-11-23T23:59:59.9999999Z | 4837 |
| 2024-11-30T23:59:59.9999999Z | 4392 |
This query groups HTTP log events by end-of-week boundaries and counts the total requests in each week.
Track weekly average span duration for each service to monitor performance trends over time.
**Query**
```kusto
['otel-demo-traces']
| extend week_end = endofweek(_time)
| summarize avg_duration = avg(duration) by week_end, ['service.name']
| sort by week_end asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20week_end%20%3D%20endofweek\(_time\)%20%7C%20summarize%20avg_duration%20%3D%20avg\(duration\)%20by%20week_end%2C%20%5B'service.name'%5D%20%7C%20sort%20by%20week_end%20asc%22%7D)
**Output**
| week\_end | service.name | avg\_duration |
| ---------------------------- | ------------ | ---------------- |
| 2024-11-16T23:59:59.9999999Z | frontend | 00:00:01.2430000 |
| 2024-11-23T23:59:59.9999999Z | frontend | 00:00:01.1870000 |
| 2024-11-30T23:59:59.9999999Z | frontend | 00:00:01.3010000 |
This query shows how average span duration changes week by week for each service, helping you spot performance regressions.
Monitor weekly error trends to detect sustained increases in server failures.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 500
| extend week_end = endofweek(_time)
| summarize error_count = count() by week_end
| sort by week_end asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20500%20%7C%20extend%20week_end%20%3D%20endofweek\(_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20week_end%20%7C%20sort%20by%20week_end%20asc%22%7D)
**Output**
| week\_end | error\_count |
| ---------------------------- | ------------ |
| 2024-11-16T23:59:59.9999999Z | 52 |
| 2024-11-23T23:59:59.9999999Z | 78 |
| 2024-11-30T23:59:59.9999999Z | 41 |
This query counts server errors by week to help you identify weeks with elevated failure rates.
## List of related functions [#list-of-related-functions]
* [startofweek](/apl/scalar-functions/datetime-functions/startofweek): Returns the start of the week for a datetime, useful for defining the beginning of weekly intervals.
* [endofday](/apl/scalar-functions/datetime-functions/endofday): Returns the end of the day for a datetime.
* [endofmonth](/apl/scalar-functions/datetime-functions/endofmonth): Returns the end of the month for a datetime.
* [endofyear](/apl/scalar-functions/datetime-functions/endofyear): Returns the end of the year for a datetime.
* [week\_of\_year](/apl/scalar-functions/datetime-functions/week-of-year): Returns the ISO 8601 week number, useful for week-based grouping.
## Other query languages [#other-query-languages]
In Splunk SPL, there is no direct equivalent to `endofweek`. You typically use manual date arithmetic with `eval` and `relative_time` to calculate the end of the week. In APL, the `endofweek` function handles this directly and supports an optional week offset.
```sql Splunk example
... | eval week_end=relative_time(now(), "@w7+6d@d+86399")
```
```kusto APL equivalent
... | extend week_end = endofweek(_time)
```
In ANSI SQL, you typically combine `DATE_TRUNC` with interval arithmetic to calculate the end of the week. The exact behavior depends on how each platform defines the start of the week. In APL, `endofweek` returns the end of the week as Saturday 23:59:59.9999999.
```sql SQL example
SELECT DATE_TRUNC('week', timestamp_column) + INTERVAL '7 days' - INTERVAL '1 second' AS week_end FROM events;
```
```kusto APL equivalent
['dataset']
| extend week_end = endofweek(_time)
```
---
# endofyear
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/endofyear
Use the `endofyear` function in APL to return the end of the year containing a given datetime value. The function returns a datetime representing the last moment of that year (December 31, 23:59:59.9999999).
You can use `endofyear` to align events to year-end boundaries, which is useful for annual aggregation, fiscal year analysis, and year-over-year comparisons in dashboards.
Use it when you want to:
* Group events by year-end boundaries for annual reporting.
* Compare activity or metrics across calendar years.
* Align timestamps to the end of the year for time-series bucketing.
## Usage [#usage]
### Syntax [#syntax]
```kusto
endofyear(datetime [, offset])
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | -------------------------------------------------------------------------------- |
| datetime | `datetime` | The input datetime value. |
| offset | `long` | Optional: The number of years to offset from the input datetime. Default is `0`. |
### Returns [#returns]
A `datetime` representing the end of the year for the given date, shifted by the offset if specified. The return value is December 31 at 23:59:59.9999999 of the input year.
## Use case examples [#use-case-examples]
Count total HTTP requests per year to understand annual traffic volume.
**Query**
```kusto
['sample-http-logs']
| extend year_end = endofyear(_time)
| summarize total_requests = count() by year_end
| sort by year_end asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20year_end%20%3D%20endofyear\(_time\)%20%7C%20summarize%20total_requests%20%3D%20count\(\)%20by%20year_end%20%7C%20sort%20by%20year_end%20asc%22%7D)
**Output**
| year\_end | total\_requests |
| ---------------------------- | --------------- |
| 2024-12-31T23:59:59.9999999Z | 1523 |
| 2025-12-31T23:59:59.9999999Z | 2841 |
This query groups each HTTP log entry by its year-end boundary and counts the total number of requests per year.
Track yearly trace volume by service to compare annual activity across services.
**Query**
```kusto
['otel-demo-traces']
| extend year_end = endofyear(_time)
| summarize trace_count = count() by year_end, ['service.name']
| sort by year_end asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20year_end%20%3D%20endofyear\(_time\)%20%7C%20summarize%20trace_count%20%3D%20count\(\)%20by%20year_end%2C%20%5B'service.name'%5D%20%7C%20sort%20by%20year_end%20asc%22%7D)
**Output**
| year\_end | service.name | trace\_count |
| ---------------------------- | ------------ | ------------ |
| 2024-12-31T23:59:59.9999999Z | frontend | 5320 |
| 2024-12-31T23:59:59.9999999Z | cart | 2150 |
| 2025-12-31T23:59:59.9999999Z | frontend | 6100 |
This query counts traces per service per year, using year-end boundaries for grouping.
Summarize yearly error totals to identify years with elevated server error rates.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 500
| extend year_end = endofyear(_time)
| summarize error_count = count() by year_end
| sort by year_end asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20500%20%7C%20extend%20year_end%20%3D%20endofyear\(_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20year_end%20%7C%20sort%20by%20year_end%20asc%22%7D)
**Output**
| year\_end | error\_count |
| ---------------------------- | ------------ |
| 2024-12-31T23:59:59.9999999Z | 187 |
| 2025-12-31T23:59:59.9999999Z | 342 |
This query filters for server errors and groups them by year-end boundary to reveal annual error totals.
## List of related functions [#list-of-related-functions]
* [startofyear](/apl/scalar-functions/datetime-functions/startofyear): Returns the start of the year for a datetime value.
* [endofmonth](/apl/scalar-functions/datetime-functions/endofmonth): Returns the end of the month for a datetime, useful for monthly boundary calculations.
* [endofweek](/apl/scalar-functions/datetime-functions/endofweek): Returns the end of the week for a datetime value.
* [endofday](/apl/scalar-functions/datetime-functions/endofday): Returns the end of the day for a datetime value.
* [getyear](/apl/scalar-functions/datetime-functions/getyear): Extracts the year part from a datetime as an integer.
## Other query languages [#other-query-languages]
In Splunk SPL, there is no direct equivalent to `endofyear`. You typically need to extract the year and manually construct the year-end timestamp. In APL, the `endofyear` function returns the last moment of the year in a single call.
```sql Splunk example
... | eval year=strftime(_time, "%Y") | eval year_end=year."-12-31T23:59:59"
```
```kusto APL equivalent
... | extend year_end = endofyear(_time)
```
In ANSI SQL, you can calculate the end of the year by truncating to the year and adding an interval. Different SQL platforms offer varying syntax for this. In APL, `endofyear` provides this in a single function call.
```sql SQL example
SELECT DATE_TRUNC('year', timestamp_column) + INTERVAL '1 year' - INTERVAL '1 second' AS year_end FROM events;
```
```kusto APL equivalent
['dataset']
| extend year_end = endofyear(_time)
```
---
# getmonth
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/getmonth
Use the `getmonth` function in APL to extract the month number from a datetime value. The function returns an integer from 1 to 12, where 1 represents January and 12 represents December.
You can use `getmonth` to group records by month when analyzing seasonal patterns, monthly trends, or periodic fluctuations in your data. This is useful for dashboards, monthly reporting, and cohort analysis.
Use it when you want to:
* Aggregate events by month for trend analysis.
* Compare metrics across months to detect seasonal patterns.
* Create monthly summaries across log, trace, or security datasets.
## Usage [#usage]
### Syntax [#syntax]
```kusto
getmonth(datetime)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | ------------------------- |
| datetime | `datetime` | The input datetime value. |
### Returns [#returns]
An `int` from 1 to 12 representing the month number.
## Use case examples [#use-case-examples]
Analyze HTTP request volume by month to identify traffic patterns throughout the year.
**Query**
```kusto
['sample-http-logs']
| extend month = getmonth(_time)
| summarize request_count = count() by month
| sort by month asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20month%20%3D%20getmonth\(_time\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20month%20%7C%20sort%20by%20month%20asc%22%7D)
**Output**
| month | request\_count |
| ----- | -------------- |
| 1 | 4521 |
| 2 | 4187 |
| 3 | 4893 |
This query groups HTTP log entries by month number and counts the requests in each month.
Compare monthly average span durations by service to spot performance changes across months.
**Query**
```kusto
['otel-demo-traces']
| extend month = getmonth(_time)
| summarize avg_duration = avg(duration) by month, ['service.name']
| sort by month asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20month%20%3D%20getmonth\(_time\)%20%7C%20summarize%20avg_duration%20%3D%20avg\(duration\)%20by%20month%2C%20%5B'service.name'%5D%20%7C%20sort%20by%20month%20asc%22%7D)
**Output**
| month | service.name | avg\_duration |
| ----- | ------------ | ---------------- |
| 1 | frontend | 00:00:01.2340000 |
| 2 | frontend | 00:00:01.1890000 |
| 3 | frontend | 00:00:01.3020000 |
This query calculates the average span duration per month for each service, helping you track monthly performance trends.
Find which months have the most server errors to uncover seasonal reliability patterns.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 500
| extend month = getmonth(_time)
| summarize error_count = count() by month
| sort by error_count desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20500%20%7C%20extend%20month%20%3D%20getmonth\(_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20month%20%7C%20sort%20by%20error_count%20desc%22%7D)
**Output**
| month | error\_count |
| ----- | ------------ |
| 7 | 89 |
| 12 | 76 |
| 3 | 54 |
This query identifies the months with the highest number of server errors, sorted by error count in descending order.
## List of related functions [#list-of-related-functions]
* [monthofyear](/apl/scalar-functions/datetime-functions/monthofyear): Returns the month number from a datetime. Equivalent to `getmonth`.
* [getyear](/apl/scalar-functions/datetime-functions/getyear): Extracts the year part from a datetime as an integer.
* [dayofmonth](/apl/scalar-functions/datetime-functions/dayofmonth): Returns the day of the month from a datetime.
* [startofmonth](/apl/scalar-functions/datetime-functions/startofmonth): Returns the start of the month for a datetime, useful for monthly binning.
* [endofmonth](/apl/scalar-functions/datetime-functions/endofmonth): Returns the end of the month for a datetime value.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use the `strftime` function with the `%m` specifier to extract the month number. In APL, the `getmonth` function directly returns the month number as an integer.
```sql Splunk example
... | eval month=strftime(_time, "%m")
```
```kusto APL equivalent
... | extend month = getmonth(_time)
```
In ANSI SQL, you use `EXTRACT(MONTH FROM timestamp)` or the `MONTH()` function to get the month number. In APL, `getmonth` provides the same result.
```sql SQL example
SELECT EXTRACT(MONTH FROM timestamp_column) AS month FROM events;
```
```kusto APL equivalent
['dataset']
| extend month = getmonth(_time)
```
---
# getyear
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/getyear
Use the `getyear` function in APL to extract the year part from a datetime value. The function returns an integer representing the calendar year.
You can use `getyear` to group records by year when analyzing multi-year trends, comparing annual performance, or building year-level summaries. This is useful for dashboards, long-term reporting, and historical analysis.
Use it when you want to:
* Aggregate events by year for trend analysis.
* Compare metrics across years to detect growth or decline.
* Create year-level summaries across log, trace, or security datasets.
## Usage [#usage]
### Syntax [#syntax]
```kusto
getyear(datetime)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | ------------------------- |
| datetime | `datetime` | The input datetime value. |
### Returns [#returns]
An `int` representing the year.
## Use case examples [#use-case-examples]
Count HTTP requests by year to understand long-term traffic trends.
**Query**
```kusto
['sample-http-logs']
| extend year = getyear(_time)
| summarize request_count = count() by year
| sort by year asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20year%20%3D%20getyear\(_time\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20year%20%7C%20sort%20by%20year%20asc%22%7D)
**Output**
| year | request\_count |
| ---- | -------------- |
| 2024 | 52340 |
| 2025 | 61892 |
This query groups HTTP log entries by year and counts the total requests per year.
Compare trace volume across years by service to track annual growth in observability data.
**Query**
```kusto
['otel-demo-traces']
| extend year = getyear(_time)
| summarize trace_count = count() by year, ['service.name']
| sort by year asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20year%20%3D%20getyear\(_time\)%20%7C%20summarize%20trace_count%20%3D%20count\(\)%20by%20year%2C%20%5B'service.name'%5D%20%7C%20sort%20by%20year%20asc%22%7D)
**Output**
| year | service.name | trace\_count |
| ---- | ------------ | ------------ |
| 2024 | frontend | 12450 |
| 2024 | cart | 5320 |
| 2025 | frontend | 14780 |
This query counts traces per service per year, showing how trace volume changes annually for each service.
Track yearly error trends to identify whether error rates increase or decrease over time.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 400
| extend year = getyear(_time)
| summarize error_count = count() by year
| sort by year asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20400%20%7C%20extend%20year%20%3D%20getyear\(_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20year%20%7C%20sort%20by%20year%20asc%22%7D)
**Output**
| year | error\_count |
| ---- | ------------ |
| 2024 | 1245 |
| 2025 | 1587 |
This query filters for HTTP errors (status 400 and above) and counts them per year to reveal annual error trends.
## List of related functions [#list-of-related-functions]
* [getmonth](/apl/scalar-functions/datetime-functions/getmonth): Extracts the month number from a datetime as an integer.
* [dayofyear](/apl/scalar-functions/datetime-functions/dayofyear): Returns the day number within the year from a datetime.
* [monthofyear](/apl/scalar-functions/datetime-functions/monthofyear): Returns the month number from a datetime. Equivalent to `getmonth`.
* [startofyear](/apl/scalar-functions/datetime-functions/startofyear): Returns the start of the year for a datetime, useful for year-level binning.
* [endofyear](/apl/scalar-functions/datetime-functions/endofyear): Returns the end of the year for a datetime value.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use the `strftime` function with the `%Y` specifier to extract the four-digit year. In APL, the `getyear` function directly returns the year as an integer.
```sql Splunk example
... | eval year=strftime(_time, "%Y")
```
```kusto APL equivalent
... | extend year = getyear(_time)
```
In ANSI SQL, you use `EXTRACT(YEAR FROM timestamp)` or the `YEAR()` function to get the year. In APL, `getyear` provides the same result.
```sql SQL example
SELECT EXTRACT(YEAR FROM timestamp_column) AS year FROM events;
```
```kusto APL equivalent
['dataset']
| extend year = getyear(_time)
```
---
# hourofday
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/hourofday
Use the `hourofday` function in APL to extract the hour of the day from a datetime value. The function returns an integer from 0 to 23, where 0 represents midnight and 23 represents 11 PM.
You can use `hourofday` to group records by hour for time-of-day analysis, peak traffic identification, and intraday pattern detection. This is useful for operational dashboards, capacity planning, and anomaly detection.
Use it when you want to:
* Identify peak traffic hours in your services.
* Analyze hourly patterns in request volume or error rates.
* Create time-of-day summaries across log, trace, or security datasets.
## Usage [#usage]
### Syntax [#syntax]
```kusto
hourofday(datetime)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | ------------------------- |
| datetime | `datetime` | The input datetime value. |
### Returns [#returns]
An `int` from 0 to 23 representing the hour of the day.
## Use case examples [#use-case-examples]
Analyze HTTP request volume by hour to identify peak traffic periods.
**Query**
```kusto
['sample-http-logs']
| extend hour = hourofday(_time)
| summarize request_count = count() by hour
| sort by hour asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20hour%20%3D%20hourofday\(_time\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20hour%20%7C%20sort%20by%20hour%20asc%22%7D)
**Output**
| hour | request\_count |
| ---- | -------------- |
| 0 | 312 |
| 1 | 287 |
| 14 | 1523 |
This query groups HTTP log entries by hour and counts the requests per hour, revealing peak and off-peak periods.
Find peak hours for trace activity by service to understand when services experience the highest load.
**Query**
```kusto
['otel-demo-traces']
| extend hour = hourofday(_time)
| summarize trace_count = count() by hour, ['service.name']
| sort by hour asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20hour%20%3D%20hourofday\(_time\)%20%7C%20summarize%20trace_count%20%3D%20count\(\)%20by%20hour%2C%20%5B'service.name'%5D%20%7C%20sort%20by%20hour%20asc%22%7D)
**Output**
| hour | service.name | trace\_count |
| ---- | ------------ | ------------ |
| 9 | frontend | 2450 |
| 10 | frontend | 2780 |
| 14 | cart | 1340 |
This query shows the hourly distribution of traces per service, helping you identify when each service is busiest.
Detect after-hours error spikes by analyzing the hourly distribution of HTTP errors.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 400
| extend hour = hourofday(_time)
| summarize error_count = count() by hour
| sort by hour asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20400%20%7C%20extend%20hour%20%3D%20hourofday\(_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20hour%20%7C%20sort%20by%20hour%20asc%22%7D)
**Output**
| hour | error\_count |
| ---- | ------------ |
| 2 | 87 |
| 3 | 92 |
| 15 | 34 |
This query reveals the hourly pattern of HTTP errors, helping you detect unusual activity during off-peak hours.
## List of related functions [#list-of-related-functions]
* [dayofweek](/apl/scalar-functions/datetime-functions/dayofweek): Returns the day of the week as a timespan, complementing hourly analysis with day-level detail.
* [dayofmonth](/apl/scalar-functions/datetime-functions/dayofmonth): Returns the day of the month from a datetime.
* [datetime-part](/apl/scalar-functions/datetime-functions/datetime-part): Extracts a specific date part (such as hour) as an integer.
* [startofday](/apl/scalar-functions/datetime-functions/startofday): Returns the start of the day for a datetime, useful for daily binning.
* [endofday](/apl/scalar-functions/datetime-functions/endofday): Returns the end of the day for a datetime value.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use the `strftime` function with the `%H` specifier to extract the hour of the day. In APL, the `hourofday` function directly returns the hour as an integer.
```sql Splunk example
... | eval hour=strftime(_time, "%H")
```
```kusto APL equivalent
... | extend hour = hourofday(_time)
```
In ANSI SQL, you use `EXTRACT(HOUR FROM timestamp)` or the `HOUR()` function to get the hour. In APL, `hourofday` provides the same result.
```sql SQL example
SELECT EXTRACT(HOUR FROM timestamp_column) AS hour FROM events;
```
```kusto APL equivalent
['dataset']
| extend hour = hourofday(_time)
```
---
# monthofyear
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/monthofyear
Use the `monthofyear` function in APL to extract the month number from a datetime value. The function returns an integer from 1 to 12, where 1 represents January and 12 represents December.
The `monthofyear` and `getmonth` functions return the same result. Use either interchangeably.
You can use `monthofyear` to group records by month when analyzing seasonal patterns, month-over-month comparisons, or periodic trends. This is useful for dashboards, monthly reporting, and cohort analysis.
Use it when you want to:
* Aggregate events by month for seasonal trend analysis.
* Compare metrics month over month to detect recurring patterns.
* Create monthly summaries across log, trace, or security datasets.
## Usage [#usage]
### Syntax [#syntax]
```kusto
monthofyear(datetime)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | ------------------------- |
| datetime | `datetime` | The input datetime value. |
### Returns [#returns]
An `int` from 1 to 12 representing the month number of the year.
## Use case examples [#use-case-examples]
Analyze average request duration by month to identify seasonal performance variations.
**Query**
```kusto
['sample-http-logs']
| extend month = monthofyear(_time)
| summarize avg_duration = avg(req_duration_ms) by month
| sort by month asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20month%20%3D%20monthofyear\(_time\)%20%7C%20summarize%20avg_duration%20%3D%20avg\(req_duration_ms\)%20by%20month%20%7C%20sort%20by%20month%20asc%22%7D)
**Output**
| month | avg\_duration |
| ----- | ------------- |
| 1 | 243.8 |
| 2 | 238.5 |
| 3 | 251.2 |
This query calculates the average request duration per month, helping you spot months with degraded performance.
Compare monthly span counts per service to understand how trace volume fluctuates across months.
**Query**
```kusto
['otel-demo-traces']
| extend month = monthofyear(_time)
| summarize span_count = count() by month, ['service.name']
| sort by month asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20month%20%3D%20monthofyear\(_time\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20month%2C%20%5B'service.name'%5D%20%7C%20sort%20by%20month%20asc%22%7D)
**Output**
| month | service.name | span\_count |
| ----- | ------------ | ----------- |
| 1 | frontend | 4520 |
| 1 | cart | 2150 |
| 2 | frontend | 4830 |
This query counts spans per service per month, showing monthly variations in trace activity for each service.
Identify seasonal patterns in server error rates by counting errors per month.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 500
| extend month = monthofyear(_time)
| summarize error_count = count() by month
| sort by month asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20500%20%7C%20extend%20month%20%3D%20monthofyear\(_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20month%20%7C%20sort%20by%20month%20asc%22%7D)
**Output**
| month | error\_count |
| ----- | ------------ |
| 1 | 42 |
| 2 | 38 |
| 3 | 56 |
This query reveals the monthly distribution of server errors, helping you identify months with elevated failure rates.
## List of related functions [#list-of-related-functions]
* [getmonth](/apl/scalar-functions/datetime-functions/getmonth): Returns the month number from a datetime. Equivalent to `monthofyear`.
* [getyear](/apl/scalar-functions/datetime-functions/getyear): Extracts the year part from a datetime as an integer.
* [dayofmonth](/apl/scalar-functions/datetime-functions/dayofmonth): Returns the day of the month from a datetime.
* [startofmonth](/apl/scalar-functions/datetime-functions/startofmonth): Returns the start of the month for a datetime, useful for monthly binning.
* [endofmonth](/apl/scalar-functions/datetime-functions/endofmonth): Returns the end of the month for a datetime value.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use the `strftime` function with the `%m` specifier to extract the month number. In APL, the `monthofyear` function directly returns the month number as an integer.
```sql Splunk example
... | eval month=strftime(_time, "%m")
```
```kusto APL equivalent
... | extend month = monthofyear(_time)
```
In ANSI SQL, you use `EXTRACT(MONTH FROM timestamp)` or the `MONTH()` function to get the month number. In APL, `monthofyear` provides the same result.
```sql SQL example
SELECT EXTRACT(MONTH FROM timestamp_column) AS month FROM events;
```
```kusto APL equivalent
['dataset']
| extend month = monthofyear(_time)
```
---
# now
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/now
Use the `now` function in APL to return the current UTC clock time as a `datetime` value, optionally offset by a given timespan. `now` is evaluated once at the start of the query and returns the same fixed value for the entire duration of the query, regardless of how long the query takes to run. This means all uses of `now` within a query refer to the same point in time.
You can use `now` to calculate relative times, filter events by recency, and compute the age of records in your dataset.
Use it when you want to:
* Filter events to a recent time window.
* Calculate how long ago an event occurred.
* Add the current timestamp to query output for audit or comparison purposes.
## Usage [#usage]
### Syntax [#syntax]
```kusto
now([offset])
```
### Parameters [#parameters]
| Name | Type | Description |
| ------ | ---------- | ------------------------------------------------------------------------- |
| offset | `timespan` | Optional: A timespan added to the current UTC clock time. Default is `0`. |
### Returns [#returns]
The current UTC clock time as a `datetime`. All references to `now()` within a single statement return the same value.
## Use case examples [#use-case-examples]
Calculate the age of each request in hours to understand how recent events are.
**Query**
```kusto
['sample-http-logs']
| extend age_hours = datetime_diff('hour', now(), _time)
| project _time, age_hours, method, status
| take 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20age_hours%20%3D%20datetime_diff\('hour'%2C%20now\(\)%2C%20_time\)%20%7C%20project%20_time%2C%20age_hours%2C%20method%2C%20status%20%7C%20take%2010%22%7D)
**Output**
| \_time | age\_hours | method | status |
| -------------------- | ---------- | ------ | ------ |
| 2025-01-15T10:00:00Z | 48 | GET | 200 |
| 2025-01-15T10:05:00Z | 47 | POST | 201 |
| 2025-01-15T10:10:00Z | 47 | GET | 500 |
This query calculates how many hours ago each HTTP request occurred by comparing the event time to the current time.
Find traces from the last 5 minutes to monitor recent activity by service.
**Query**
```kusto
['otel-demo-traces']
| where _time > now(-5m)
| summarize trace_count = count() by ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20_time%20%3E%20now\(-5m\)%20%7C%20summarize%20trace_count%20%3D%20count\(\)%20by%20%5B'service.name'%5D%22%7D)
**Output**
| service.name | trace\_count |
| ------------ | ------------ |
| frontend | 120 |
| cart | 85 |
| checkout | 42 |
This query filters traces to those generated in the last 5 minutes and counts them by service name.
Show the current time alongside each failed request to calculate how many minutes have passed since the event.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 400
| extend current_time = now()
| extend time_since = datetime_diff('minute', current_time, _time)
| project _time, current_time, time_since, status, uri
| take 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20400%20%7C%20extend%20current_time%20%3D%20now\(\)%20%7C%20extend%20time_since%20%3D%20datetime_diff\('minute'%2C%20current_time%2C%20_time\)%20%7C%20project%20_time%2C%20current_time%2C%20time_since%2C%20status%2C%20uri%20%7C%20take%2010%22%7D)
**Output**
| \_time | current\_time | time\_since | status | uri |
| -------------------- | -------------------- | ----------- | ------ | ---------- |
| 2025-01-15T10:00:00Z | 2025-01-17T10:00:00Z | 2880 | 403 | /admin |
| 2025-01-15T10:05:00Z | 2025-01-17T10:00:00Z | 2875 | 500 | /api/users |
| 2025-01-15T10:10:00Z | 2025-01-17T10:00:00Z | 2870 | 404 | /missing |
This query adds the current timestamp to each record and calculates the elapsed time in minutes since each failed request occurred.
## List of related functions [#list-of-related-functions]
* [ago](/apl/scalar-functions/datetime-functions/ago): Subtracts a given timespan from the current UTC clock time.
* [datetime\_add](/apl/scalar-functions/datetime-functions/datetime-add): Adds a specified amount to a datetime value.
* [datetime\_diff](/apl/scalar-functions/datetime-functions/datetime-diff): Calculates the difference between two datetime values.
* [startofday](/apl/scalar-functions/datetime-functions/startofday): Returns the start of the day for a datetime value.
* [endofday](/apl/scalar-functions/datetime-functions/endofday): Returns the end of the day for a datetime value.
## Other query languages [#other-query-languages]
In Splunk SPL, `now()` returns the current time as a Unix timestamp. In APL, `now()` returns a `datetime` value in UTC and supports an optional timespan offset to shift the returned time forward or backward.
```sql Splunk example
... | eval current_time=now()
```
```kusto APL equivalent
... | extend current_time = now()
```
In ANSI SQL, you use `CURRENT_TIMESTAMP` or `NOW()` to retrieve the current date and time. In APL, `now()` behaves similarly but also accepts an optional timespan offset to shift the returned time.
```sql SQL example
SELECT CURRENT_TIMESTAMP AS current_time;
```
```kusto APL equivalent
['dataset']
| extend current_time = now()
```
---
# startofday
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/startofday
Use the `startofday` function in APL to round a datetime value down to the start of the day. The function returns midnight (00:00:00) for the date that contains the given datetime value. You can optionally shift the result by a specified number of days using the offset parameter.
You can use `startofday` to bin events into daily buckets for aggregation, reporting, and trend analysis across log, trace, and security datasets.
Use it when you want to:
* Group events by day for daily summaries and dashboards.
* Align timestamps to day boundaries for consistent aggregation.
* Compare metrics across different days.
## Usage [#usage]
### Syntax [#syntax]
```kusto
startofday(datetime [, offset])
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | ------------------------------------------------------------------------------- |
| datetime | `datetime` | The input datetime value. |
| offset | `long` | Optional: The number of days to offset from the input datetime. Default is `0`. |
### Returns [#returns]
A `datetime` representing the start of the day (00:00:00) for the given date value, shifted by the offset if specified.
## Use case examples [#use-case-examples]
Count requests per day to identify daily traffic patterns.
**Query**
```kusto
['sample-http-logs']
| extend day_start = startofday(_time)
| summarize request_count = count() by day_start
| sort by day_start asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20day_start%20%3D%20startofday\(_time\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20day_start%20%7C%20sort%20by%20day_start%20asc%22%7D)
**Output**
| day\_start | request\_count |
| -------------------- | -------------- |
| 2025-01-13T00:00:00Z | 1523 |
| 2025-01-14T00:00:00Z | 1687 |
| 2025-01-15T00:00:00Z | 1445 |
This query bins each HTTP request to the start of its day and counts the total requests per day.
Calculate the daily average span duration for each service.
**Query**
```kusto
['otel-demo-traces']
| extend day_start = startofday(_time)
| summarize avg_duration = avg(duration) by day_start, ['service.name']
| sort by day_start asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20day_start%20%3D%20startofday\(_time\)%20%7C%20summarize%20avg_duration%20%3D%20avg\(duration\)%20by%20day_start%2C%20%5B'service.name'%5D%20%7C%20sort%20by%20day_start%20asc%22%7D)
**Output**
| day\_start | service.name | avg\_duration |
| -------------------- | ------------ | ---------------- |
| 2025-01-13T00:00:00Z | frontend | 00:00:01.2340000 |
| 2025-01-14T00:00:00Z | frontend | 00:00:01.1750000 |
| 2025-01-15T00:00:00Z | frontend | 00:00:01.2890000 |
This query groups trace spans by day and service, then calculates the average span duration for each combination.
Track daily error counts to identify days with unusual server error activity.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 500
| extend day_start = startofday(_time)
| summarize error_count = count() by day_start
| sort by day_start asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20500%20%7C%20extend%20day_start%20%3D%20startofday\(_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20day_start%20%7C%20sort%20by%20day_start%20asc%22%7D)
**Output**
| day\_start | error\_count |
| -------------------- | ------------ |
| 2025-01-13T00:00:00Z | 12 |
| 2025-01-14T00:00:00Z | 27 |
| 2025-01-15T00:00:00Z | 8 |
This query filters for server errors and counts them per day to reveal daily error patterns.
## List of related functions [#list-of-related-functions]
* [endofday](/apl/scalar-functions/datetime-functions/endofday): Returns the end of the day for a datetime value.
* [startofweek](/apl/scalar-functions/datetime-functions/startofweek): Returns the start of the week for a datetime value.
* [startofmonth](/apl/scalar-functions/datetime-functions/startofmonth): Returns the start of the month for a datetime value.
* [startofyear](/apl/scalar-functions/datetime-functions/startofyear): Returns the start of the year for a datetime value.
* [bin](/apl/scalar-functions/rounding-functions/bin): Rounds values down to a fixed-size bin, useful for grouping timestamps.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `relative_time` with the `@d` snap-to modifier to round a timestamp to the start of the day. In APL, the `startofday` function achieves the same result and supports an optional day offset.
```sql Splunk example
... | eval day_start=relative_time(_time, "@d")
```
```kusto APL equivalent
... | extend day_start = startofday(_time)
```
In ANSI SQL, you use `DATE_TRUNC('day', timestamp_column)` to truncate a timestamp to the start of the day. In APL, `startofday` provides the same functionality with an optional offset parameter.
```sql SQL example
SELECT DATE_TRUNC('day', timestamp_column) AS day_start FROM events;
```
```kusto APL equivalent
['dataset']
| extend day_start = startofday(_time)
```
---
# startofmonth
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/startofmonth
Use the `startofmonth` function in APL to round a datetime value down to the first day of the month at midnight (00:00:00). You can optionally shift the result by a specified number of months using the offset parameter.
You can use `startofmonth` to bin events into monthly buckets for aggregation, reporting, and trend analysis. This is especially useful for monthly summaries, billing cycle calculations, and long-term trend monitoring.
Use it when you want to:
* Aggregate events or metrics by month.
* Align timestamps to month boundaries for consistent reporting.
* Track month-over-month changes in activity or error rates.
## Usage [#usage]
### Syntax [#syntax]
```kusto
startofmonth(datetime [, offset])
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | --------------------------------------------------------------------------------- |
| datetime | `datetime` | The input datetime value. |
| offset | `long` | Optional: The number of months to offset from the input datetime. Default is `0`. |
### Returns [#returns]
A `datetime` representing the start of the month (first day at 00:00:00) for the given date value, shifted by the offset if specified.
## Use case examples [#use-case-examples]
Count requests per month to identify monthly traffic trends.
**Query**
```kusto
['sample-http-logs']
| extend month_start = startofmonth(_time)
| summarize request_count = count() by month_start
| sort by month_start asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20month_start%20%3D%20startofmonth\(_time\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20month_start%20%7C%20sort%20by%20month_start%20asc%22%7D)
**Output**
| month\_start | request\_count |
| -------------------- | -------------- |
| 2024-11-01T00:00:00Z | 42350 |
| 2024-12-01T00:00:00Z | 45120 |
| 2025-01-01T00:00:00Z | 38900 |
This query bins each HTTP request to the start of its month and counts the total requests per month.
Track the monthly average span duration for each service.
**Query**
```kusto
['otel-demo-traces']
| extend month_start = startofmonth(_time)
| summarize avg_duration = avg(duration) by month_start, ['service.name']
| sort by month_start asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20month_start%20%3D%20startofmonth\(_time\)%20%7C%20summarize%20avg_duration%20%3D%20avg\(duration\)%20by%20month_start%2C%20%5B'service.name'%5D%20%7C%20sort%20by%20month_start%20asc%22%7D)
**Output**
| month\_start | service.name | avg\_duration |
| -------------------- | ------------ | ---------------- |
| 2024-11-01T00:00:00Z | frontend | 00:00:01.2340000 |
| 2024-12-01T00:00:00Z | frontend | 00:00:01.1750000 |
| 2025-01-01T00:00:00Z | frontend | 00:00:01.2890000 |
This query groups trace spans by month and service, then calculates the average span duration for each combination.
Monitor monthly server error volume to spot months with elevated failure rates.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 500
| extend month_start = startofmonth(_time)
| summarize error_count = count() by month_start
| sort by month_start asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20500%20%7C%20extend%20month_start%20%3D%20startofmonth\(_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20month_start%20%7C%20sort%20by%20month_start%20asc%22%7D)
**Output**
| month\_start | error\_count |
| -------------------- | ------------ |
| 2024-11-01T00:00:00Z | 156 |
| 2024-12-01T00:00:00Z | 203 |
| 2025-01-01T00:00:00Z | 134 |
This query filters for server errors and counts them per month to reveal monthly error patterns.
## List of related functions [#list-of-related-functions]
* [endofmonth](/apl/scalar-functions/datetime-functions/endofmonth): Returns the end of the month for a datetime value.
* [startofday](/apl/scalar-functions/datetime-functions/startofday): Returns the start of the day for a datetime value.
* [startofweek](/apl/scalar-functions/datetime-functions/startofweek): Returns the start of the week for a datetime value.
* [startofyear](/apl/scalar-functions/datetime-functions/startofyear): Returns the start of the year for a datetime value.
* [monthofyear](/apl/scalar-functions/datetime-functions/monthofyear): Returns the month number from a datetime value.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `relative_time` with the `@mon` snap-to modifier to round a timestamp to the start of the month. In APL, the `startofmonth` function achieves the same result and supports an optional month offset.
```sql Splunk example
... | eval month_start=relative_time(_time, "@mon")
```
```kusto APL equivalent
... | extend month_start = startofmonth(_time)
```
In ANSI SQL, you use `DATE_TRUNC('month', timestamp_column)` to truncate a timestamp to the first day of the month. In APL, `startofmonth` provides the same functionality with an optional offset parameter.
```sql SQL example
SELECT DATE_TRUNC('month', timestamp_column) AS month_start FROM events;
```
```kusto APL equivalent
['dataset']
| extend month_start = startofmonth(_time)
```
---
# startofweek
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/startofweek
Use the `startofweek` function in APL to round a datetime value down to the start of the week. The function returns the preceding Sunday at midnight (00:00:00) for the date that contains the given datetime value. You can optionally shift the result by a specified number of weeks using the offset parameter.
You can use `startofweek` to bin events into weekly buckets for aggregation, reporting, and trend analysis across log, trace, and security datasets.
Use it when you want to:
* Group events by week for weekly summaries and dashboards.
* Align timestamps to week boundaries for consistent aggregation.
* Compare metrics across different weeks.
## Usage [#usage]
### Syntax [#syntax]
```kusto
startofweek(datetime [, offset])
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | -------------------------------------------------------------------------------- |
| datetime | `datetime` | The input datetime value. |
| offset | `long` | Optional: The number of weeks to offset from the input datetime. Default is `0`. |
### Returns [#returns]
A `datetime` representing the start of the week (Sunday at 00:00:00) for the given date value, shifted by the offset if specified.
## Use case examples [#use-case-examples]
Count requests per week to identify weekly traffic patterns.
**Query**
```kusto
['sample-http-logs']
| extend week_start = startofweek(_time)
| summarize request_count = count() by week_start
| sort by week_start asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20week_start%20%3D%20startofweek\(_time\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20week_start%20%7C%20sort%20by%20week_start%20asc%22%7D)
**Output**
| week\_start | request\_count |
| -------------------- | -------------- |
| 2025-01-05T00:00:00Z | 10234 |
| 2025-01-12T00:00:00Z | 11587 |
| 2025-01-19T00:00:00Z | 9876 |
This query bins each HTTP request to the start of its week and counts the total requests per week.
Track the weekly average span duration for each service.
**Query**
```kusto
['otel-demo-traces']
| extend week_start = startofweek(_time)
| summarize avg_duration = avg(duration) by week_start, ['service.name']
| sort by week_start asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20week_start%20%3D%20startofweek\(_time\)%20%7C%20summarize%20avg_duration%20%3D%20avg\(duration\)%20by%20week_start%2C%20%5B'service.name'%5D%20%7C%20sort%20by%20week_start%20asc%22%7D)
**Output**
| week\_start | service.name | avg\_duration |
| -------------------- | ------------ | ---------------- |
| 2025-01-05T00:00:00Z | frontend | 00:00:01.2340000 |
| 2025-01-12T00:00:00Z | frontend | 00:00:01.1750000 |
| 2025-01-19T00:00:00Z | frontend | 00:00:01.2890000 |
This query groups trace spans by week and service, then calculates the average span duration for each combination.
Monitor weekly server error trends to detect weeks with unusual failure activity.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 500
| extend week_start = startofweek(_time)
| summarize error_count = count() by week_start
| sort by week_start asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20500%20%7C%20extend%20week_start%20%3D%20startofweek\(_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20week_start%20%7C%20sort%20by%20week_start%20asc%22%7D)
**Output**
| week\_start | error\_count |
| -------------------- | ------------ |
| 2025-01-05T00:00:00Z | 45 |
| 2025-01-12T00:00:00Z | 67 |
| 2025-01-19T00:00:00Z | 38 |
This query filters for server errors and counts them per week to reveal weekly error patterns.
## List of related functions [#list-of-related-functions]
* [endofweek](/apl/scalar-functions/datetime-functions/endofweek): Returns the end of the week for a datetime value.
* [startofday](/apl/scalar-functions/datetime-functions/startofday): Returns the start of the day for a datetime value.
* [startofmonth](/apl/scalar-functions/datetime-functions/startofmonth): Returns the start of the month for a datetime value.
* [startofyear](/apl/scalar-functions/datetime-functions/startofyear): Returns the start of the year for a datetime value.
* [week\_of\_year](/apl/scalar-functions/datetime-functions/week-of-year): Returns the ISO 8601 week number from a datetime value.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `relative_time` with the `@w0` snap-to modifier to round a timestamp to the start of the week (Sunday). In APL, the `startofweek` function achieves the same result and supports an optional week offset.
```sql Splunk example
... | eval week_start=relative_time(_time, "@w0")
```
```kusto APL equivalent
... | extend week_start = startofweek(_time)
```
In ANSI SQL, you use `DATE_TRUNC('week', timestamp_column)` to truncate a timestamp to the start of the week. Note that the start day of the week varies across SQL implementations. In APL, `startofweek` always uses Sunday as the start of the week.
```sql SQL example
SELECT DATE_TRUNC('week', timestamp_column) AS week_start FROM events;
```
```kusto APL equivalent
['dataset']
| extend week_start = startofweek(_time)
```
---
# startofyear
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/startofyear
Use the `startofyear` function in APL to round a datetime value down to the first day of the year at midnight (January 1 at 00:00:00). This function is useful for binning events into yearly buckets for long-term trend analysis.
You can use `startofyear` to group records by year when analyzing annual trends, performing year-over-year comparisons, or building yearly aggregate reports across log, trace, and security datasets.
Use it when you want to:
* Aggregate events or metrics by year.
* Align timestamps to year boundaries for annual reporting.
* Compare activity or error rates across different years.
## Usage [#usage]
### Syntax [#syntax]
```kusto
startofyear(datetime [, offset])
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | -------------------------------------------------------------------------------- |
| datetime | `datetime` | The input datetime value. |
| offset | `long` | Optional: The number of years to offset from the input datetime. Default is `0`. |
### Returns [#returns]
A `datetime` representing the start of the year (January 1 at 00:00:00) for the given date value, shifted by the offset if specified.
## Use case examples [#use-case-examples]
Count requests per year to understand long-term traffic volume.
**Query**
```kusto
['sample-http-logs']
| extend year_start = startofyear(_time)
| summarize request_count = count() by year_start
| sort by year_start asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20year_start%20%3D%20startofyear\(_time\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20year_start%20%7C%20sort%20by%20year_start%20asc%22%7D)
**Output**
| year\_start | request\_count |
| -------------------- | -------------- |
| 2024-01-01T00:00:00Z | 523400 |
| 2025-01-01T00:00:00Z | 148200 |
This query bins each HTTP request to the start of its year and counts the total requests per year.
Compare yearly trace volume by service to understand long-term usage patterns.
**Query**
```kusto
['otel-demo-traces']
| extend year_start = startofyear(_time)
| summarize trace_count = count() by year_start, ['service.name']
| sort by year_start asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20year_start%20%3D%20startofyear\(_time\)%20%7C%20summarize%20trace_count%20%3D%20count\(\)%20by%20year_start%2C%20%5B'service.name'%5D%20%7C%20sort%20by%20year_start%20asc%22%7D)
**Output**
| year\_start | service.name | trace\_count |
| -------------------- | ------------ | ------------ |
| 2024-01-01T00:00:00Z | frontend | 245000 |
| 2024-01-01T00:00:00Z | cart | 132000 |
| 2025-01-01T00:00:00Z | frontend | 67000 |
This query groups trace spans by year and service, then counts the total traces for each combination.
Track yearly error trends to identify year-over-year changes in server error volume.
**Query**
```kusto
['sample-http-logs']
| where toint(status) >= 500
| extend year_start = startofyear(_time)
| summarize error_count = count() by year_start
| sort by year_start asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20toint\(status\)%20%3E%3D%20500%20%7C%20extend%20year_start%20%3D%20startofyear\(_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20year_start%20%7C%20sort%20by%20year_start%20asc%22%7D)
**Output**
| year\_start | error\_count |
| -------------------- | ------------ |
| 2024-01-01T00:00:00Z | 1890 |
| 2025-01-01T00:00:00Z | 534 |
This query filters for server errors and counts them per year to reveal yearly error trends.
## List of related functions [#list-of-related-functions]
* [endofyear](/apl/scalar-functions/datetime-functions/endofyear): Returns the end of the year for a datetime value.
* [startofday](/apl/scalar-functions/datetime-functions/startofday): Returns the start of the day for a datetime value.
* [startofmonth](/apl/scalar-functions/datetime-functions/startofmonth): Returns the start of the month for a datetime value.
* [startofweek](/apl/scalar-functions/datetime-functions/startofweek): Returns the start of the week for a datetime value.
* [getyear](/apl/scalar-functions/datetime-functions/getyear): Returns the year from a datetime value.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `relative_time` with the `@y` snap-to modifier to round a timestamp to the start of the year. In APL, the `startofyear` function achieves the same result directly.
```sql Splunk example
... | eval year_start=relative_time(_time, "@y")
```
```kusto APL equivalent
... | extend year_start = startofyear(_time)
```
In ANSI SQL, you use `DATE_TRUNC('year', timestamp_column)` to truncate a timestamp to the first day of the year. In APL, `startofyear` provides the same functionality.
```sql SQL example
SELECT DATE_TRUNC('year', timestamp_column) AS year_start FROM events;
```
```kusto APL equivalent
['dataset']
| extend year_start = startofyear(_time)
```
---
# unixtime_microseconds_todatetime
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/unixtime-microseconds-todatetime
`unixtime_microseconds_todatetime` converts a Unix timestamp that’s expressed in whole microseconds since 1970-01-01 00:00:00 UTC to an APL `datetime` value.
Use the function whenever you ingest data that stores time as epoch microseconds (for example, JSON logs from NGINX or metrics that follow the StatsD line protocol). Converting to `datetime` lets you bin, filter, and visualize events with the rest of your time-series data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
unixtime_microseconds_todatetime(microseconds)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------------- | --------------- | ----------------------------------------------------------------------- |
| `microseconds` | `int` or `long` | Whole microseconds since the Unix epoch. Fractional input is truncated. |
### Returns [#returns]
A `datetime` value that represents the given epoch microseconds at UTC precision (1 microsecond).
## Use case example [#use-case-example]
The HTTP access logs keep the timestamp as epoch microseconds and you want to convert the values to datetime.
**Query**
```kusto
['sample-http-logs']
| extend epoch_microseconds = toint(datetime_diff('Microsecond', _time, datetime(1970-01-01)))
| extend datetime_standard = unixtime_microseconds_todatetime(epoch_microseconds)
| project _time, epoch_microseconds, datetime_standard
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20epoch_microseconds%20%3D%20toint\(datetime_diff\('Microsecond'%2C%20_time%2C%20datetime\(1970-01-01\)\)\)%20%7C%20extend%20datetime_standard%20%3D%20unixtime_microseconds_todatetime\(epoch_microseconds\)%20%7C%20project%20_time%2C%20epoch_microseconds%2C%20datetime_standard%22%7D)
**Output**
| \_time | epoch\_microseconds | datetime\_standard |
| ---------------- | ------------------- | -------------------- |
| May 15, 12:09:22 | 1,747,303,762 | 2025-05-15T10:09:22Z |
This query converts the timestamp to epoch microseconds and then back to datetime for demonstration purposes.
## List of related functions [#list-of-related-functions]
* [unixtime\_milliseconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-milliseconds-todatetime): Converts a Unix timestamp expressed in whole milliseconds to an APL `datetime` value.
* [unixtime\_nanoseconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-nanoseconds-todatetime): Converts a Unix timestamp expressed in whole nanoseconds to an APL `datetime` value.
* [unixtime\_seconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-seconds-todatetime): Converts a Unix timestamp expressed in whole seconds to an APL `datetime` value.
## Other query languages [#other-query-languages]
In Splunk, you often convert epoch values with `eval ts=strftime(_time,"%Y-%m-%dT%H:%M:%S.%6N")`. In APL, the conversion happens with a scalar function, so you can use it inline wherever a `datetime` literal is accepted.
```sql Splunk example
| eval eventTime=strftime( micro_ts/1000000 , "%Y-%m-%dT%H:%M:%S.%6N")
```
```kusto APL equivalent
| extend eventTime = unixtime_microseconds_todatetime(micro_ts)
```
Standard SQL engines rarely expose microsecond-epoch helpers. You usually cast or divide by 1,000,000 and add an interval. APL gives you a dedicated scalar function that returns a native `datetime`, which then supports the full date-time syntax.
```sql SQL example
SELECT TIMESTAMP '1970-01-01 00:00:00' + micro_ts / 1000000 * INTERVAL '1 second' FROM events;
```
```kusto APL equivalent
['events']
| extend eventTime = unixtime_microseconds_todatetime(micro_ts)
```
---
# unixtime_milliseconds_todatetime
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/unixtime-milliseconds-todatetime
`unixtime_milliseconds_todatetime` converts a Unix timestamp that’s expressed in whole milliseconds since 1970-01-01 00:00:00 UTC to an APL `datetime` value.
Use the function whenever you ingest data that stores time as epoch milliseconds (for example, JSON logs from NGINX or metrics that follow the StatsD line protocol). Converting to `datetime` lets you bin, filter, and visualize events with the rest of your time-series data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
unixtime_milliseconds_todatetime(milliseconds)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------------- | --------------- | ----------------------------------------------------------------------- |
| `milliseconds` | `int` or `long` | Whole milliseconds since the Unix epoch. Fractional input is truncated. |
### Returns [#returns]
A `datetime` value that represents the given epoch milliseconds at UTC precision (1 millisecond).
## Use case example [#use-case-example]
The HTTP access logs keep the timestamp as epoch milliseconds and you want to convert the values to datetime.
**Query**
```kusto
['sample-http-logs']
| extend epoch_milliseconds = toint(datetime_diff('Millisecond', _time, datetime(1970-01-01)))
| extend datetime_standard = unixtime_milliseconds_todatetime(epoch_milliseconds)
| project _time, epoch_milliseconds, datetime_standard
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20epoch_milliseconds%20%3D%20toint\(datetime_diff\('Millisecond'%2C%20_time%2C%20datetime\(1970-01-01\)\)\)%20%7C%20extend%20datetime_standard%20%3D%20unixtime_milliseconds_todatetime\(epoch_milliseconds\)%20%7C%20project%20_time%2C%20epoch_milliseconds%2C%20datetime_standard%22%7D)
**Output**
| \_time | epoch\_milliseconds | datetime\_standard |
| ---------------- | ------------------- | -------------------- |
| May 15, 12:09:22 | 1,747,303,762 | 2025-05-15T10:09:22Z |
This query converts the timestamp to epoch milliseconds and then back to datetime for demonstration purposes.
## List of related functions [#list-of-related-functions]
* [unixtime\_microseconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-microseconds-todatetime): Converts a Unix timestamp expressed in whole microseconds to an APL `datetime` value.
* [unixtime\_nanoseconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-nanoseconds-todatetime): Converts a Unix timestamp expressed in whole nanoseconds to an APL `datetime` value.
* [unixtime\_seconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-seconds-todatetime): Converts a Unix timestamp expressed in whole seconds to an APL `datetime` value.
## Other query languages [#other-query-languages]
`unixtime_milliseconds_todatetime()` corresponds to an `eval` expression that divides the epoch value by 1000 and formats the result. You skip both steps in APL because the function takes milliseconds directly.
```sql Splunk example
| eval timestamp=strftime(epoch_ms/1000,"%Y-%m-%dT%H:%M:%SZ")
```
```kusto APL equivalent
| extend timestamp=unixtime_milliseconds_todatetime(epoch_ms)
```
The function plays the same role as `FROM_UNIXTIME()` or `TO_TIMESTAMP()` in SQL dialects. In APL, you don’t divide by 1,000 because the function expects milliseconds.
```sql SQL example
SELECT FROM_UNIXTIME(epoch_ms/1000) AS timestamp FROM requests;
```
```kusto APL equivalent
['sample-http-logs']
| extend timestamp=unixtime_milliseconds_todatetime(epoch_ms)
```
---
# unixtime_nanoseconds_todatetime
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/unixtime-nanoseconds-todatetime
`unixtime_nanoseconds_todatetime` converts a Unix timestamp that’s expressed in whole nanoseconds since 1970-01-01 00:00:00 UTC to an APL `datetime` value.
Use the function whenever you ingest data that stores time as epoch nanoseconds (for example, JSON logs from NGINX or metrics that follow the StatsD line protocol). Converting to `datetime` lets you bin, filter, and visualize events with the rest of your time-series data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
unixtime_nanoseconds_todatetime(nanoseconds)
```
### Parameters [#parameters]
| Name | Type | Description |
| ------------- | --------------- | ---------------------------------------------------------------------- |
| `nanoseconds` | `int` or `long` | Whole nanoseconds since the Unix epoch. Fractional input is truncated. |
### Returns [#returns]
A `datetime` value that represents the given epoch nanoseconds at UTC precision (1 nanosecond).
## Use case example [#use-case-example]
The HTTP access logs keep the timestamp as epoch nanoseconds and you want to convert the values to datetime.
**Query**
```kusto
['sample-http-logs']
| extend epoch_nanoseconds = toint(datetime_diff('Nanosecond', _time, datetime(1970-01-01)))
| extend datetime_standard = unixtime_nanoseconds_todatetime(epoch_nanoseconds)
| project _time, epoch_nanoseconds, datetime_standard
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20epoch_nanoseconds%20%3D%20toint\(datetime_diff\('Nanosecond'%2C%20_time%2C%20datetime\(1970-01-01\)\)\)%20%7C%20extend%20datetime_standard%20%3D%20unixtime_nanoseconds_todatetime\(epoch_nanoseconds\)%20%7C%20project%20_time%2C%20epoch_nanoseconds%2C%20datetime_standard%22%7D)
**Output**
| \_time | epoch\_nanoseconds | datetime\_standard |
| ---------------- | ------------------ | -------------------- |
| May 15, 12:09:22 | 1,747,303,762 | 2025-05-15T10:09:22Z |
This query converts the timestamp to epoch nanoseconds and then back to datetime for demonstration purposes.
## List of related functions [#list-of-related-functions]
* [unixtime\_microseconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-microseconds-todatetime): Converts a Unix timestamp expressed in whole microseconds to an APL `datetime` value.
* [unixtime\_milliseconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-milliseconds-todatetime): Converts a Unix timestamp expressed in whole milliseconds to an APL `datetime` value.
* [unixtime\_seconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-seconds-todatetime): Converts a Unix timestamp expressed in whole seconds to an APL `datetime` value.
## Other query languages [#other-query-languages]
Splunk SPL usually stores `_time` in seconds and uses functions such as `strftime` or `strptime` for conversion. In APL, you pass the nanosecond integer directly to `unixtime_nanoseconds_todatetime`, so you don’t divide by 1,000,000,000 first.
```sql Splunk example
| eval event_time = strftime(epoch_ns/1000000000, "%Y-%m-%dT%H:%M:%S.%N%z")
```
```kusto APL equivalent
| extend event_time = unixtime_nanoseconds_todatetime(epoch_ns)
```
Many SQL engines use `TO_TIMESTAMP_LTZ()` or similar functions that expect seconds or microseconds. In APL, you pass the nanosecond value directly, and the function returns a `datetime` (UTC).
```sql SQL example
SELECT TO_TIMESTAMP_LTZ(epoch_ns/1e9) AS event_time
FROM events;
```
```kusto APL equivalent
events
| extend event_time = unixtime_nanoseconds_todatetime(epoch_ns)
```
---
# unixtime_seconds_todatetime
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/unixtime-seconds-todatetime
`unixtime_seconds_todatetime` converts a Unix timestamp that’s expressed in whole seconds since 1970-01-01 00:00:00 UTC to an APL [datetime value](/apl/data-types/scalar-data-types).
Use the function whenever you ingest data that stores time as epoch seconds (for example, JSON logs from NGINX or metrics that follow the StatsD line protocol). Converting to `datetime` lets you bin, filter, and visualize events with the rest of your time-series data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
unixtime_seconds_todatetime(seconds)
```
### Parameters [#parameters]
| Name | Type | Description |
| --------- | --------------- | ------------------------------------------------------------------ |
| `seconds` | `int` or `long` | Whole seconds since the Unix epoch. Fractional input is truncated. |
### Returns [#returns]
A `datetime` value that represents the given epoch seconds at UTC precision (1 second).
## Use case example [#use-case-example]
The HTTP access logs keep the timestamp as epoch seconds and you want to convert the values to datetime.
**Query**
```kusto
['sample-http-logs']
| extend epoch_seconds = toint(datetime_diff('Second', _time, datetime(1970-01-01)))
| extend datetime_standard = unixtime_seconds_todatetime(epoch_seconds)
| project _time, epoch_seconds, datetime_standard
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20epoch_seconds%20%3D%20toint\(datetime_diff\('Second'%2C%20_time%2C%20datetime\(1970-01-01\)\)\)%20%7C%20extend%20datetime_standard%20%3D%20unixtime_seconds_todatetime\(epoch_seconds\)%20%7C%20project%20_time%2C%20epoch_seconds%2C%20datetime_standard%22%7D)
**Output**
| \_time | epoch\_seconds | datetime\_standard |
| ---------------- | -------------- | -------------------- |
| May 15, 12:09:22 | 1,747,303,762 | 2025-05-15T10:09:22Z |
This query converts the timestamp to epoch seconds and then back to datetime for demonstration purposes.
## List of related functions [#list-of-related-functions]
* [unixtime\_microseconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-microseconds-todatetime): Converts a Unix timestamp expressed in whole microseconds to an APL `datetime` value.
* [unixtime\_milliseconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-milliseconds-todatetime): Converts a Unix timestamp expressed in whole milliseconds to an APL `datetime` value.
* [unixtime\_nanoseconds\_todatetime](/apl/scalar-functions/datetime-functions/unixtime-nanoseconds-todatetime): Converts a Unix timestamp expressed in whole nanoseconds to an APL `datetime` value.
## Other query languages [#other-query-languages]
`unixtime_seconds_todatetime` replaces the combination of `eval strftime` / `strptime` that you normally use in Splunk. Pass the epoch value directly and APL returns a `datetime`.
```sql Splunk example
eval event_time = strftime(epoch, "%Y-%m-%dT%H:%M:%S")
```
```kusto APL equivalent
extend event_time = unixtime_seconds_todatetime(epoch)
```
Most ANSI SQL engines call this conversion with `FROM_UNIXTIME` or `TO_TIMESTAMP`. The APL version has the same single-argument signature, returns a full `datetime`, and automatically interprets the input as seconds (not milliseconds).
```sql SQL example
SELECT TO_TIMESTAMP(epoch_seconds) AS event_time FROM events;
```
```kusto APL equivalent
['events']
| extend event_time = unixtime_seconds_todatetime(epoch_seconds)
```
---
# week_of_year
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/week-of-year
Use the `week_of_year` function in APL to extract the ISO 8601 week number from a datetime expression. The ISO 8601 standard defines the first week of the year as the one that contains the first Thursday of the year, and weeks start on Mondays.
You can use `week_of_year` to group records by week when analyzing trends over time. This is especially useful for weekly aggregation in dashboards, anomaly detection, and cohort analysis.
Use it when you want to:
* Track activity or metrics week by week.
* Normalize data across different timeframes by weekly intervals.
* Generate week-based summaries across log, trace, or security datasets.
## Usage [#usage]
### Syntax [#syntax]
```kusto
week_of_year(datetime)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ---------- | ------------------------- |
| datetime | `datetime` | The input datetime value. |
### Returns [#returns]
A `long` representing the ISO 8601 week number (from 1 to 53).
## Use case examples [#use-case-examples]
Group HTTP log events by week to understand traffic trends and average request duration.
**Query**
```kusto
['sample-http-logs']
| extend week = week_of_year(_time)
| summarize avg(req_duration_ms) by week
| sort by week asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20week%20%3D%20week_of_year\(_time\)%20%7C%20summarize%20avg\(req_duration_ms\)%20by%20week%20%7C%20sort%20by%20week%20asc%22%7D)
**Output**
| week | avg\_req\_duration\_ms |
| ---- | ---------------------- |
| 1 | 243.8 |
| 2 | 251.1 |
| 3 | 237.4 |
This query extracts the ISO week number for each record and calculates the average request duration per week.
Use weekly grouping to monitor changes in span durations for frontend services over time.
**Query**
```kusto
['otel-demo-traces']
| where ['service.name'] == 'frontend'
| extend week = week_of_year(_time)
| summarize avg_duration = avg(duration) by week
| sort by week asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20%5B'service.name'%5D%20%3D%3D%20'frontend'%20%7C%20extend%20week%20%3D%20week_of_year\(_time\)%20%7C%20summarize%20avg_duration%20%3D%20avg\(duration\)%20by%20week%20%7C%20sort%20by%20week%20asc%22%7D)
**Output**
| week | avg\_duration |
| ---- | ---------------- |
| 1 | 00:00:01.2340000 |
| 2 | 00:00:01.1750000 |
| 3 | 00:00:01.2890000 |
This query shows how the average span duration changes weekly for the `frontend` service.
Count HTTP errors by week to detect unusual spikes in failed requests.
**Query**
```kusto
['sample-http-logs']
| where status startswith '5'
| extend week = week_of_year(_time)
| summarize error_count = count() by week
| sort by week asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20startswith%20'5'%20%7C%20extend%20week%20%3D%20week_of_year\(_time\)%20%7C%20summarize%20error_count%20%3D%20count\(\)%20by%20week%20%7C%20sort%20by%20week%20asc%22%7D)
**Output**
| week | error\_count |
| ---- | ------------ |
| 1 | 18 |
| 2 | 34 |
| 3 | 12 |
This query detects week-by-week patterns in server error frequency, helping identify problem periods.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use the `strftime` function with the `%V` or `%U` specifiers to extract the week of the year. In APL, the `week_of_year` function directly extracts the ISO 8601 week number from a datetime.
```sql Splunk example
... | eval week=strftime(_time, "%V")
```
```kusto APL equivalent
... | extend week = week_of_year(_time)
```
In ANSI SQL, you often use `EXTRACT(WEEK FROM timestamp)` or `DATEPART(WEEK, timestamp)`. These implementations may differ slightly across platforms in how they define the first week of the year. APL uses the ISO 8601 definition via `week_of_year`.
```sql SQL example
SELECT EXTRACT(WEEK FROM timestamp_column) AS week FROM events;
```
```kusto APL equivalent
['dataset']
| extend week = week_of_year(_time)
```
---
# hash_md5
Source: https://axiom.co/docs/apl/scalar-functions/hash-functions/hash-md5
## Introduction [#introduction]
The `hash_md5` function returns the MD5 hash of a scalar value as a 32-character hexadecimal string. Use it to anonymize personally identifiable information while preserving joinability, detect duplicate records across datasets, or generate consistent bucket keys for grouping.
MD5 produces a 128-bit digest that's fast to compute. It isn't suitable for cryptographic security, but is appropriate for data deduplication, checksumming, and non-security anonymization tasks. For security-sensitive use cases, use [`hash_sha256`](/apl/scalar-functions/hash-functions/hash-sha256) or [`hash_sha512`](/apl/scalar-functions/hash-functions/hash-sha512) instead.
## Usage [#usage]
### Syntax [#syntax]
```kusto
hash_md5(source)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------ | ------ | -------- | -------------------------------------------------------------- |
| source | scalar | Yes | The value to hash. APL converts it to a string before hashing. |
### Returns [#returns]
The MD5 hash of `source` as a 32-character lowercase hexadecimal string.
## Use case examples [#use-case-examples]
Anonymize user IDs before counting requests per user to protect PII in shared dashboards.
**Query**
```kusto
['sample-http-logs']
| extend hashed_id = hash_md5(id)
| summarize request_count = count() by hashed_id
| top 5 by request_count
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20hashed_id%20%3D%20hash_md5%28id%29%20%7C%20summarize%20request_count%20%3D%20count%28%29%20by%20hashed_id%20%7C%20top%205%20by%20request_count%22%7D)
**Output**
| hashed\_id | request\_count |
| -------------------------------- | -------------- |
| b980a9c041dbd33d5893fad65d33284b | 128 |
| 3f7a2c1e8d4b6f9e0c5d3a7b2e4f8c1d | 97 |
| 9c2e4a6f1d3b7e8c5a2f4d6b9e1c3a7f | 85 |
| 1a3c5e7b9f2d4a6c8e0b3f5d7a9c1e3b | 74 |
| 7f9e1c3a5b2d4f6e8c0a3b5d7e9f1c3a | 69 |
The query replaces raw user IDs with MD5 hashes before aggregating, so the busiest users are visible without exposing their original identifiers.
Hash trace IDs to create stable, anonymized surrogate keys for grouping.
**Query**
```kusto
['otel-demo-traces']
| extend hashed_trace = hash_md5(trace_id)
| project _time, ['service.name'], hashed_trace, duration
| take 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20hashed_trace%20%3D%20hash_md5%28trace_id%29%20%7C%20project%20_time%2C%20%5B%27service.name%27%5D%2C%20hashed_trace%2C%20duration%20%7C%20take%2010%22%7D)
**Output**
| \_time | service.name | hashed\_trace | duration |
| ------------------- | ------------ | -------------------------------- | -------- |
| 2024-01-15 10:23:01 | frontend | a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4 | 320ms |
| 2024-01-15 10:23:02 | checkout | f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3 | 875ms |
| 2024-01-15 10:23:03 | cart | 2c4e6a8b0d2c4e6a8b0d2c4e6a8b0d2c | 140ms |
The query projects the hashed trace ID alongside service name and duration so you can analyze traces without exposing the original trace identifiers.
## List of related functions [#list-of-related-functions]
* [hash\_sha1](/apl/scalar-functions/hash-functions/hash-sha1): Returns a 40-character SHA-1 hex digest. Use `hash_sha1` when you need a larger digest than MD5 but legacy compatibility matters.
* [hash\_sha256](/apl/scalar-functions/hash-functions/hash-sha256): Returns a 64-character SHA-256 hex digest. Use `hash_sha256` for security-sensitive hashing.
* [hash\_sha512](/apl/scalar-functions/hash-functions/hash-sha512): Returns a 128-character SHA-512 hex digest for maximum hash strength.
* [hash](/apl/scalar-functions/hash-functions/hash): Returns a signed 64-bit integer hash. Use `hash` when you need a compact numeric key rather than a hex string.
## Other query languages [#other-query-languages]
Splunk provides the `md5(X)` function that returns a 32-character hex string. APL's `hash_md5` works the same way.
```sql Splunk example
... | eval hashed_id = md5(id)
```
```kusto APL equivalent
['sample-http-logs']
| extend hashed_id = hash_md5(id)
```
ANSI SQL has no standard MD5 function, but most databases provide one: MySQL's `MD5()`, PostgreSQL's `md5()`. APL's `hash_md5` returns the same 32-character lowercase hex digest.
```sql SQL example
SELECT MD5(id) AS hashed_id FROM sample_http_logs
```
```kusto APL equivalent
['sample-http-logs']
| extend hashed_id = hash_md5(id)
```
---
# hash_sha1
Source: https://axiom.co/docs/apl/scalar-functions/hash-functions/hash-sha1
## Introduction [#introduction]
The `hash_sha1` function returns the SHA-1 hash of a scalar value as a 40-character hexadecimal string. Use it to generate consistent identifiers, detect duplicates across datasets, or fingerprint values for comparison.
SHA-1 produces a 160-bit digest that's faster to compute than SHA-256 while producing a larger output than MD5. SHA-1 is deprecated for cryptographic security use cases, but remains appropriate for non-security tasks such as data deduplication, fingerprinting, and consistent grouping. For security-sensitive hashing, use [`hash_sha256`](/apl/scalar-functions/hash-functions/hash-sha256) or [`hash_sha512`](/apl/scalar-functions/hash-functions/hash-sha512).
## Usage [#usage]
### Syntax [#syntax]
```kusto
hash_sha1(source)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------ | ------ | -------- | -------------------------------------------------------------- |
| source | scalar | Yes | The value to hash. APL converts it to a string before hashing. |
### Returns [#returns]
The SHA-1 hash of `source` as a 40-character lowercase hexadecimal string.
## Use case examples [#use-case-examples]
Anonymize user IDs and count requests per hashed user to protect PII while tracking usage patterns.
**Query**
```kusto
['sample-http-logs']
| extend hashed_id = hash_sha1(id)
| summarize request_count = count() by hashed_id
| top 5 by request_count
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20hashed_id%20%3D%20hash_sha1%28id%29%20%7C%20summarize%20request_count%20%3D%20count%28%29%20by%20hashed_id%20%7C%20top%205%20by%20request_count%22%7D)
**Output**
| hashed\_id | request\_count |
| ---------------------------------------- | -------------- |
| 9f9af029585ba014e07cd3910ca976cf56160616 | 128 |
| a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 | 97 |
| 356a192b7913b04c54574d18c28d46e6395428ab | 85 |
| da4b9237bacccdf19c0760cab7aec4a8359010b0 | 74 |
| 77de68daecd823babbb58edb1c8e14d7106e83bb | 69 |
The query replaces raw user IDs with SHA-1 hashes before aggregating, so the busiest users are visible without exposing their original identifiers.
Hash span IDs to create stable surrogate keys for cross-dataset joins or external reporting.
**Query**
```kusto
['otel-demo-traces']
| extend hashed_span = hash_sha1(span_id)
| project _time, ['service.name'], hashed_span, duration
| take 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20hashed_span%20%3D%20hash_sha1%28span_id%29%20%7C%20project%20_time%2C%20%5B%27service.name%27%5D%2C%20hashed_span%2C%20duration%20%7C%20take%2010%22%7D)
**Output**
| \_time | service.name | hashed\_span | duration |
| ------------------- | ------------ | ---------------------------------------- | -------- |
| 2024-01-15 10:23:01 | frontend | 9f9af029585ba014e07cd3910ca976cf56160616 | 320ms |
| 2024-01-15 10:23:02 | checkout | a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 | 875ms |
| 2024-01-15 10:23:03 | cart | 356a192b7913b04c54574d18c28d46e6395428ab | 140ms |
The query projects the hashed span ID alongside service name and duration so you can work with span fingerprints in downstream pipelines.
## List of related functions [#list-of-related-functions]
* [hash\_md5](/apl/scalar-functions/hash-functions/hash-md5): Returns a 32-character MD5 hex digest. Use `hash_md5` when a shorter digest is sufficient and speed is the priority.
* [hash\_sha256](/apl/scalar-functions/hash-functions/hash-sha256): Returns a 64-character SHA-256 hex digest. Use `hash_sha256` for security-sensitive hashing.
* [hash\_sha512](/apl/scalar-functions/hash-functions/hash-sha512): Returns a 128-character SHA-512 hex digest for maximum hash strength.
* [hash](/apl/scalar-functions/hash-functions/hash): Returns a signed 64-bit integer hash. Use `hash` when you need a compact numeric key rather than a hex string.
## Other query languages [#other-query-languages]
Splunk provides the `sha1(X)` function that returns a 40-character hex string. APL's `hash_sha1` works the same way.
```sql Splunk example
... | eval hashed = sha1(id)
```
```kusto APL equivalent
['sample-http-logs']
| extend hashed = hash_sha1(id)
```
ANSI SQL has no standard SHA-1 function. PostgreSQL provides `encode(digest(value, 'sha1'), 'hex')`. APL's `hash_sha1` returns the same 40-character lowercase hex digest.
```sql SQL example
SELECT encode(digest(id, 'sha1'), 'hex') AS hashed FROM sample_http_logs
```
```kusto APL equivalent
['sample-http-logs']
| extend hashed = hash_sha1(id)
```
---
# hash_sha256
Source: https://axiom.co/docs/apl/scalar-functions/hash-functions/hash-sha256
## Introduction [#introduction]
The `hash_sha256` function returns the SHA-256 hash of a scalar value as a 64-character hexadecimal string. Use it for security-sensitive hashing, compliance requirements, data integrity checks, or any scenario where cryptographic strength is needed.
SHA-256 produces a 256-bit digest and is the current industry standard for secure hashing. Unlike MD5 and SHA-1, SHA-256 isn't practically vulnerable to collision attacks, making it appropriate for use in security workflows such as verifying log integrity, fingerprinting malware indicators, or hashing credentials. For even longer digests, use [`hash_sha512`](/apl/scalar-functions/hash-functions/hash-sha512).
## Usage [#usage]
### Syntax [#syntax]
```kusto
hash_sha256(source)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------ | ------ | -------- | -------------------------------------------------------------- |
| source | scalar | Yes | The value to hash. APL converts it to a string before hashing. |
### Returns [#returns]
The SHA-256 hash of `source` as a 64-character lowercase hexadecimal string.
## Use case examples [#use-case-examples]
Anonymize user IDs with a cryptographically strong hash before publishing usage summaries.
**Query**
```kusto
['sample-http-logs']
| extend hashed_id = hash_sha256(id)
| summarize request_count = count() by hashed_id
| top 5 by request_count
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20hashed_id%20%3D%20hash_sha256%28id%29%20%7C%20summarize%20request_count%20%3D%20count%28%29%20by%20hashed_id%20%7C%20top%205%20by%20request_count%22%7D)
**Output**
| hashed\_id | request\_count |
| ---------------------------------------------------------------- | -------------- |
| bb4770ff4ac5b7d2be41a088cb27d8bcaad53b574b6f27941e8e48e9e10fc25a | 128 |
| 2c624232cdd221771294dfbb310acbc8c12eb62dd7a3b4bfc41de8dd73d7a7c | 97 |
| 19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7 | 85 |
| 4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb5bda0e1b1b68a2a3e | 74 |
| ef2d127de37b942baad06145e54b0c619a1f22327b2ebbcfbec78f5564afe39d | 69 |
The query replaces user IDs with SHA-256 hashes before aggregating, producing a privacy-safe summary of the most active users.
Fingerprint trace IDs with SHA-256 for use in external security or compliance systems.
**Query**
```kusto
['otel-demo-traces']
| extend hashed_trace = hash_sha256(trace_id)
| project _time, ['service.name'], hashed_trace, duration
| take 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20hashed_trace%20%3D%20hash_sha256%28trace_id%29%20%7C%20project%20_time%2C%20%5B%27service.name%27%5D%2C%20hashed_trace%2C%20duration%20%7C%20take%2010%22%7D)
**Output**
| \_time | service.name | hashed\_trace | duration |
| ------------------- | ------------ | ---------------------------------------------------------------- | -------- |
| 2024-01-15 10:23:01 | frontend | bb4770ff4ac5b7d2be41a088cb27d8bcaad53b574b6f27941e8e48e9e10fc25a | 320ms |
| 2024-01-15 10:23:02 | checkout | 2c624232cdd221771294dfbb310acbc8c12eb62dd7a3b4bfc41de8dd73d7a7c | 875ms |
| 2024-01-15 10:23:03 | cart | 19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7 | 140ms |
The query outputs hashed trace IDs that can be shared with external audit or compliance tools without exposing internal identifiers.
## List of related functions [#list-of-related-functions]
* [hash\_sha512](/apl/scalar-functions/hash-functions/hash-sha512): Returns a 128-character SHA-512 hex digest. Use `hash_sha512` when your security policy requires a longer digest than SHA-256.
* [hash\_sha1](/apl/scalar-functions/hash-functions/hash-sha1): Returns a 40-character SHA-1 hex digest. SHA-1 is deprecated for security use; prefer `hash_sha256`.
* [hash\_md5](/apl/scalar-functions/hash-functions/hash-md5): Returns a 32-character MD5 hex digest. MD5 isn't cryptographically safe; use `hash_sha256` for security contexts.
* [hash](/apl/scalar-functions/hash-functions/hash): Returns a signed 64-bit integer hash. Use `hash` when you need a compact numeric key rather than a hex string.
## Other query languages [#other-query-languages]
Splunk provides the `sha256(X)` function that returns a 64-character hex string. APL's `hash_sha256` works the same way.
```sql Splunk example
... | eval hashed = sha256(id)
```
```kusto APL equivalent
['sample-http-logs']
| extend hashed = hash_sha256(id)
```
ANSI SQL has no standard SHA-256 function. PostgreSQL provides `encode(digest(value, 'sha256'), 'hex')`. APL's `hash_sha256` returns the same 64-character lowercase hex digest.
```sql SQL example
SELECT encode(digest(id, 'sha256'), 'hex') AS hashed FROM sample_http_logs
```
```kusto APL equivalent
['sample-http-logs']
| extend hashed = hash_sha256(id)
```
---
# hash_sha512
Source: https://axiom.co/docs/apl/scalar-functions/hash-functions/hash-sha512
## Introduction [#introduction]
The `hash_sha512` function returns the SHA-512 hash of a scalar value as a 128-character hexadecimal string. Use it when your security policy or compliance requirements demand the strongest standard hash available, or when you need a longer digest than SHA-256 provides.
SHA-512 produces a 512-bit digest, making it the most collision-resistant of the SHA hash functions available in APL. It's well suited for high-security fingerprinting, long-term integrity verification, and compliance use cases. For most everyday hashing tasks, [`hash_sha256`](/apl/scalar-functions/hash-functions/hash-sha256) is sufficient.
## Usage [#usage]
### Syntax [#syntax]
```kusto
hash_sha512(source)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------ | ------ | -------- | -------------------------------------------------------------- |
| source | scalar | Yes | The value to hash. APL converts it to a string before hashing. |
### Returns [#returns]
The SHA-512 hash of `source` as a 128-character lowercase hexadecimal string.
## Use case examples [#use-case-examples]
Anonymize user IDs with a maximum-strength hash before publishing compliance-sensitive usage summaries.
**Query**
```kusto
['sample-http-logs']
| extend hashed_id = hash_sha512(id)
| summarize request_count = count() by hashed_id
| top 5 by request_count
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20hashed_id%20%3D%20hash_sha512%28id%29%20%7C%20summarize%20request_count%20%3D%20count%28%29%20by%20hashed_id%20%7C%20top%205%20by%20request_count%22%7D)
**Output**
| hashed\_id | request\_count |
| -------------------------------------------------------------------------------------------------------------------------------- | -------------- |
| 0878a61b503dd5a9fe9ea3545d6d3bd41c3b50a47f3594cb8bbab3e47558d68fc8fcc409cd0831e91afc4e609ef9da84e0696c50354ad86b25f2609efef6a834 | 128 |
| 95c6eacdd41170b129c3c287cfe088d4fafea34e371422b94eb78b9653a89d4132af33ef39dd6b3d80e18c33b21ae167ec9e9c2d820860689c647ffb725498c4 | 97 |
| cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e | 85 |
The query uses SHA-512 to produce maximum-strength anonymized user keys before aggregating request counts.
Fingerprint span IDs with SHA-512 for use in high-security audit trails.
**Query**
```kusto
['otel-demo-traces']
| extend hashed_span = hash_sha512(span_id)
| project _time, ['service.name'], hashed_span, duration
| take 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20hashed_span%20%3D%20hash_sha512%28span_id%29%20%7C%20project%20_time%2C%20%5B%27service.name%27%5D%2C%20hashed_span%2C%20duration%20%7C%20take%2010%22%7D)
**Output**
| \_time | service.name | hashed\_span | duration |
| ------------------- | ------------ | ------------------------------------------------------------------- | -------- |
| 2024-01-15 10:23:01 | frontend | 0878a61b503dd5a9fe9ea3545d6d3bd41c3b50a47f3594cb8bbab3e47558d68f... | 320ms |
| 2024-01-15 10:23:02 | checkout | 95c6eacdd41170b129c3c287cfe088d4fafea34e371422b94eb78b9653a89d41... | 875ms |
| 2024-01-15 10:23:03 | cart | cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce... | 140ms |
The query outputs SHA-512 fingerprints of span IDs for external audit systems that require maximum-length digests.
## List of related functions [#list-of-related-functions]
* [hash\_sha256](/apl/scalar-functions/hash-functions/hash-sha256): Returns a 64-character SHA-256 hex digest. Use `hash_sha256` when SHA-256 strength is sufficient and you prefer shorter digests.
* [hash\_sha1](/apl/scalar-functions/hash-functions/hash-sha1): Returns a 40-character SHA-1 hex digest. SHA-1 is deprecated for security use; prefer `hash_sha512`.
* [hash\_md5](/apl/scalar-functions/hash-functions/hash-md5): Returns a 32-character MD5 hex digest. Not cryptographically safe; use `hash_sha512` for security contexts.
* [hash](/apl/scalar-functions/hash-functions/hash): Returns a signed 64-bit integer hash. Use `hash` when you need a compact numeric key rather than a hex string.
## Other query languages [#other-query-languages]
Splunk provides the `sha512(X)` function that returns a 128-character hex string. APL's `hash_sha512` works the same way.
```sql Splunk example
... | eval hashed = sha512(id)
```
```kusto APL equivalent
['sample-http-logs']
| extend hashed = hash_sha512(id)
```
ANSI SQL has no standard SHA-512 function. PostgreSQL provides `encode(digest(value, 'sha512'), 'hex')`. APL's `hash_sha512` returns the same 128-character lowercase hex digest.
```sql SQL example
SELECT encode(digest(id, 'sha512'), 'hex') AS hashed FROM sample_http_logs
```
```kusto APL equivalent
['sample-http-logs']
| extend hashed = hash_sha512(id)
```
---
# hash
Source: https://axiom.co/docs/apl/scalar-functions/hash-functions/hash
Use the `hash` scalar function to transform any data type as a string of bytes into a signed integer. The result is deterministic so the value is always identical given the same input data.
Use the `hash` function to:
* Anonymise personally identifiable information (PII) while preserving joinability.
* Create reproducible buckets for sampling, sharding, or load-balancing.
* Build low-cardinality keys for fast aggregation and look-ups.
* You need a reversible-by-key surrogate or a quick way to distribute rows evenly.
Don’t use `hash` to generate values for long term usage. `hash` is generic and the underlying hashing algorithm may change. For long term stability, use the [other hash functions](/apl/scalar-functions/hash-functions) with specific algorithm like `hash_sha1`.
## Usage [#usage]
### Syntax [#syntax]
```kusto
hash(source [, salt])
```
### Parameters [#parameters]
| Name | Type | Description |
| ----------- | ------ | ----------------------------------------------------------------------------------------- |
| valsourceue | scalar | Any scalar expression except `real`. |
| salt | `int` | (Optional) Salt that lets you derive a different 64-bit domain while keeping determinism. |
### Returns [#returns]
The signed integer hash of `source` (and `salt` if supplied).
## Use case examples [#use-case-examples]
Hash requesters to see your busiest anonymous users.
**Query**
```kusto
['sample-http-logs']
| extend anon_id = hash(id)
| summarize requests = count() by anon_id
| top 5 by requests
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20anon_id%20%3D%20hash%28id%29%20%7C%20summarize%20requests%20%3D%20count%28%29%20by%20anon_id%20%7C%20top%205%20by%20requests%22%7D)
**Output**
| anon\_id | requests |
| -------------------- | -------- |
| -5872831405421830129 | 128 |
| 902175364502087611 | 97 |
| -354879610945237854 | 85 |
| 6423087105927348713 | 74 |
| -919087345721004317 | 69 |
The query replaces raw IDs with hashed surrogates, counts requests per surrogate, then lists the five most active requesters without exposing PII.
Hash trace IDs to see which anonymous trace has the most spans.
**Query**
```kusto
['otel-demo-traces']
| extend trace_bucket = hash(trace_id)
| summarize spans = count() by trace_bucket
| sort by spans desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20trace_bucket%20%3D%20hash\(trace_id\)%20%7C%20summarize%20spans%20%3D%20count\(\)%20by%20trace_bucket%20%7C%20sort%20by%20spans%20desc%22%7D)
**Output**
| trace\_bucket | spans |
| -------------------------- | ----- |
| 8,858,860,617,655,667,000 | 62 |
| 4,193,515,424,067,409,000 | 62 |
| 1,779,014,838,419,064,000 | 62 |
| 5,399,024,001,804,211,000 | 62 |
| -2,480,347,067,347,939,000 | 62 |
Group suspicious endpoints without leaking the exact URI.
**Query**
```kusto
['sample-http-logs']
| extend uri_hash = hash(uri)
| summarize requests = count() by uri_hash, status
| top 10 by requests
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20uri_hash%20%3D%20hash%28uri%29%20%7C%20summarize%20requests%20%3D%20count%28%29%20by%20uri_hash%2C%20status%20%7C%20top%2010%20by%20requests%22%7D)
**Output**
| uri\_hash | status | requests |
| ------------------- | ------ | -------- |
| -123640987553821047 | 404 | 230 |
| 4385902145098764321 | 403 | 145 |
| -85439034872109873 | 401 | 132 |
| 493820743209857311 | 404 | 129 |
| -90348122345872001 | 500 | 118 |
The query hides sensitive path information yet still lets you see which hashed endpoints return the most errors.
## Other query languages [#other-query-languages]
Splunk’s `hash` (or `md5`, `sha1`, etc.) returns a hexadecimal string and lets you pick an algorithm. In APL `hash` always returns a 64-bit integer that trades cryptographic strength for speed and compactness. Use `hash_sha256` if you need a cryptographically secure digest.
```sql Splunk example
... | eval anon_id = md5(id) | stats count by anon_id
```
```kusto APL equivalent
['sample-http-logs']
| extend anon_id = hash(id)
| summarize count() by anon_id
```
Standard SQL often exposes vendor-specific functions such as `HASH` (BigQuery), `HASH_BYTES` (SQL Server), or `MD5`. These return either bytes or hex strings. In APL `hash` always yields an `int64`. To emulate SQL’s modulo bucketing, pipe the result into the arithmetic operator that you need.
```sql SQL example
SELECT HASH(id) % 10 AS bucket, COUNT(*) AS requests
FROM sample_http_logs
GROUP BY bucket
```
```kusto APL equivalent
['sample-http-logs']
| extend bucket = abs(hash(id) % 10)
| summarize requests = count() by bucket
```
---
# format_ipv4_mask
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/format-ipv4-mask
Use the `format_ipv4_mask` function to format an IPv4 address and a bitmask into Classless Inter-Domain Routing (CIDR) notation. This function is useful when you need to standardize or analyze network addresses, especially in datasets that contain raw IPs or numerical IP representations. It supports both string-based and numeric IPv4 inputs and can apply an optional prefix to generate a subnet mask.
You can use `format_ipv4_mask` to normalize IP addresses, extract network segments, or apply filtering or grouping logic based on subnet granularity.
## Usage [#usage]
### Syntax [#syntax]
```kusto
format_ipv4_mask(ip, prefix)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------ | ------ | -------- | -------------------------------------------------------------------------------------------------------------- |
| ip | string | ✓ | The IPv4 address in CIDR notation. You can use a string (for example, `'192.168.1.1'`) or a big-endian number. |
| prefix | int | ✓ | An integer between 0 and 32. Specifies how many leading bits to include in the mask. |
### Returns [#returns]
A string representing the IPv4 address in CIDR notation if the conversion succeeds. If the conversion fails, the function returns an empty string.
## Example [#example]
**Query**
```kusto
['sample-http-logs']
| extend subnet = format_ipv4_mask('192.168.1.54', 24)
| project _time, subnet
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20subnet%20%3D%20format_ipv4_mask\('192.168.1.54'%2C%2024\)%20%7C%20project%20_time%2C%20subnet%22%7D)
**Output**
| \_time | subnet |
| ----------------- | -------------- |
| 1Jun 30, 11:11:46 | 192.168.1.0/24 |
## List of related functions [#list-of-related-functions]
* [format\_ipv4](/apl/scalar-functions/ip-functions/format-ipv4): Converts a 32-bit unsigned integer to an IPv4 address string. Use it when your input is a raw numeric IP instead of a prefix length.
* [parse\_ipv4](/apl/scalar-functions/ip-functions/parse-ipv4): Parses an IPv4 string into a numeric representation. Use it when you want to do arithmetic or masking on IP addresses.
* [ipv4\_is\_in\_range](/apl/scalar-functions/ip-functions/ipv4-is-in-range): Checks whether an IPv4 address falls within a given range. Use it when you need to filter or classify IPs against subnets.
## Other query languages [#other-query-languages]
SPL doesn’t have a direct built-in equivalent to `format_ipv4_mask`. To format IPv4 addresses with subnet masks, you typically use custom field extractions or external lookup tables. In contrast, APL provides a native function for this task, simplifying analysis at the network or subnet level.
```sql Splunk example
| eval cidr=ip."/24"
```
```kusto APL equivalent
format_ipv4_mask('192.168.1.10', 24)
```
Standard SQL lacks native functions for manipulating IP addresses or CIDR notation. This type of transformation usually requires application-side logic or user-defined functions (UDFs). APL simplifies this by offering a first-class function for formatting IPs directly in queries.
```sql SQL example
-- Requires custom UDF or external processing
SELECT format_ip_with_mask(ip, 24) FROM connections
```
```kusto APL equivalent
format_ipv4_mask(ip, 24)
```
---
# format_ipv4
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/format-ipv4
The `format_ipv4` function in APL converts a numeric representation of an IPv4 address into its standard dotted-decimal format. This function is particularly useful when working with logs or datasets where IP addresses are stored as integers, making them hard to interpret directly.
You can use `format_ipv4` to enhance log readability, enrich security logs, or convert raw telemetry data for analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
format_ipv4(ipv4address)
```
### Parameters [#parameters]
* `ipv4address`: A `long` numeric representation of the IPv4 address in network byte order.
### Returns [#returns]
* Returns a string representing the IPv4 address in dotted-decimal format.
* Returns an empty string if the conversion fails.
## Use case example [#use-case-example]
When analyzing HTTP request logs, you can convert IP addresses stored as integers into a readable format to identify client locations or troubleshoot issues.
**Query**
```kusto
['sample-http-logs']
| extend formatted_ip = format_ipv4(3232235776)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20formatted_ip%20%3D%20format_ipv4\(3232235776\)%22%7D)
**Output**
| \_time | formatted\_ip | status | uri | method |
| ------------------- | ------------- | ------ | ------------- | ------ |
| 2024-11-14 10:00:00 | 192.168.1.0 | 200 | /api/products | GET |
This query decodes raw IP addresses into a human-readable format for easier analysis.
## List of related functions [#list-of-related-functions]
* [has\_any\_ipv4](/apl/scalar-functions/ip-functions/has-any-ipv4): Matches any IP address in a string column with a list of IP addresses or ranges.
* [has\_ipv4](/apl/scalar-functions/ip-functions/has-ipv4): Checks if a single IP address is present in a string column.
* [ipv4\_compare](/apl/scalar-functions/ip-functions/ipv4-compare): Compares two IPv4 addresses lexicographically. Use for sorting or range evaluations.
* [parse\_ipv4](/apl/scalar-functions/ip-functions/parse-ipv4): Converts a dotted-decimal IP address into a numeric representation.
## Other query languages [#other-query-languages]
In Splunk SPL, IPv4 address conversion is typically not a built-in function. You may need to use custom scripts or calculations. APL simplifies this process with the `format_ipv4` function.
ANSI SQL doesn’t have a built-in function for IPv4 formatting. You’d often use string manipulation or external utilities to achieve the same result. In APL, `format_ipv4` offers a straightforward solution.
---
# geo_info_from_ip_address
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/geo-info-from-ip-address
The `geo_info_from_ip_address` function in APL retrieves geographic information based on an IP address. It maps an IP address to attributes such as city, region, and country, allowing you to perform location-based analytics on your datasets. This function is particularly useful for analyzing web logs, security events, and telemetry data to uncover geographic trends or detect anomalies based on location.
## Usage [#usage]
### Syntax [#syntax]
```kusto
geo_info_from_ip_address(ip_address)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| ------------ | ------ | ------------------------------------------------------------ |
| `ip_address` | string | The IP address for which to retrieve geographic information. |
### Returns [#returns]
A dynamic object containing the IP address’s geographic attributes (if available). The object contains the following fields:
| Name | Type | Description |
| ------------ | ------ | -------------------------------------------- |
| country | string | Country name |
| state | string | State (subdivision) name |
| city | string | City name |
| latitude | real | Latitude coordinate |
| longitude | real | Longitude coordinate |
| country\_iso | string | ISO code of the country |
| time\_zone | string | Time zone in which the IP address is located |
## Use case example [#use-case-example]
Use geographic data to analyze web log traffic.
**Query**
```kusto
['sample-http-logs']
| extend geo_info = geo_info_from_ip_address('172.217.22.14')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20geo_info%20%3D%20geo_info_from_ip_address\('172.217.22.14'\)%22%7D)
**Output**
```json geo_info
{
"state": "",
"longitude": -97.822,
"latitude": 37.751,
"country_iso": "US",
"country": "United States",
"city": "",
"time_zone": "America/Chicago"
}
```
This query identifies the geographic location of the IP address `172.217.22.14`.
## List of related functions [#list-of-related-functions]
* [has\_any\_ipv4](/apl/scalar-functions/ip-functions/has-any-ipv4): Matches any IP address in a string column with a list of IP addresses or ranges.
* [has\_ipv4](/apl/scalar-functions/ip-functions/has-ipv4): Checks if a single IP address is present in a string column.
* [ipv4\_is\_in\_range](/apl/scalar-functions/ip-functions/ipv4-is-in-range): Checks if an IP address is within a specified range.
* [ipv4\_is\_private](/apl/scalar-functions/ip-functions/ipv4-is-private): Checks if an IPv4 address is within private IP ranges.
## IPv4 Examples [#ipv4-examples]
### Extract geolocation information from IPv4 address [#extract-geolocation-information-from-ipv4-address]
```kusto
['sample-http-logs']
| extend ip_location = geo_info_from_ip_address('172.217.11.4')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20ip_location%20%3D%20geo_info_from_ip_address%28%27172.217.11.4%27%29%22%7D)
### Project geolocation information from IPv4 address [#project-geolocation-information-from-ipv4-address]
```kusto
['sample-http-logs']
| project ip_location=geo_info_from_ip_address('20.53.203.50')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20project%20ip_location%3Dgeo_info_from_ip_address%28%2720.53.203.50%27%29%22%7D)
### Filter geolocation information from IPv4 address [#filter-geolocation-information-from-ipv4-address]
```kusto
['sample-http-logs']
| extend ip_location = geo_info_from_ip_address('20.53.203.50')
| where ip_location.country == "Australia" and ip_location.country_iso == "AU" and ip_location.state == "New South Wales"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20ip_location%20%3D%20geo_info_from_ip_address%28%2720.53.203.50%27%29%5Cn%7C%20where%20ip_location.country%20%3D%3D%20%5C%22Australia%5C%22%20and%20ip_location.country_iso%20%3D%3D%20%5C%22AU%5C%22%20and%20ip_location.state%20%3D%3D%20%5C%22New%20South%20Wales%5C%22%22%7D)
### Group geolocation information from IPv4 address [#group-geolocation-information-from-ipv4-address]
```kusto
['sample-http-logs']
| extend ip_location = geo_info_from_ip_address('20.53.203.50')
| summarize Count=count() by ip_location.state, ip_location.city, ip_location.latitude, ip_location.longitude
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20ip_location%20%3D%20geo_info_from_ip_address%28%2720.53.203.50%27%29%5Cn%7C%20summarize%20Count%3Dcount%28%29%20by%20ip_location.state%2C%20ip_location.city%2C%20ip_location.latitude%2C%20ip_location.longitude%22%7D)
## IPv6 Examples [#ipv6-examples]
### Extract geolocation information from IPv6 address [#extract-geolocation-information-from-ipv6-address]
```kusto
['sample-http-logs']
| extend ip_location = geo_info_from_ip_address('2607:f8b0:4005:805::200e')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20ip_location%20%3D%20geo_info_from_ip_address%28%272607%3Af8b0%3A4005%3A805%3A%3A200e%27%29%22%7D)
### Project geolocation information from IPv6 address [#project-geolocation-information-from-ipv6-address]
```kusto
['sample-http-logs']
| project ip_location=geo_info_from_ip_address('2a03:2880:f12c:83:face:b00c::25de')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20project%20ip_location%3Dgeo_info_from_ip_address%28%272a03%3A2880%3Af12c%3A83%3Aface%3Ab00c%3A%3A25de%27%29%22%7D)
### Filter geolocation information from IPv6 address [#filter-geolocation-information-from-ipv6-address]
```kusto
['sample-http-logs']
| extend ip_location = geo_info_from_ip_address('2a03:2880:f12c:83:face:b00c::25de')
| where ip_location.country == "United States" and ip_location.country_iso == "US" and ip_location.state == "Florida"
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20ip_location%20%3D%20geo_info_from_ip_address%28%272a03%3A2880%3Af12c%3A83%3Aface%3Ab00c%3A%3A25de%27%29%5Cn%7C%20where%20ip_location.country%20%3D%3D%20%5C%22United%20States%5C%22%20and%20ip_location.country_iso%20%3D%3D%20%5C%22US%5C%22%20and%20ip_location.state%20%3D%3D%20%5C%22Florida%5C%22%22%7D)
### Group geolocation information from IPv6 address [#group-geolocation-information-from-ipv6-address]
```kusto
['sample-http-logs']
| extend ip_location = geo_info_from_ip_address('2a03:2880:f12c:83:face:b00c::25de')
| summarize Count=count() by ip_location.state, ip_location.city, ip_location.latitude, ip_location.longitude
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%5Cn%7C%20extend%20ip_location%20%3D%20geo_info_from_ip_address%28%272a03%3A2880%3Af12c%3A83%3Aface%3Ab00c%3A%3A25de%27%29%5Cn%7C%20summarize%20Count%3Dcount%28%29%20by%20ip_location.state%2C%20ip_location.city%2C%20ip_location.latitude%2C%20ip_location.longitude%22%7D)
## Other query languages [#other-query-languages]
In Splunk, the equivalent process often involves using lookup tables or add-ons to resolve IP addresses into geographic details. In APL, `geo_info_from_ip_address` performs the resolution natively within the query, streamlining the workflow.
```sql Splunk example
| eval geo_info = iplocation(client_ip)
```
```kusto APL equivalent
['sample-http-logs']
| extend geo_info = geo_info_from_ip_address(client_ip)
```
In SQL, geographic information retrieval typically requires a separate database or API integration. In APL, the `geo_info_from_ip_address` function directly provides geographic details, simplifying the query process.
```sql SQL example
SELECT ip_to_location(client_ip) AS geo_info
FROM sample_http_logs
```
```kusto APL equivalent
['sample-http-logs']
| extend geo_info = geo_info_from_ip_address(client_ip)
```
---
# has_any_ipv4_prefix
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/has-any-ipv4-prefix
The `has_any_ipv4_prefix` function in APL lets you determine if an IPv4 address starts with any prefix in a list of specified prefixes. This function is particularly useful for filtering, segmenting, and analyzing data involving IP addresses, such as log data, network traffic, or security events. By efficiently checking prefixes, you can identify IP ranges of interest for purposes like geolocation, access control, or anomaly detection.
## Usage [#usage]
### Syntax [#syntax]
```kusto
has_any_ipv4_prefix(ip_column, prefixes)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| ----------- | --------- | ----------------------------------------- |
| `ip_column` | `string` | The column containing the IPv4 address. |
| `prefixes` | `dynamic` | A list of IPv4 prefixes to check against. |
### Returns [#returns]
* `true` if the IPv4 address matches any of the specified prefixes.
* `false` otherwise.
## Use case example [#use-case-example]
Detect requests from specific IP ranges.
**Query**
```kusto
['sample-http-logs']
| extend has_ip_prefix = has_any_ipv4_prefix('192.168.0.1', dynamic(['172.16.', '192.168.']))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20has_ip_prefix%20%3D%20has_any_ipv4_prefix\('192.168.0.1'%2C%20dynamic\(%5B'172.16.'%2C%20'192.168.'%5D\)\)%22%7D)
**Output**
| \_time | has\_ip\_prefix | status |
| ------------------- | --------------- | ------ |
| 2024-11-14T10:00:00 | true | 200 |
## List of related functions [#list-of-related-functions]
* [has\_any\_ipv4](/apl/scalar-functions/ip-functions/has-any-ipv4): Matches any IP address in a string column with a list of IP addresses or ranges.
* [has\_ipv4\_prefix](/apl/scalar-functions/ip-functions/has-ipv4-prefix): Checks if an IPv4 address matches a single prefix.
* [has\_ipv4](/apl/scalar-functions/ip-functions/has-ipv4): Checks if a single IP address is present in a string column.
## Other query languages [#other-query-languages]
In Splunk SPL, checking if an IP address matches a prefix requires custom search logic with pattern matching or conditional expressions. In APL, `has_any_ipv4_prefix` provides a direct and optimized way to perform this check.
```sql Splunk example
| eval is_in_range=if(match(ip, "10.*") OR match(ip, "192.168.*"), 1, 0)
```
```kusto APL equivalent
['sample-http-logs']
| where has_any_ipv4_prefix(uri, dynamic(['10.', '192.168.']))
```
In ANSI SQL, you need to use `LIKE` clauses combined with `OR` operators to check prefixes. In APL, the `has_any_ipv4_prefix` function simplifies this process by accepting a dynamic list of prefixes.
```sql SQL example
SELECT * FROM logs
WHERE ip LIKE '10.%' OR ip LIKE '192.168.%';
```
```kusto APL equivalent
['sample-http-logs']
| where has_any_ipv4_prefix(uri, dynamic(['10.', '192.168.']))
```
---
# has_any_ipv4
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/has-any-ipv4
The `has_any_ipv4` function in Axiom Processing Language (APL) allows you to check whether a specified column contains any IPv4 addresses from a given set of IPv4 addresses or CIDR ranges. This function is useful when analyzing logs, tracing OpenTelemetry data, or investigating security events to quickly filter records based on a predefined list of IP addresses or subnets.
## Usage [#usage]
### Syntax [#syntax]
```kusto
has_any_ipv4(column, ip_list)
```
### Parameters [#parameters]
| Parameter | Description | Type |
| --------- | ---------------------------------------- | --------- |
| `column` | The column to evaluate. | `string` |
| `ip_list` | A list of IPv4 addresses or CIDR ranges. | `dynamic` |
### Returns [#returns]
A boolean value indicating whether the specified column contains any of the given IPv4 addresses or matches any of the CIDR ranges in `ip_list`.
## Use case example [#use-case-example]
When analyzing logs, you can use `has_any_ipv4` to filter requests from specific IPv4 addresses or subnets.
**Query**
```kusto
['sample-http-logs']
| extend has_ip = has_any_ipv4('192.168.1.1', dynamic(['192.168.1.1', '192.168.0.0/16']))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20has_ip%20%3D%20has_any_ipv4\('192.168.1.1'%2C%20dynamic\(%5B'192.168.1.1'%2C%20'192.168.0.0%2F16'%5D\)\)%22%7D)
**Output**
| \_time | has\_ip | status |
| ------------------- | ------- | ------ |
| 2024-11-14T10:00:00 | true | 200 |
This query identifies log entries from specific IPs or subnets.
## List of related functions [#list-of-related-functions]
* [has\_ipv4\_prefix](/apl/scalar-functions/ip-functions/has-ipv4-prefix): Checks if an IPv4 address matches a single prefix.
* [has\_ipv4](/apl/scalar-functions/ip-functions/has-ipv4): Checks if a single IP address is present in a string column.
## Other query languages [#other-query-languages]
In Splunk, you typically use the `cidrmatch` or similar functions for working with IP ranges. In APL, `has_any_ipv4` offers similar functionality by matching any IPv4 address in a column against multiple values or ranges.
```sql Splunk example
| where cidrmatch("192.168.1.0/24", ip_field)
```
```kusto APL equivalent
['sample-http-logs']
| where has_any_ipv4('ip_field', dynamic(['192.168.1.0/24']))
```
SQL doesn’t natively support CIDR matching or IP address comparison out of the box. In APL, the `has_any_ipv4` function is designed to simplify these checks with concise syntax.
```sql SQL example
SELECT * FROM logs WHERE ip_field = '192.168.1.1' OR ip_field = '192.168.1.2';
```
```kusto APL equivalent
['sample-http-logs']
| where has_any_ipv4('ip_field', dynamic(['192.168.1.1', '192.168.1.2']))
```
---
# has_ipv4_prefix
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/has-ipv4-prefix
The `has_ipv4_prefix` function checks if an IPv4 address starts with a specified prefix. Use this function to filter or match IPv4 addresses efficiently based on their prefixes. It’s particularly useful when analyzing network traffic, identifying specific address ranges, or working with CIDR-based IP filtering in datasets.
## Usage [#usage]
### Syntax [#syntax]
```kusto
has_ipv4_prefix(column_name, prefix)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| ------------- | ------ | ---------------------------------------------------------------------- |
| `column_name` | string | The column containing the IPv4 addresses to evaluate. |
| `prefix` | string | The prefix to check for, expressed as a string (for example, "192.0"). |
### Returns [#returns]
* Returns a Boolean (`true` or `false`) indicating whether the IPv4 address starts with the specified prefix.
## Use case example [#use-case-example]
Use `has_ipv4_prefix` to filter logs for requests originating from a specific IP range.
**Query**
```kusto
['sample-http-logs']
| extend has_prefix= has_ipv4_prefix('192.168.0.1', '192.168.')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20has_prefix%3D%20has_ipv4_prefix\('192.168.0.1'%2C%20'192.168.'\)%22%7D)
**Output**
| \_time | has\_prefix | status |
| ------------------- | ----------- | ------ |
| 2024-11-14T10:00:00 | true | 200 |
## List of related functions [#list-of-related-functions]
* [has\_any\_ipv4](/apl/scalar-functions/ip-functions/has-any-ipv4): Matches any IP address in a string column with a list of IP addresses or ranges.
* [has\_ipv4](/apl/scalar-functions/ip-functions/has-ipv4): Checks if a single IP address is present in a string column.
## Other query languages [#other-query-languages]
In Splunk SPL, you use string-based matching or CIDR functions for IP comparison. In APL, `has_ipv4_prefix` simplifies the process by directly comparing an IP against a prefix.
```sql Splunk example
| eval is_match = if(cidrmatch("192.168.0.0/24", ip), true, false)
```
```kusto APL equivalent
['sample-http-logs']
| where has_ipv4_prefix(uri, "192.168.0")
```
In ANSI SQL, there is no direct equivalent to `has_ipv4_prefix`. You would typically use substring or LIKE operators for partial matching. APL provides a dedicated function for this purpose, ensuring simplicity and accuracy.
```sql SQL example
SELECT *
FROM sample_http_logs
WHERE ip LIKE '192.168.0%'
```
```kusto APL equivalent
['sample-http-logs']
| where has_ipv4_prefix(uri, "192.168.0")
```
---
# has_ipv4
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/has-ipv4
## Introduction [#introduction]
The `has_ipv4` function in Axiom Processing Language (APL) allows you to check if a specified IPv4 address appears in a given text. The function is useful for tasks such as analyzing logs, monitoring security events, and processing network data where you need to identify or filter entries based on IP addresses.
To use `has_ipv4`, ensure that IP addresses in the text are properly delimited with non-alphanumeric characters. For example:
* **Valid:** `192.168.1.1` in `"Requests from: 192.168.1.1, 10.1.1.115."`
* **Invalid:** `192.168.1.1` in `"192.168.1.1ThisText"`
The function returns `true` if the IP address is valid and present in the text. Otherwise, it returns `false`.
## Usage [#usage]
### Syntax [#syntax]
```kusto
has_ipv4(source, ip_address)
```
### Parameters [#parameters]
| Name | Type | Description |
| ------------ | ------ | --------------------------------------------------- |
| `source` | string | The source text where to search for the IP address. |
| `ip_address` | string | The IP address to look for in the source. |
### Returns [#returns]
* `true` if `ip_address` is a valid IP address and is found in `source`.
* `false` otherwise.
## Use case example [#use-case-example]
Identify requests coming from a specific IP address in HTTP logs.
**Query**
```kusto
['sample-http-logs']
| extend has_ip = has_ipv4('Requests from: 192.168.1.1, 10.1.1.115.', '192.168.1.1')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20has_ip%20%3D%20has_ipv4\('Requests%20from%3A%20192.168.1.1%2C%2010.1.1.115.'%2C%20'192.168.1.1'\)%22%7D)
**Output**
| \_time | has\_ip | status |
| ------------------- | ------- | ------ |
| 2024-11-14T10:00:00 | true | 200 |
## List of related functions [#list-of-related-functions]
* [has\_any\_ipv4](/apl/scalar-functions/ip-functions/has-any-ipv4): Matches any IP address in a string column with a list of IP addresses or ranges.
* [has\_ipv4\_prefix](/apl/scalar-functions/ip-functions/has-ipv4-prefix): Checks if an IPv4 address matches a single prefix.
## Other query languages [#other-query-languages]
In Splunk SPL, you might use `match` or similar regex-based functions to locate IPv4 addresses in a string. In APL, `has_ipv4` provides a simpler and more efficient alternative for detecting specific IPv4 addresses.
```sql Splunk example
search sourcetype=access_combined | eval isPresent=match(_raw, "192\.168\.1\.1")
```
```kusto APL equivalent
print result=has_ipv4('05:04:54 192.168.1.1 GET /favicon.ico 404', '192.168.1.1')
```
In ANSI SQL, locating IPv4 addresses often involves string manipulation or pattern matching with `LIKE` or regular expressions. APL’s `has_ipv4` function provides a more concise and purpose-built approach.
```sql SQL example
SELECT CASE WHEN column_text LIKE '%192.168.1.1%' THEN TRUE ELSE FALSE END AS result
FROM log_table;
```
```kusto APL equivalent
print result=has_ipv4('05:04:54 192.168.1.1 GET /favicon.ico 404', '192.168.1.1')
```
---
# ipv4_compare
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv4-compare
The `ipv4_compare` function in APL allows you to compare two IPv4 addresses lexicographically or numerically. This is useful for sorting IP addresses, validating CIDR ranges, or detecting overlaps between IP ranges. It’s particularly helpful in analyzing network logs, performing security investigations, and managing IP-based filters or rules.
## Usage [#usage]
### Syntax [#syntax]
```kusto
ipv4_compare(ip1, ip2)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------ | ----------------------------------- |
| `ip1` | string | The first IPv4 address to compare. |
| `ip2` | string | The second IPv4 address to compare. |
### Returns [#returns]
* Returns `1` if the long representation of `ip1` is greater than the long representation of `ip2`
* Returns `0` if the long representation of `ip1` is equal to the long representation of `ip2`
* Returns `-1` if the long representation of `ip1` is less than the long representation of `ip2`
* Returns `null` if the conversion fails.
## Use case example [#use-case-example]
You can use `ipv4_compare` to sort logs based on IP addresses or to identify connections between specific IPs.
**Query**
```kusto
['sample-http-logs']
| extend ip1 = '192.168.1.1', ip2 = '192.168.1.10'
| extend comparison = ipv4_compare(ip1, ip2)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20ip1%20%3D%20%27192.168.1.1%27%2C%20ip2%20%3D%20%27192.168.1.10%27%20%7C%20extend%20comparison%20%3D%20ipv4_compare\(ip1%2C%20ip2\)%22%7D)
**Output**
| ip1 | ip2 | comparison |
| ----------- | ------------ | ---------- |
| 192.168.1.1 | 192.168.1.10 | -1 |
This query compares two hardcoded IP addresses. It returns `-1`, indicating that `192.168.1.1` is lexicographically less than `192.168.1.10`.
## List of related functions [#list-of-related-functions]
* [ipv4\_is\_in\_range](/apl/scalar-functions/ip-functions/ipv4-is-in-range): Checks if an IP address is within a specified range.
* [ipv4\_is\_private](/apl/scalar-functions/ip-functions/ipv4-is-private): Checks if an IPv4 address is within private IP ranges.
* [parse\_ipv4](/apl/scalar-functions/ip-functions/parse-ipv4): Converts a dotted-decimal IP address into a numeric representation.
## Other query languages [#other-query-languages]
In Splunk SPL, similar functionality can be achieved using `sort` or custom commands. In APL, `ipv4_compare` is a dedicated function for comparing two IPv4 addresses.
```sql Splunk example
| eval comparison = if(ip1 < ip2, -1, if(ip1 == ip2, 0, 1))
```
```kusto APL equivalent
| extend comparison = ipv4_compare(ip1, ip2)
```
In ANSI SQL, you might manually parse or order IP addresses as strings. In APL, `ipv4_compare` simplifies this task with built-in support for IPv4 comparison.
```sql SQL example
SELECT CASE
WHEN ip1 < ip2 THEN -1
WHEN ip1 = ip2 THEN 0
ELSE 1
END AS comparison
FROM ips;
```
```kusto APL equivalent
['sample-http-logs']
| extend comparison = ipv4_compare(ip1, ip2)
```
---
# ipv4_is_in_any_range
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv4-is-in-any-range
The `ipv4_is_in_any_range` function checks whether a given IPv4 address belongs to any range of IPv4 subnets. You can use it to evaluate whether an IP address falls within a set of CIDR blocks or IP ranges, which is useful for filtering, monitoring, or analyzing network traffic in your datasets.
This function is particularly helpful for security monitoring, analyzing log data for specific geolocated traffic, or validating access based on allowed IP ranges.
## Usage [#usage]
### Syntax [#syntax]
```kusto
ipv4_is_in_any_range(ip_address: string, ranges: dynamic)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| ------------ | ------- | --------------------------------------------------------------------------- |
| `ip_address` | string | The IPv4 address to evaluate. |
| `ranges` | dynamic | A list of IPv4 ranges or CIDR blocks to check against (in JSON array form). |
### Returns [#returns]
* `true` if the IP address is in any specified range.
* `false` otherwise.
* `null` if the conversion of a string wasn’t successful.
## Use case example [#use-case-example]
Identify log entries from specific subnets, such as local office IP ranges.
**Query**
```kusto
['sample-http-logs']
| extend is_in_range = ipv4_is_in_any_range('192.168.0.0', dynamic(['192.168.0.0/24', '10.0.0.0/8']))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%20%7C%20extend%20is_in_range%20%3D%20ipv4_is_in_any_range\('192.168.0.0'%2C%20dynamic\(%5B'192.168.0.0%2F24'%2C%20'10.0.0.0%2F8'%5D\)\)%22%7D)
**Output**
| \_time | id | method | uri | status | is\_in\_range |
| ------------------- | ------- | ------ | ----- | ------ | ------------- |
| 2024-11-14 10:00:00 | user123 | GET | /home | 200 | true |
## List of related functions [#list-of-related-functions]
* [ipv4\_compare](/apl/scalar-functions/ip-functions/ipv4-compare): Compares two IPv4 addresses lexicographically. Use for sorting or range evaluations.
* [ipv4\_is\_in\_range](/apl/scalar-functions/ip-functions/ipv4-is-in-range): Checks if an IP address is within a specified range.
* [ipv4\_is\_private](/apl/scalar-functions/ip-functions/ipv4-is-private): Checks if an IPv4 address is within private IP ranges.
* [parse\_ipv4](/apl/scalar-functions/ip-functions/parse-ipv4): Converts a dotted-decimal IP address into a numeric representation.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `cidrmatch` to check if an IP belongs to a range. In APL, `ipv4_is_in_any_range` is equivalent, but it supports evaluating against multiple ranges simultaneously.
```sql Splunk example
| eval is_in_range = cidrmatch("192.168.0.0/24", ip_address)
```
```kusto APL equivalent
['dataset']
| extend is_in_range = ipv4_is_in_any_range(ip_address, dynamic(['192.168.0.0/24', '10.0.0.0/8']))
```
ANSI SQL doesn’t have a built-in function for checking IP ranges. Instead, you use custom functions or comparisons. APL’s `ipv4_is_in_any_range` simplifies this by handling multiple CIDR blocks and ranges in a single function.
```sql SQL example
SELECT *,
CASE WHEN ip_address BETWEEN '192.168.0.0' AND '192.168.0.255' THEN 1 ELSE 0 END AS is_in_range
FROM dataset;
```
```kusto APL equivalent
['dataset']
| extend is_in_range = ipv4_is_in_any_range(ip_address, dynamic(['192.168.0.0/24', '10.0.0.0/8']))
```
---
# ipv4_is_in_range
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv4-is-in-range
The `ipv4_is_in_range` function in Axiom Processing Language (APL) determines whether an IPv4 address falls within a specified range of addresses. This function is particularly useful for filtering or grouping logs based on geographic regions, network blocks, or security zones.
You can use this function to:
* Analyze logs for requests originating from specific IP address ranges.
* Detect unauthorized or suspicious activity by isolating traffic outside trusted IP ranges.
* Aggregate metrics for specific IP blocks or subnets.
## Usage [#usage]
### Syntax [#syntax]
```kusto
ipv4_is_in_range(ip: string, range: string)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------ | ---------------------------------------------------------------- |
| `ip` | string | The IPv4 address to evaluate. |
| `range` | string | The IPv4 range in CIDR notation (for example, `192.168.1.0/24`). |
### Returns [#returns]
* `true` if the IPv4 address is in the range.
* `false` otherwise.
* `null` if the conversion of a string wasn’t successful.
## Use case example [#use-case-example]
You can use `ipv4_is_in_range` to identify traffic from specific geographic regions or service provider IP blocks.
**Query**
```kusto
['sample-http-logs']
| extend in_range = ipv4_is_in_range('192.168.1.0', '192.168.1.0/24')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20in_range%20%3D%20ipv4_is_in_range\('192.168.1.0'%2C%20'192.168.1.0%2F24'\)%22%7D)
**Output**
| geo.city | in\_range |
| -------- | --------- |
| Seattle | true |
| Denver | true |
This query identifies the number of requests from IP addresses in the specified range.
## List of related functions [#list-of-related-functions]
* [ipv4\_compare](/apl/scalar-functions/ip-functions/ipv4-compare): Compares two IPv4 addresses lexicographically. Use for sorting or range evaluations.
* [ipv4\_is\_private](/apl/scalar-functions/ip-functions/ipv4-is-private): Checks if an IPv4 address is within private IP ranges.
* [parse\_ipv4](/apl/scalar-functions/ip-functions/parse-ipv4): Converts a dotted-decimal IP address into a numeric representation.
## Other query languages [#other-query-languages]
The `ipv4_is_in_range` function in APL operates similarly to the `cidrmatch` function in Splunk SPL. Both determine whether an IP address belongs to a specified range, but APL uses a different syntax and format.
```sql Splunk example
| eval in_range = cidrmatch("192.168.0.0/24", ip_address)
```
```kusto APL equivalent
['sample-http-logs']
| extend in_range = ipv4_is_in_range(ip_address, '192.168.0.0/24')
```
ANSI SQL doesn’t have a built-in equivalent for determining if an IP address belongs to a CIDR range. In SQL, you would typically need custom functions or expressions to achieve this. APL’s `ipv4_is_in_range` provides a concise way to perform this operation.
```sql SQL example
SELECT CASE
WHEN ip_address BETWEEN '192.168.0.0' AND '192.168.0.255' THEN 1
ELSE 0
END AS in_range
FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend in_range = ipv4_is_in_range(ip_address, '192.168.0.0/24')
```
---
# ipv4_is_match
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv4-is-match
The `ipv4_is_match` function in APL helps you determine whether a given IPv4 address matches a specific IPv4 pattern. This function is especially useful for tasks that involve IP address filtering, including network security analyses, log file inspections, and geo-locational data processing. By specifying patterns that include wildcards or CIDR notations, you can efficiently check if an IP address falls within defined ranges or meets specific conditions.
## Usage [#usage]
### Syntax [#syntax]
```kusto
ipv4_is_match(ipaddress1, ipaddress2, prefix)
```
### Parameters [#parameters]
* **ipaddress1**: A string representing the first IPv4 address you want to evaluate. Use CIDR notation (for example, `192.168.1.0/24`).
* **ipaddress2**: A string representing the second IPv4 address you want to evaluate. Use CIDR notation (for example, `192.168.1.0/24`).
* **prefix**: Optionally, a number between 0 and 32 that specifies the number of most-significant bits taken into account.
### Returns [#returns]
* `true` if the IPv4 addresses match.
* `false` otherwise.
* `null` if the conversion of an IPv4 string wasn’t successful.
## Use case example [#use-case-example]
The `ipv4_is_match` function allows you to identify traffic based on IP addresses, enabling faster identification of traffic patterns and potential issues.
**Query**
```kusto
['sample-http-logs']
| extend is_match = ipv4_is_match('203.0.113.112', '203.0.113.112')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20is_match%20%3D%20ipv4_is_match\('203.0.113.112'%2C%20'203.0.113.112'\)%22%7D)
**Output**
| \_time | id | status | method | uri | is\_match |
| ------------------- | ------------- | ------ | ------ | ----------- | --------- |
| 2023-11-11T13:20:14 | 203.0.113.45 | 403 | GET | /admin | true |
| 2023-11-11T13:30:32 | 203.0.113.101 | 401 | POST | /restricted | true |
## List of related functions [#list-of-related-functions]
* [has\_any\_ipv4](/apl/scalar-functions/ip-functions/has-any-ipv4): Matches any IP address in a string column with a list of IP addresses or ranges.
* [has\_ipv4\_prefix](/apl/scalar-functions/ip-functions/has-ipv4-prefix): Checks if an IPv4 address matches a single prefix.
* [has\_ipv4](/apl/scalar-functions/ip-functions/has-ipv4): Checks if a single IP address is present in a string column.
* [ipv4\_compare](/apl/scalar-functions/ip-functions/ipv4-compare): Compares two IPv4 addresses lexicographically. Use for sorting or range evaluations.
## Other query languages [#other-query-languages]
The `ipv4_is_match` function in APL resembles the `cidrmatch` function in Splunk SPL. Both functions assess whether an IP address falls within a designated CIDR range, but `ipv4_is_match` also supports wildcard pattern matching, providing additional flexibility.
```sql Splunk example
cidrmatch("192.168.1.0/24", ip)
```
```kusto APL equivalent
ipv4_is_match(ip, "192.168.1.0/24")
```
ANSI SQL lacks a direct equivalent to the `ipv4_is_match` function, but you can replicate similar functionality with a combination of `LIKE` and range checking. However, these approaches can be complex and less efficient than `ipv4_is_match`, which simplifies CIDR and wildcard-based IP matching.
```sql SQL example
ip LIKE '192.168.1.0'
```
```kusto APL equivalent
ipv4_is_match(ip, "192.168.1.0")
```
---
# ipv4_is_private
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv4-is-private
The `ipv4_is_private` function determines if an IPv4 address belongs to a private range, as defined by [RFC 1918](https://www.rfc-editor.org/rfc/rfc1918). You can use this function to filter private addresses in datasets such as server logs, network traffic, and other IP-based data.
This function is especially useful in scenarios where you want to:
* Exclude private IPs from logs to focus on public traffic.
* Identify traffic originating from within an internal network.
* Simplify security analysis by categorizing IP addresses.
The private IPv4 addresses reserved for private networks by the Internet Assigned Numbers Authority (IANA) are the following:
| IP address range | Number of addresses | Largest CIDR block (subnet mask) |
| ----------------------------- | ------------------- | -------------------------------- |
| 10.0.0.0 – 10.255.255.255 | 16777216 | 10.0.0.0/8 (255.0.0.0) |
| 172.16.0.0 – 172.31.255.255 | 1048576 | 172.16.0.0/12 (255.240.0.0) |
| 192.168.0.0 – 192.168.255.255 | 65536 | 192.168.0.0/16 (255.255.0.0) |
## Usage [#usage]
### Syntax [#syntax]
```kusto
ipv4_is_private(ip: string)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------ | ------------------------------------------------------ |
| `ip` | string | The IPv4 address to evaluate for private range status. |
### Returns [#returns]
* `true`: The input IP address is private.
* `false`: The input IP address isn’t private.
## Use case example [#use-case-example]
You can use `ipv4_is_private` to filter logs and focus on public traffic for external analysis.
**Query**
```kusto
['sample-http-logs']
| extend is_private = ipv4_is_private('192.168.0.1')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20is_private%20%3D%20ipv4_is_private\('192.168.0.1'\)%22%7D)
**Output**
| geo.country | is\_private |
| ----------- | ----------- |
| USA | true |
| UK | true |
## List of related functions [#list-of-related-functions]
* [ipv4\_compare](/apl/scalar-functions/ip-functions/ipv4-compare): Compares two IPv4 addresses lexicographically. Use for sorting or range evaluations.
* [ipv4\_is\_in\_range](/apl/scalar-functions/ip-functions/ipv4-is-in-range): Checks if an IP address is within a specified range.
* [parse\_ipv4](/apl/scalar-functions/ip-functions/parse-ipv4): Converts a dotted-decimal IP address into a numeric representation.
## Other query languages [#other-query-languages]
In Splunk SPL, you might use a combination of CIDR matching functions or regex to check for private IPs. In APL, the `ipv4_is_private` function offers a built-in and concise way to achieve the same result.
```sql Splunk example
eval is_private=if(cidrmatch("10.0.0.0/8", ip) OR cidrmatch("172.16.0.0/12", ip) OR cidrmatch("192.168.0.0/16", ip), 1, 0)
```
```kusto APL equivalent
['sample-http-logs']
| extend is_private=ipv4_is_private(client_ip)
```
In ANSI SQL, you might use `CASE` statements with CIDR-based checks or regex patterns to detect private IPs. In APL, the `ipv4_is_private` function simplifies this with a single call.
```sql SQL example
SELECT ip,
CASE
WHEN ip LIKE '10.%' OR ip LIKE '172.16.%' OR ip LIKE '192.168.%' THEN 'true'
ELSE 'false'
END AS is_private
FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend is_private=ipv4_is_private(client_ip)
```
---
# ipv4_netmask_suffix
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv4-netmask-suffix
The `ipv4_netmask_suffix` function in APL extracts the netmask suffix from an IPv4 address. The netmask suffix, also known as the subnet prefix length, specifies how many bits are used for the network portion of the address.
This function is useful for network log analysis, security auditing, and infrastructure monitoring. It helps you categorize IP addresses by their subnets, enabling you to detect patterns or anomalies in network traffic or to manage IP allocations effectively.
## Usage [#usage]
### Syntax [#syntax]
```kusto
ipv4_netmask_suffix(ipv4address)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| ------------- | ------ | ------------------------------------------------------------------ |
| `ipv4address` | string | The IPv4 address in CIDR notation (for example, `192.168.1.1/24`). |
### Returns [#returns]
* Returns an integer representing the netmask suffix. For example, `24` for `192.168.1.1/24`.
* Returns the value `32` when the input IPv4 address doesn’t contain the suffix.
* Returns `null` if the input isn’t a valid IPv4 address in CIDR notation.
## Use case example [#use-case-example]
When analyzing network traffic logs, you can extract the netmask suffix to group or filter traffic by subnets.
**Query**
```kusto
['sample-http-logs']
| extend netmask = ipv4_netmask_suffix('192.168.1.1/24')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20netmask%20%3D%20ipv4_netmask_suffix\('192.168.1.1%2F24'\)%22%7D)
**Output**
| geo.country | netmask |
| ----------- | ------- |
| USA | 24 |
| UK | 24 |
## List of related functions [#list-of-related-functions]
* [ipv4\_compare](/apl/scalar-functions/ip-functions/ipv4-compare): Compares two IPv4 addresses lexicographically. Use for sorting or range evaluations.
* [ipv4\_is\_in\_range](/apl/scalar-functions/ip-functions/ipv4-is-in-range): Checks if an IP address is within a specified range.
* [ipv4\_is\_private](/apl/scalar-functions/ip-functions/ipv4-is-private): Checks if an IPv4 address is within private IP ranges.
* [parse\_ipv4](/apl/scalar-functions/ip-functions/parse-ipv4): Converts a dotted-decimal IP address into a numeric representation.
## Other query languages [#other-query-languages]
In Splunk, netmask suffix extraction typically requires manual parsing or custom scripts. In APL, the `ipv4_netmask_suffix` function simplifies this task by directly extracting the suffix from an IPv4 address in CIDR notation.
```spl Splunk example
eval netmask = replace(ip, "^.*?/", "")
```
```kusto APL equivalent
extend netmask = ipv4_netmask_suffix(ip)
```
In ANSI SQL, extracting the netmask suffix often involves using string functions like `SUBSTRING` or `CHARINDEX`. In APL, the `ipv4_netmask_suffix` function provides a direct and efficient alternative.
```sql SQL example
SELECT SUBSTRING(ip, CHARINDEX('/', ip) + 1, LEN(ip)) AS netmask FROM logs;
```
```kusto APL equivalent
extend netmask = ipv4_netmask_suffix(ip)
```
---
# ipv6_compare
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv6-compare
Use the `ipv6_compare` function to compare two IPv6 addresses and determine their relative order. This function helps you evaluate whether one address is less than, equal to, or greater than another. It returns `-1`, `0`, or `1` accordingly.
You can use `ipv6_compare` in scenarios where IPv6 addresses are relevant, such as sorting traffic logs, grouping metrics by address ranges, or identifying duplicate or misordered entries. It’s especially useful in network observability and security use cases where working with IPv6 is common.
## Usage [#usage]
### Syntax [#syntax]
```kusto
ipv6_compare(ipv6_1, ipv6_2)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ------ | ----------------------------------- |
| `ipv6_1` | string | The first IPv6 address to compare. |
| `ipv6_2` | string | The second IPv6 address to compare. |
### Returns [#returns]
An integer that represents the result of the comparison:
* `-1` if `ipv6_1` is less than `ipv6_2`
* `0` if `ipv6_1` is equal to `ipv6_2`
* `1` if `ipv6_1` is greater than `ipv6_2`
## Example [#example]
Use `ipv6_compare` to identify whether requests from certain IPv6 addresses fall into specific ranges or appear out of expected order.
**Query**
```kusto
['sample-http-logs']
| extend comparison = ipv6_compare('2001:db8::1', '2001:db8::abcd')
| project _time, uri, method, status, comparison
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20comparison%20%3D%20ipv6_compare\('2001%3Adb8%3A%3A1'%2C%20'2001%3Adb8%3A%3Aabcd'\)%20%7C%20project%20_time%2C%20uri%2C%20method%2C%20status%2C%20comparison%22%7D)
**Output**
| \_time | uri | method | status | comparison |
| -------------------- | ----------- | ------ | ------ | ---------- |
| 2025-06-29T22:10:00Z | /products/1 | GET | 200 | -1 |
This example compares two static IPv6 addresses and attaches the result to each row for further filtering or grouping.
## List of related functions [#list-of-related-functions]
* [ipv6\_is\_match](/apl/scalar-functions/ip-functions/ipv6-is-match): Checks if an IPv6 address matches a given subnet. Use it for range filtering instead of sorting or comparison.
* [ipv4\_is\_private](/apl/scalar-functions/ip-functions/ipv4-is-private): Determines whether an IPv4 address is in a private range. Use this to filter non-public traffic.
* [ipv4\_compare](/apl/scalar-functions/ip-functions/ipv4-compare): Works the same way as `ipv6_compare` but for IPv4 addresses. Use it when your data contains IPv4 instead of IPv6.
## Other query languages [#other-query-languages]
Splunk SPL doesn’t have a built-in function for directly comparing IPv6 addresses. Users often work around this limitation by converting the addresses into a comparable numeric format using external scripts or custom commands.
```sql Splunk example
| eval ip1 = "2001:db8::1", ip2 = "2001:db8::2"
| eval comparison = if(ip1 == ip2, 0, if(ip1 < ip2, -1, 1))
```
```kusto APL equivalent
print comparison = ipv6_compare('2001:db8::1', '2001:db8::2')
```
ANSI SQL doesn’t natively support IPv6 comparisons. Typically, users must store IPv6 addresses as strings or binary values and write custom logic to compare them.
```sql SQL example
SELECT CASE
WHEN ip1 = ip2 THEN 0
WHEN ip1 < ip2 THEN -1
ELSE 1
END AS comparison
FROM my_table
```
```kusto APL equivalent
print comparison = ipv6_compare('2001:db8::1', '2001:db8::2')
```
---
# ipv6_is_in_any_range
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv6-is-in-any-range
Use the `ipv6_is_in_any_range` function to determine whether a given IPv6 address belongs to any of a specified set of IPv6 CIDR ranges. This function is particularly useful in log enrichment, threat detection, and network analysis tasks that involve validating or filtering IP addresses against allowlists or blocklists.
You can use this function to:
* Detect whether traffic originates from known internal or external networks.
* Match IPv6 addresses against predefined address ranges for compliance or security auditing.
* Filter datasets based on whether requesters fall into allowed or disallowed IP zones.
## Usage [#usage]
### Syntax [#syntax]
```kusto
ipv6_is_in_any_range(ipv6_address, ipv6_ranges)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------------- | --------------- | ---------------------------------------------------------------- |
| `ipv6_address` | `string` | An IPv6 address in standard format (for example, `2001:db8::1`). |
| `ipv6_ranges` | `dynamic array` | A JSON array of IPv6 CIDR strings to compare against. |
### Returns [#returns]
A `bool` value:
* `true` if the given IPv6 address is within any of the provided CIDR ranges.
* `false` otherwise.
## Example [#example]
You want to detect HTTP requests from a specific internal IPv6 block.
**Query**
```kusto
['sample-http-logs']
| extend inRange = ipv6_is_in_any_range('2001:db8::1234', dynamic(['2001:db8::/32', 'fd00::/8']))
| project _time, uri, method, status, inRange
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20inRange%20%3D%20ipv6_is_in_any_range\('2001%3Adb8%3A%3A1234'%2C%20dynamic\(%5B'2001%3Adb8%3A%3A%2F32'%2C%20'fd00%3A%3A%2F8'%5D\)\)%20%7C%20project%20_time%2C%20id%2C%20uri%2C%20method%2C%20status%2C%20inRange%22%7D)
**Output**
| \_time | uri | method | status | inRange |
| -------------------- | ------------ | ------ | ------ | ------- |
| 2025-06-30T01:00:00Z | /api/login | POST | 200 | true |
| 2025-06-30T01:01:00Z | /healthcheck | GET | 204 | true |
## List of related functions [#list-of-related-functions]
* [ipv4\_is\_in\_any\_range](/apl/scalar-functions/ip-functions/ipv4-is-in-any-range): Use this function when working with IPv4 addresses instead of IPv6.
* [ipv6\_compare](/apl/scalar-functions/ip-functions/ipv6-compare): Compares two IPv6 addresses. Use this for sorting or deduplication rather than range matching.
* [ipv6\_is\_match](/apl/scalar-functions/ip-functions/ipv6-is-match): Checks whether an IPv6 address matches a specific range. Use this if you need to test against a single CIDR block.
## Other query languages [#other-query-languages]
Splunk doesn’t offer a built-in function that directly checks if an IP falls within a list of CIDR ranges. Typically, SPL users must write custom logic using `cidrmatch()` repeatedly or rely on lookup tables.
```sql Splunk example
| eval is_internal = if(cidrmatch("2001:db8::/32", ip), "true", "false")
```
```kusto APL equivalent
ipv6_is_in_any_range('2001:db8::1', dynamic(['2001:db8::/32']))
```
ANSI SQL doesn’t natively support IPv6-aware CIDR range checks. Such functionality usually requires user-defined functions or external extensions.
```sql SQL example
-- Typically handled via stored procedures or UDFs in extended SQL environments
SELECT ip, is_in_range(ip, '2001:db8::/32') FROM traffic_logs
```
```kusto APL equivalent
ipv6_is_in_any_range('2001:db8::1', dynamic(['2001:db8::/32']))
```
---
# ipv6_is_in_range
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv6-is-in-range
Use the `ipv6_is_in_range` function to check whether an IPv6 address falls within a specified IPv6 CIDR range. This is useful when you need to classify, filter, or segment network traffic by address range—such as identifying requests from internal subnets, geo-localized regional blocks, or known malicious networks.
You can use this function when analyzing HTTP logs, trace telemetry, or security events where IPv6 addresses are present, and you want to restrict attention to or exclude certain address ranges.
## Usage [#usage]
### Syntax [#syntax]
```kusto
ipv6_is_in_range(ipv6: string, cidr_range: string)
```
### Parameters [#parameters]
| Name | Type | Description |
| ------------ | ------ | --------------------------------------------- |
| `ipv6` | string | The IPv6 address to check. |
| `cidr_range` | string | The IPv6 CIDR block (e.g. `'2001:db8::/32'`). |
### Returns [#returns]
A `bool` value:
* `true` if the IPv6 address is within the specified CIDR range.
* `false` otherwise.
## Example [#example]
Use this function to isolate internal service calls originating from a designated IPv6 block.
**Query**
```kusto
['otel-demo-traces']
| extend inRange = ipv6_is_in_range('fd00::a1b2', 'fd00::/8')
| project _time, span_id, ['service.name'], duration, inRange
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20inRange%20%3D%20ipv6_is_in_range\('fd00%3A%3Aa1b2'%2C%20'fd00%3A%3A%2F8'\)%20%7C%20project%20_time%2C%20span_id%2C%20%5B'service.name'%5D%2C%20duration%2C%20inRange%22%7D)
**Output**
| \_time | span\_id | \['service.name'] | duration | inRange |
| -------------------- | -------- | ----------------- | ---------- | ------- |
| 2025-06-28T11:20:00Z | span-124 | frontend | 00:00:02.4 | true |
| 2025-06-28T11:21:03Z | span-209 | cartservice | 00:00:01.1 | true |
## List of related functions [#list-of-related-functions]
* [ipv4\_is\_in\_range](/apl/scalar-functions/ip-functions/ipv4-is-in-range): Checks whether an IPv4 address is within a specified CIDR range. Use this function when working with IPv4 instead of IPv6.
* [ipv6\_compare](/apl/scalar-functions/ip-functions/ipv6-compare): Compares two IPv6 addresses. Use when you want to sort or test address equality or ordering.
* [ipv6\_is\_match](/apl/scalar-functions/ip-functions/ipv6-is-match): Checks whether an IPv6 address matches a pattern. Use for wildcard or partial-match filtering rather than range checking.
## Other query languages [#other-query-languages]
In Splunk SPL, IP range checking for IPv6 addresses typically requires custom scripts or manual logic, as there is no built-in function equivalent to `ipv6_is_in_range`.
```sql Splunk example
| eval inRange=if(cidrmatch("2001:db8::/32", src_ip), "yes", "no")
```
```kusto APL equivalent
['sample-http-logs']
| extend inRange = ipv6_is_in_range(src_ip, '2001:db8::/32')
```
ANSI SQL doesn’t have native functions for CIDR range checks on IPv6 addresses. You typically rely on user-defined functions (UDFs) or external tooling. In APL, `ipv6_is_in_range` provides this capability out of the box.
```sql SQL example
-- Using a hypothetical UDF
SELECT ipv6_in_range(ip_address, '2001:db8::/32') AS in_range FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend inRange = ipv6_is_in_range(src_ip, '2001:db8::/32')
```
---
# ipv6_is_match
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv6-is-match
Use the `ipv6_is_match` function to determine whether an IPv6 address belongs to a specified IPv6 subnet. This function is useful when you want to classify, filter, or route network events based on IPv6 subnet membership.
You can use `ipv6_is_match` in scenarios such as identifying traffic from a known address range, enforcing access control policies, or correlating logs to specific networks. It supports CIDR notation for subnet specification and returns a boolean value for each row in your dataset.
## Usage [#usage]
### Syntax [#syntax]
```kusto
ipv6_is_match(ipv6_address, ipv6_subnet)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------------- | ------ | ----------------------------------------------------------------- |
| `ipv6_address` | string | The full IPv6 address you want to check. |
| `ipv6_subnet` | string | The target subnet in CIDR notation, for example, `2001:db8::/32`. |
### Returns [#returns]
A boolean value:
* `true` if the `ipv6_address` belongs to the specified `ipv6_subnet`.
* `false` otherwise.
## Example [#example]
Identify requests that originate from a known IPv6 subnet.
**Query**
```kusto
['sample-http-logs']
| extend isInternal = ipv6_is_match('2001:db8:abcd::1', '2001:db8::/32')
| project _time, uri, method, status, isInternal
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20isInternal%20%3D%20ipv6_is_match\('2001%3Adb8%3Aabcd%3A%3A1'%2C%20'2001%3Adb8%3A%3A%2F32'\)%20%7C%20project%20_time%2C%20uri%2C%20method%2C%20status%2C%20isInternal%22%7D)
**Output**
| \_time | uri | method | status | isInternal |
| -------------------- | ----------- | ------ | ------ | ---------- |
| 2025-06-28T13:04:10Z | /health | GET | 200 | true |
| 2025-06-28T13:05:22Z | /api/orders | POST | 201 | true |
## List of related functions [#list-of-related-functions]
* [ipv4\_is\_match](/apl/scalar-functions/ip-functions/ipv4-is-match): Checks whether an IPv4 address belongs to a specified IPv4 subnet. Use it when working with IPv4 addresses.
* [parse\_ipv4](/apl/scalar-functions/ip-functions/parse-ipv4): Parses a string into an IPv4 address. Use it when working with raw IPv4 strings.
## Other query languages [#other-query-languages]
Splunk SPL doesn’t have a dedicated function for matching IPv6 addresses against CIDR blocks. You typically use regular expressions or custom lookups to perform similar checks. In contrast, APL provides a built-in function that directly evaluates IPv6 CIDR membership.
```sql Splunk example
| eval is_in_subnet=if(match(ipv6_field, "^2001:db8::/32"), "true", "false")
```
```kusto APL equivalent
['sample-http-logs']
| extend is_in_subnet = ipv6_is_match('2001:db8:abcd:0012::0', '2001:db8::/32')
```
ANSI SQL doesn’t have a standard function to check if an IPv6 address belongs to a subnet. You often implement this logic with string manipulation or rely on database-specific functions. APL simplifies this with `ipv6_is_match`, which accepts a full IPv6 address and a subnet in CIDR notation.
```sql SQL example
SELECT CASE
WHEN ip_address LIKE '2001:db8:%' THEN TRUE
ELSE FALSE
END AS is_in_subnet
FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend is_in_subnet = ipv6_is_match('2001:db8:abcd:0012::0', '2001:db8::/32')
```
---
# parse_ipv4_mask
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/parse-ipv4-mask
## Introduction [#introduction]
The `parse_ipv4_mask` function in APL converts an IPv4 address and its associated netmask into a signed 64-bit wide, long number representation in big-endian order. Use this function when you need to process or compare IPv4 addresses efficiently as numerical values, such as for IP range filtering, subnet calculations, or network analysis.
This function is particularly useful in scenarios where you need a compact and precise way to represent IP addresses and their masks for further aggregation or filtering.
## Usage [#usage]
### Syntax [#syntax]
```kusto
parse_ipv4_mask(ip, prefix)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ------ | ------------------------------------------------------------------------- |
| `ip` | string | The IPv4 address to convert to a long number. |
| `prefix` | int | An integer from 0 to 32 representing the number of most-significant bits. |
### Returns [#returns]
* A signed, 64-bit long number in big-endian order if the conversion is successful.
* `null` if the conversion is unsuccessful.
### Example [#example]
```kusto
print parse_ipv4_mask("127.0.0.1", 24)
```
## Use case example [#use-case-example]
Use `parse_ipv4_mask` to analyze logs and filter entries based on IP ranges.
**Query**
```kusto
['sample-http-logs']
| extend masked_ip = parse_ipv4_mask('192.168.0.1', 24)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20masked_ip%20%3D%20parse_ipv4_mask\('192.168.0.1'%2C%2024\)%22%7D)
**Output**
| \_time | uri | method | masked\_ip |
| ------------------- | ----------- | ------ | ------------- |
| 2024-11-14T10:00:00 | /index.html | GET | 3,232,235,520 |
## List of related functions [#list-of-related-functions]
* [ipv4\_compare](/apl/scalar-functions/ip-functions/ipv4-compare): Compares two IPv4 addresses lexicographically. Use for sorting or range evaluations.
* [ipv4\_is\_in\_range](/apl/scalar-functions/ip-functions/ipv4-is-in-range): Checks if an IP address is within a specified range.
* [parse\_ipv4](/apl/scalar-functions/ip-functions/parse-ipv4): Converts a dotted-decimal IP address into a numeric representation.
## Other query languages [#other-query-languages]
In Splunk SPL, you use functions like `cidrmatch` for subnet operations. In APL, `parse_ipv4_mask` focuses on converting an IP and mask into a numerical representation for low-level processing.
```sql Splunk example
| eval converted_ip = cidrmatch("192.168.1.0/24", ip)
```
```kusto APL equivalent
print converted_ip = parse_ipv4_mask("192.168.1.0", 24)
```
In ANSI SQL, you typically use custom expressions or stored procedures to perform similar IP address transformations. In APL, `parse_ipv4_mask` offers a built-in, optimized function for this task.
```sql SQL example
SELECT inet_aton('192.168.1.0') & (0xFFFFFFFF << (32 - 24)) AS converted_ip
```
```kusto APL equivalent
print converted_ip = parse_ipv4_mask("192.168.1.0", 24)
```
---
# parse_ipv4
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/parse-ipv4
The `parse_ipv4` function in APL converts an IPv4 address and represents it as a long number. You can use this function to convert an IPv4 address for advanced analysis, filtering, or comparisons. It’s especially useful for tasks like analyzing network traffic logs, identifying trends in IP address usage, or performing security-related queries.
## Usage [#usage]
### Syntax [#syntax]
```kusto
parse_ipv4(ipv4_address)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| -------------- | ------ | --------------------------------------------- |
| `ipv4_address` | string | The IPv4 address to parse into a long number. |
### Returns [#returns]
The function returns the IPv4 address as a long number if the conversion succeeds. If the conversion fails, the function returns `null`.
## Use case example [#use-case-example]
You can use the `parse_ipv4` function to analyze web traffic by representing IP addresses as long numbers.
**Query**
```kusto
['sample-http-logs']
| extend ip_long = parse_ipv4('192.168.1.1')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20ip_octets%20%3D%20parse_ipv4\('192.168.1.1'\)%22%7D)
**Output**
| \_time | uri | method | ip\_long |
| ------------------- | ----------- | ------ | ------------- |
| 2024-11-14T10:00:00 | /index.html | GET | 3,232,235,777 |
## List of related functions [#list-of-related-functions]
* [has\_any\_ipv4](/apl/scalar-functions/ip-functions/has-any-ipv4): Matches any IP address in a string column with a list of IP addresses or ranges.
* [has\_ipv4\_prefix](/apl/scalar-functions/ip-functions/has-ipv4-prefix): Checks if an IPv4 address matches a single prefix.
* [has\_ipv4](/apl/scalar-functions/ip-functions/has-ipv4): Checks if a single IP address is present in a string column.
* [ipv4\_compare](/apl/scalar-functions/ip-functions/ipv4-compare): Compares two IPv4 addresses lexicographically. Use for sorting or range evaluations.
* [ipv4\_is\_in\_range](/apl/scalar-functions/ip-functions/ipv4-is-in-range): Checks if an IP address is within a specified range.
* [ipv4\_is\_private](/apl/scalar-functions/ip-functions/ipv4-is-private): Checks if an IPv4 address is within private IP ranges.
## Other query languages [#other-query-languages]
Splunk doesn’t provide a direct function for converting an IPv4 address into a long number. However, you can achieve similar functionality using custom SPL expressions.
```sql Splunk example
| eval ip_int = tonumber(replace(ip, "\\.", ""))
```
```kusto APL equivalent
['sample-http-logs']
| extend ip_long = parse_ipv4(uri)
```
SQL doesn’t have a built-in function equivalent to `parse_ipv4`, but you can use bitwise operations to achieve a similar result.
```sql SQL example
SELECT
(CAST(SPLIT_PART(ip, '.', 1) AS INT) << 24) +
(CAST(SPLIT_PART(ip, '.', 2) AS INT) << 16) +
(CAST(SPLIT_PART(ip, '.', 3) AS INT) << 8) +
CAST(SPLIT_PART(ip, '.', 4) AS INT) AS ip_int
FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend ip_long = parse_ipv4(uri)
```
---
# genai_concat_contents
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-concat-contents
The `genai_concat_contents` function concatenates all message contents from a GenAI conversation array into a single string. This is useful when you need to combine multiple conversation messages into a single text field for analysis, full-text search, or creating a complete conversation transcript.
You can use this function to create searchable conversation transcripts, prepare data for analysis, or consolidate conversation history for reporting.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_concat_contents(messages, separator)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| --------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| messages | dynamic | Yes | An array of message objects from a GenAI conversation. Each message typically contains a `role` and `content` field. |
| separator | string | No | The string used to separate message contents. Default is a space character (`' '`). |
### Returns [#returns]
Returns a string containing all message contents concatenated together with the specified separator.
## Example [#example]
Create a searchable conversation transcript from a GenAI conversation array.
**Query**
```kusto
['otel-demo-genai']
| extend conversation_text = genai_concat_contents(['attributes.gen_ai.input.messages'], ' / ')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20conversation_text%20%3D%20genai_concat_contents\(%5B%27attributes.gen_ai.input.messages%27%5D%2C%20%27%20%2F%20%27\)%22%7D)
{/* vale off */}
**Output**
| conversation\_text |
| ------------------------------------------------------------------------------------------------------------------------------------------------ |
| "Hello, how are you? / I'm good, thank you! / What's your name? / My name is John. / What's your favorite color? / My favorite color is blue. /" |
{/* vale on */}
This query concatenates the message contents of a GenAI conversation array with a separator of `/`.
## List of related functions [#list-of-related-functions]
* [genai\_extract\_user\_prompt](/apl/scalar-functions/genai-functions/genai-extract-user-prompt): Extracts only the user's prompt instead of all messages. Use this when you need just the user's input.
* [genai\_extract\_assistant\_response](/apl/scalar-functions/genai-functions/genai-extract-assistant-response): Extracts only the assistant's response. Use this when you need just the AI's output.
* [genai\_get\_content\_by\_role](/apl/scalar-functions/genai-functions/genai-get-content-by-role): Gets content filtered by a specific role. Use this when you need messages from a particular role like 'system' or 'tool'.
* [genai\_message\_roles](/apl/scalar-functions/genai-functions/genai-message-roles): Extracts all message roles from a conversation. Use this to understand the conversation structure.
* [strcat\_array](/apl/scalar-functions/array-functions/strcat-array): Concatenates a simple string array. Use this for non-GenAI arrays that don't have the message structure.
## Other query languages [#other-query-languages]
In Splunk SPL, you would typically use multiple `eval` commands with `mvjoin` to concatenate array values, but there’s no direct equivalent for extracting and joining message contents from nested structures.
```sql Splunk example
| eval all_content=mvjoin(messages, " ")
```
```kusto APL equivalent
['ai-logs']
| extend all_content = genai_concat_contents(messages, ' ')
```
In ANSI SQL, you would need to unnest the array and use `STRING_AGG` or similar functions to concatenate values, which is more verbose.
```sql SQL example
SELECT
conversation_id,
STRING_AGG(content, ' ') as all_content
FROM conversations
CROSS JOIN UNNEST(messages) as msg
GROUP BY conversation_id
```
```kusto APL equivalent
['ai-logs']
| extend all_content = genai_concat_contents(messages, ' ')
```
---
# genai_conversation_turns
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-conversation-turns
The `genai_conversation_turns` function counts the number of conversation turns in a GenAI messages array. A turn typically represents a user message followed by an assistant response. This metric helps you understand conversation length and engagement patterns in AI applications.
You can use this function to analyze conversation complexity, monitor user engagement, identify outlier conversations, or track conversation metrics for billing and usage analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_conversation_turns(messages)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| messages | dynamic | Yes | An array of message objects from a GenAI conversation. Each message typically contains a `role` and `content` field. |
### Returns [#returns]
Returns a long integer representing the number of conversation turns. A turn is typically counted as a user-assistant exchange pair.
## Example [#example]
Count the number of conversation turns in a GenAI chat operation.
**Query**
```kusto
['otel-demo-genai']
| extend turns = genai_conversation_turns(['attributes.gen_ai.input.messages'])
| summarize avg_turns = avg(turns), max_turns = max(turns)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20turns%20%3D%20genai_conversation_turns\(%5B%27attributes.gen_ai.input.messages%27%5D\)%20%7C%20summarize%20avg_turns%20%3D%20avg\(turns\)%2C%20max_turns%20%3D%20max\(turns\)%22%7D)
**Output**
| avg\_turns | max\_turns |
| ---------- | ---------- |
| 4.2 | 12 |
This query calculates the average and maximum number of conversation turns, helping you understand conversation complexity and engagement patterns.
## List of related functions [#list-of-related-functions]
* [genai\_message\_roles](/apl/scalar-functions/genai-functions/genai-message-roles): Extracts all message roles to understand conversation structure. Use this when you need to analyze the role distribution in conversations.
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the total number of messages (not turns). Use this when you need the raw message count instead of turn count.
* [genai\_cost](/apl/scalar-functions/genai-functions/genai-cost): Calculates the cost of a conversation. Use this in combination with turn count to understand cost per turn.
* [genai\_estimate\_tokens](/apl/scalar-functions/genai-functions/genai-estimate-tokens): Estimates token usage. Use this with turn count to analyze tokens per turn.
## Other query languages [#other-query-languages]
In Splunk SPL, you would typically use `eval` with `mvcount` to count array elements, but there’s no built-in function specifically for counting conversation turns.
```sql Splunk example
| eval turn_count=mvcount(messages)/2
```
```kusto APL equivalent
['ai-logs']
| extend turn_count = genai_conversation_turns(messages)
```
In ANSI SQL, you would need to unnest the array and count rows, then divide by the number of roles, which is more complex.
```sql SQL example
SELECT
conversation_id,
COUNT(*) / 2 as turn_count
FROM conversations
CROSS JOIN UNNEST(messages)
GROUP BY conversation_id
```
```kusto APL equivalent
['ai-logs']
| extend turn_count = genai_conversation_turns(messages)
```
---
# genai_cost
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-cost
The `genai_cost` function calculates the total cost of a GenAI API call based on the model name, input tokens, and output tokens. This function uses current pricing information for various AI models to provide accurate cost estimates.
You can use this function to track AI spending, analyze cost per conversation, identify expensive queries, or create cost reports and budgets for AI services.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_cost(model, input_tokens, output_tokens)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------------- | ------ | -------- | ---------------------------------------------------------------------------------- |
| model | string | Yes | The name of the AI model (for example, 'gpt-4', 'claude-3-opus', 'gpt-3.5-turbo'). |
| input\_tokens | long | Yes | The number of input tokens (prompt tokens) used in the API call. |
| output\_tokens | long | Yes | The number of output tokens (completion tokens) generated by the API call. |
### Returns [#returns]
Returns a real number representing the total cost in dollars (USD) for the API call based on the model's pricing.
## Example [#example]
Calculate the total cost of a GenAI API call based on model and token usage.
**Query**
```kusto
['otel-demo-genai']
| extend model = ['attributes.gen_ai.response.model']
| extend input_tokens = tolong(['attributes.gen_ai.usage.input_tokens'])
| extend output_tokens = tolong(['attributes.gen_ai.usage.output_tokens'])
| extend api_cost = genai_cost(model, input_tokens, output_tokens)
| summarize total_cost = sum(api_cost), avg_cost = avg(api_cost)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20model%20%3D%20%5B%27attributes.gen_ai.response.model%27%5D%20%7C%20extend%20input_tokens%20%3D%20tolong\(%5B%27attributes.gen_ai.usage.input_tokens%27%5D\)%20%7C%20extend%20output_tokens%20%3D%20tolong\(%5B%27attributes.gen_ai.usage.output_tokens%27%5D\)%20%7C%20extend%20api_cost%20%3D%20genai_cost\(model%2C%20input_tokens%2C%20output_tokens\)%20%7C%20summarize%20total_cost%20%3D%20sum\(api_cost\)%2C%20avg_cost%20%3D%20avg\(api_cost\)%22%7D)
**Output**
| total\_cost | avg\_cost |
| ----------- | --------- |
| 12.45 | 0.0125 |
This query calculates total and average spending on AI API calls, helping you track costs and identify spending trends.
## List of related functions [#list-of-related-functions]
* [genai\_input\_cost](/apl/scalar-functions/genai-functions/genai-input-cost): Calculates only the input token cost. Use this when you need to separate input and output costs.
* [genai\_output\_cost](/apl/scalar-functions/genai-functions/genai-output-cost): Calculates only the output token cost. Use this when analyzing generation costs separately.
* [genai\_get\_pricing](/apl/scalar-functions/genai-functions/genai-get-pricing): Gets the pricing structure for a model. Use this to understand or display pricing information.
* [genai\_estimate\_tokens](/apl/scalar-functions/genai-functions/genai-estimate-tokens): Estimates tokens from text. Use this with genai\_cost to predict costs before making API calls.
## Other query languages [#other-query-languages]
In Splunk SPL, you would need to manually calculate costs using eval and lookup tables.
```sql Splunk example
| lookup model_pricing model OUTPUT input_price output_price
| eval total_cost=(input_tokens * input_price / 1000000) + (output_tokens * output_price / 1000000)
```
```kusto APL equivalent
['ai-logs']
| extend total_cost = genai_cost(model, input_tokens, output_tokens)
```
In ANSI SQL, you would need to join with a pricing table and calculate costs manually.
```sql SQL example
SELECT
l.*,
(l.input_tokens * p.input_price / 1000000) +
(l.output_tokens * p.output_price / 1000000) as total_cost
FROM ai_logs l
JOIN model_pricing p ON l.model = p.model_name
```
```kusto APL equivalent
['ai-logs']
| extend total_cost = genai_cost(model, input_tokens, output_tokens)
```
---
# genai_estimate_tokens
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-estimate-tokens
The `genai_estimate_tokens` function estimates the number of tokens in a text string. This estimation helps you predict API costs, validate input sizes, and monitor token usage before making actual API calls to LLM services.
You can use this function to validate prompt sizes, estimate costs before API calls, monitor content length, or analyze token efficiency across different prompts.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_estimate_tokens(text)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ------ | -------- | --------------------------------------------------------------- |
| text | string | Yes | The text string for which you want to estimate the token count. |
### Returns [#returns]
Returns a long integer representing the estimated number of tokens in the input text.
## Example [#example]
Estimate the number of tokens in a GenAI conversation prompt.
**Query**
```kusto
['otel-demo-genai']
| extend user_prompt = genai_extract_user_prompt(['attributes.gen_ai.input.messages'])
| extend estimated_tokens = genai_estimate_tokens(user_prompt)
| summarize avg_tokens = avg(estimated_tokens), max_tokens = max(estimated_tokens)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20user_prompt%20%3D%20genai_extract_user_prompt\(%5B%27attributes.gen_ai.input.messages%27%5D\)%20%7C%20extend%20estimated_tokens%20%3D%20genai_estimate_tokens\(user_prompt\)%20%7C%20summarize%20avg_tokens%20%3D%20avg\(estimated_tokens\)%2C%20max_tokens%20%3D%20max\(estimated_tokens\)%22%7D)
**Output**
| avg\_tokens | max\_tokens |
| ----------- | ----------- |
| 245 | 1024 |
This query analyzes prompt token usage patterns, helping you predict costs and validate input sizes.
## List of related functions [#list-of-related-functions]
* [genai\_cost](/apl/scalar-functions/genai-functions/genai-cost): Calculates the actual cost based on token usage. Use this in combination with token estimates to predict costs.
* [strlen](/apl/scalar-functions/string-functions#strlen): Returns string length in characters. Use this for a simpler character count without token estimation.
* [string\_size](/apl/scalar-functions/string-functions/string-size): Returns string length in characters. Use this when you need character count instead of token count.
* [genai\_input\_cost](/apl/scalar-functions/genai-functions/genai-input-cost): Calculates input token cost. Combine with token estimation to predict prompt costs.
## Other query languages [#other-query-languages]
In Splunk SPL, there’s no direct equivalent for token estimation. You would typically use character or word count as a rough approximation.
```sql Splunk example
| eval estimated_tokens=len(text)/4
```
```kusto APL equivalent
['ai-logs']
| extend estimated_tokens = genai_estimate_tokens(text)
```
In ANSI SQL, you would need to use character-based estimations, which are less accurate than proper token counting.
```sql SQL example
SELECT
text,
LENGTH(text) / 4 as estimated_tokens
FROM prompts
```
```kusto APL equivalent
['ai-logs']
| extend estimated_tokens = genai_estimate_tokens(text)
```
---
# genai_extract_assistant_response
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-extract-assistant-response
The `genai_extract_assistant_response` function extracts the assistant’s response from a GenAI messages array. It returns the content of the last message with the 'assistant' role, which typically contains the AI model’s generated response to the user.
You can use this function to analyze AI responses, evaluate response quality, perform sentiment analysis on AI outputs, or track specific response patterns for monitoring and debugging.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_extract_assistant_response(messages)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
| messages | dynamic | Yes | An array of message objects from a GenAI conversation. Each message typically contains `role` and `content` fields. |
### Returns [#returns]
Returns a string containing the content of the last assistant message in the conversation, or an empty string if no assistant message is found.
## Example [#example]
Extract the assistant's response from a GenAI conversation.
**Query**
```kusto
['otel-demo-genai']
| extend ai_response = genai_extract_assistant_response(['attributes.gen_ai.input.messages'])
| where strlen(ai_response) > 0
| project _time, ai_response
| limit 3
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20ai_response%20%3D%20genai_extract_assistant_response\(%5B%27attributes.gen_ai.input.messages%27%5D\)%20%7C%20where%20strlen\(ai_response\)%20%3E%200%20%7C%20project%20_time%2C%20ai_response%20%7C%20limit%203%22%7D)
**Output**
| \_time | ai\_response |
| -------------------- | --------------------------------------------------------------------------- |
| 2024-01-15T10:30:00Z | To reset your password, click the 'Forgot Password' link on the login page. |
| 2024-01-15T10:31:00Z | Business hours are Monday to Friday, 9 AM to 5 PM EST. |
This query extracts AI responses, helping you analyze response quality and patterns.
## List of related functions [#list-of-related-functions]
* [genai\_extract\_user\_prompt](/apl/scalar-functions/genai-functions/genai-extract-user-prompt): Extracts the user's prompt. Use this to analyze what users are asking.
* [genai\_extract\_system\_prompt](/apl/scalar-functions/genai-functions/genai-extract-system-prompt): Extracts the system prompt. Use this to understand how the AI is configured.
* [genai\_get\_content\_by\_role](/apl/scalar-functions/genai-functions/genai-get-content-by-role): Gets content by any role. Use this when you need messages from roles other than assistant.
* [genai\_concat\_contents](/apl/scalar-functions/genai-functions/genai-concat-contents): Concatenates all message contents. Use this when you need the full conversation instead of just the assistant's response.
* [genai\_has\_tool\_calls](/apl/scalar-functions/genai-functions/genai-has-tool-calls): Checks for tool calls in messages. Use this to detect when the assistant made function calls.
## Other query languages [#other-query-languages]
In Splunk SPL, you would need to use complex eval statements with mvfilter and mvindex to extract assistant messages.
```sql Splunk example
| eval assistant_msgs=mvfilter(match(role, "assistant"))
| eval response=mvindex(assistant_msgs, -1)
```
```kusto APL equivalent
['ai-logs']
| extend response = genai_extract_assistant_response(messages)
```
In ANSI SQL, you would need to unnest arrays, filter by role, and select the last message, which is more verbose.
```sql SQL example
SELECT
conversation_id,
content as assistant_response
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY conversation_id ORDER BY msg_index DESC) as rn
FROM conversations
CROSS JOIN UNNEST(messages) WITH OFFSET AS msg_index
WHERE role = 'assistant'
) WHERE rn = 1
```
```kusto APL equivalent
['ai-logs']
| extend assistant_response = genai_extract_assistant_response(messages)
```
---
# genai_extract_function_results
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-extract-function-results
The `genai_extract_function_results` function extracts function call results from GenAI messages. When an AI model uses function calling (also known as tool calling), the results are stored in specific message roles. This function retrieves those results for analysis.
You can use this function to monitor function call outcomes, debug tool integrations, analyze API usage patterns, or track the effectiveness of function calls in AI workflows.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_extract_function_results(messages)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
| messages | dynamic | Yes | An array of message objects from a GenAI conversation. Each message typically contains `role` and `content` fields. |
### Returns [#returns]
Returns a dynamic object containing the function call results from the conversation, or null if no function results are found.
## Example [#example]
Extract function call results from a GenAI conversation to analyze tool execution outcomes.
**Query**
```kusto
['otel-demo-genai']
| extend function_results = genai_extract_function_results(['attributes.gen_ai.input.messages'])
| where isnotnull(function_results)
| project _time, function_results
| limit 3
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20function_results%20%3D%20genai_extract_function_results\(%5B%27attributes.gen_ai.input.messages%27%5D\)%20%7C%20where%20isnotnull\(function_results\)%20%7C%20project%20_time%2C%20function_results%20%7C%20limit%203%22%7D)
**Output**
| \_time | function\_results |
| -------------------- | -------------------------------------------------------------------- |
| 2024-01-15T10:30:00Z | `{"status": "success", "data": {"temperature": 72, "humidity": 45}}` |
| 2024-01-15T10:31:00Z | `{"status": "success", "data": {"balance": 1250.50}}` |
This query shows function call results, helping you understand tool execution performance and outcomes.
## List of related functions [#list-of-related-functions]
* [genai\_extract\_tool\_calls](/apl/scalar-functions/genai-functions/genai-extract-tool-calls): Extracts the tool call requests. Use this to see what functions were requested, while genai\_extract\_function\_results shows the results.
* [genai\_has\_tool\_calls](/apl/scalar-functions/genai-functions/genai-has-tool-calls): Checks if messages contain tool calls. Use this to filter conversations that use function calling.
* [genai\_get\_content\_by\_role](/apl/scalar-functions/genai-functions/genai-get-content-by-role): Gets content by specific role. Use this for more granular extraction when you need specific role messages.
* [genai\_extract\_assistant\_response](/apl/scalar-functions/genai-functions/genai-extract-assistant-response): Extracts assistant responses. Use this when you need the AI's text response instead of function results.
## Other query languages [#other-query-languages]
In Splunk SPL, you would need to use complex filtering and extraction logic to isolate function results from nested message structures.
```sql Splunk example
| eval function_results=mvfilter(match(role, "function") OR match(role, "tool"))
| eval results=mvindex(function_results, 0)
```
```kusto APL equivalent
['ai-logs']
| extend results = genai_extract_function_results(messages)
```
In ANSI SQL, you would need to unnest arrays and filter for function or tool roles, which is more complex.
```sql SQL example
SELECT
conversation_id,
JSON_EXTRACT(content, '$.result') as function_results
FROM conversations
CROSS JOIN UNNEST(messages)
WHERE role IN ('function', 'tool')
```
```kusto APL equivalent
['ai-logs']
| extend function_results = genai_extract_function_results(messages)
```
---
# genai_extract_system_prompt
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-extract-system-prompt
The `genai_extract_system_prompt` function extracts the system prompt from a GenAI messages array. The system prompt typically contains instructions that define the AI assistant’s behavior, personality, and capabilities. It’s usually the first message with role 'system'.
You can use this function to audit AI behavior configurations, monitor prompt changes, analyze consistency across conversations, or validate that correct system instructions are being used.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_extract_system_prompt(messages)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
| messages | dynamic | Yes | An array of message objects from a GenAI conversation. Each message typically contains `role` and `content` fields. |
### Returns [#returns]
Returns a string containing the content of the system message, or an empty string if no system message is found.
## Example [#example]
Extract the system prompt from a GenAI conversation to verify AI configuration.
**Query**
```kusto
['otel-demo-genai']
| extend system_prompt = genai_extract_system_prompt(['attributes.gen_ai.input.messages'])
| where isnotempty(system_prompt)
| summarize conversation_count = count() by system_prompt
| top 3 by conversation_count
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20system_prompt%20%3D%20genai_extract_system_prompt\(%5B%27attributes.gen_ai.input.messages%27%5D\)%20%7C%20where%20isnotempty\(system_prompt\)%20%7C%20summarize%20conversation_count%20%3D%20count\(\)%20by%20system_prompt%20%7C%20top%203%20by%20conversation_count%22%7D)
**Output**
| system\_prompt | conversation\_count |
| ---------------------------------------------------------------------------- | ------------------- |
| You are a helpful customer service assistant. | 1250 |
| You are a technical support expert specializing in software troubleshooting. | 845 |
This query helps you understand which system prompts are most commonly used and track prompt variations.
## List of related functions [#list-of-related-functions]
* [genai\_extract\_user\_prompt](/apl/scalar-functions/genai-functions/genai-extract-user-prompt): Extracts the user's prompt. Use this to analyze what users are asking, while system prompts define AI behavior.
* [genai\_extract\_assistant\_response](/apl/scalar-functions/genai-functions/genai-extract-assistant-response): Extracts the assistant's response. Use this to see how the AI responded based on the system prompt.
* [genai\_get\_content\_by\_role](/apl/scalar-functions/genai-functions/genai-get-content-by-role): Gets content by any role. Use this for more flexible extraction when you need other specific roles.
* [genai\_message\_roles](/apl/scalar-functions/genai-functions/genai-message-roles): Lists all message roles. Use this to understand conversation structure and verify system message presence.
## Other query languages [#other-query-languages]
In Splunk SPL, you would need to filter messages by role and extract the first system message.
```sql Splunk example
| eval system_msgs=mvfilter(match(role, "system"))
| eval system_prompt=mvindex(system_msgs, 0)
```
```kusto APL equivalent
['ai-logs']
| extend system_prompt = genai_extract_system_prompt(messages)
```
In ANSI SQL, you would unnest the array and filter for the first system role message.
```sql SQL example
SELECT
conversation_id,
content as system_prompt
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY conversation_id ORDER BY msg_index) as rn
FROM conversations
CROSS JOIN UNNEST(messages) WITH OFFSET AS msg_index
WHERE role = 'system'
) WHERE rn = 1
```
```kusto APL equivalent
['ai-logs']
| extend system_prompt = genai_extract_system_prompt(messages)
```
---
# genai_extract_tool_calls
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-extract-tool-calls
The `genai_extract_tool_calls` function extracts tool call requests from GenAI messages. When an AI model decides to use external tools or functions, it generates tool call messages. This function retrieves those calls so you can analyze what tools are being invoked.
You can use this function to monitor tool usage patterns, debug function calling, track API integrations, or analyze which tools are most frequently requested by your AI applications.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_extract_tool_calls(messages)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
| messages | dynamic | Yes | An array of message objects from a GenAI conversation. Each message typically contains `role` and `content` fields. |
### Returns [#returns]
Returns a dynamic object containing the tool calls from the conversation, or null if no tool calls are found. Tool calls typically include function name, arguments, and call ID.
## Example [#example]
Extract tool calls from a GenAI conversation to analyze which functions are being invoked.
**Query**
```kusto
['otel-demo-genai']
| extend tool_calls = genai_extract_tool_calls(['attributes.gen_ai.input.messages'])
| where isnotnull(tool_calls)
| extend tool_name = tostring(tool_calls[0]['function']['name'])
| summarize call_count = count() by tool_name
| top 5 by call_count
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20tool_calls%20%3D%20genai_extract_tool_calls\(%5B%27attributes.gen_ai.input.messages%27%5D\)%20%7C%20where%20isnotnull\(tool_calls\)%20%7C%20extend%20tool_name%20%3D%20tostring\(tool_calls%5B0%5D%5B%27function%27%5D%5B%27name%27%5D\)%20%7C%20summarize%20call_count%20%3D%20count\(\)%20by%20tool_name%20%7C%20top%205%20by%20call_count%22%7D)
**Output**
| tool\_name | call\_count |
| ---------------- | ----------- |
| get\_weather | 245 |
| search\_database | 189 |
| send\_email | 123 |
This query shows which tools are most frequently called, helping you understand integration usage patterns.
## List of related functions [#list-of-related-functions]
* [genai\_extract\_function\_results](/apl/scalar-functions/genai-functions/genai-extract-function-results): Extracts function call results. Use this to see the outcomes of the tool calls.
* [genai\_has\_tool\_calls](/apl/scalar-functions/genai-functions/genai-has-tool-calls): Checks if messages contain tool calls. Use this to quickly filter conversations with function calling.
* [genai\_extract\_assistant\_response](/apl/scalar-functions/genai-functions/genai-extract-assistant-response): Extracts assistant text responses. Use this when you need the text response instead of tool calls.
* [genai\_get\_content\_by\_role](/apl/scalar-functions/genai-functions/genai-get-content-by-role): Gets content by role. Use this for more granular extraction of specific message types.
## Other query languages [#other-query-languages]
In Splunk SPL, you would need to filter and extract tool call information from nested message structures manually.
```sql Splunk example
| eval tool_calls=mvfilter(match(role, "assistant") AND isnotnull(tool_calls))
| eval tools=spath(tool_calls, "tool_calls")
```
```kusto APL equivalent
['ai-logs']
| extend tools = genai_extract_tool_calls(messages)
```
In ANSI SQL, you would need to unnest arrays and extract JSON fields for tool calls.
```sql SQL example
SELECT
conversation_id,
JSON_EXTRACT(content, '$.tool_calls') as tool_calls
FROM conversations
CROSS JOIN UNNEST(messages)
WHERE JSON_EXTRACT(content, '$.tool_calls') IS NOT NULL
```
```kusto APL equivalent
['ai-logs']
| extend tool_calls = genai_extract_tool_calls(messages)
```
---
# genai_extract_user_prompt
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-extract-user-prompt
The `genai_extract_user_prompt` function extracts the user’s prompt from a GenAI messages array. It returns the content of the last message with the 'user' role, which typically contains the user’s question or request to the AI.
You can use this function to analyze user queries, understand common question patterns, perform sentiment analysis on user inputs, or track user behavior and needs.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_extract_user_prompt(messages)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
| messages | dynamic | Yes | An array of message objects from a GenAI conversation. Each message typically contains `role` and `content` fields. |
### Returns [#returns]
Returns a string containing the content of the last user message in the conversation, or an empty string if no user message is found.
## Example [#example]
Extract the user's prompt from a GenAI conversation to analyze common questions.
**Query**
```kusto
['otel-demo-genai']
| extend user_query = genai_extract_user_prompt(['attributes.gen_ai.input.messages'])
| where isnotempty(user_query)
| summarize query_count = count() by user_query
| top 5 by query_count
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20user_query%20%3D%20genai_extract_user_prompt\(%5B%27attributes.gen_ai.input.messages%27%5D\)%20%7C%20where%20isnotempty\(user_query\)%20%7C%20summarize%20query_count%20%3D%20count\(\)%20by%20user_query%20%7C%20top%205%20by%20query_count%22%7D)
{/* vale off */}
**Output**
| user\_query | query\_count |
| ----------------------------- | ------------ |
| How do I reset my password? | 456 |
| What are your business hours? | 342 |
| How can I track my order? | 298 |
{/* vale on */}
This query identifies the most common user questions, helping you understand user needs and improve responses.
## List of related functions [#list-of-related-functions]
* [genai\_extract\_assistant\_response](/apl/scalar-functions/genai-functions/genai-extract-assistant-response): Extracts the assistant's response. Use this to analyze AI responses along with user prompts.
* [genai\_extract\_system\_prompt](/apl/scalar-functions/genai-functions/genai-extract-system-prompt): Extracts the system prompt. Use this to understand the AI's configuration when analyzing user queries.
* [genai\_get\_content\_by\_role](/apl/scalar-functions/genai-functions/genai-get-content-by-role): Gets content by any role. Use this for more flexible extraction when you need other specific roles.
* [genai\_concat\_contents](/apl/scalar-functions/genai-functions/genai-concat-contents): Concatenates all messages. Use this when you need the full conversation instead of just the user prompt.
* [genai\_estimate\_tokens](/apl/scalar-functions/genai-functions/genai-estimate-tokens): Estimates token count. Combine with user prompt extraction to analyze prompt sizes.
## Other query languages [#other-query-languages]
In Splunk SPL, you would need to filter messages by user role and extract the last one.
```sql Splunk example
| eval user_msgs=mvfilter(match(role, "user"))
| eval user_prompt=mvindex(user_msgs, -1)
```
```kusto APL equivalent
['ai-logs']
| extend user_prompt = genai_extract_user_prompt(messages)
```
In ANSI SQL, you would unnest the array, filter by user role, and select the last message.
```sql SQL example
SELECT
conversation_id,
content as user_prompt
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY conversation_id ORDER BY msg_index DESC) as rn
FROM conversations
CROSS JOIN UNNEST(messages) WITH OFFSET AS msg_index
WHERE role = 'user'
) WHERE rn = 1
```
```kusto APL equivalent
['ai-logs']
| extend user_prompt = genai_extract_user_prompt(messages)
```
---
# genai_get_content_by_index
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-get-content-by-index
The `genai_get_content_by_index` function retrieves the content of a message at a specific position in a GenAI messages array. This allows you to access messages by their position in the conversation sequence.
You can use this function to extract specific messages in a conversation flow, analyze conversation structure, retrieve intermediate messages, or process conversations sequentially.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_get_content_by_index(messages, index)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
| messages | dynamic | Yes | An array of message objects from a GenAI conversation. Each message typically contains `role` and `content` fields. |
| index | long | Yes | The zero-based position of the message to retrieve. Use 0 for the first message, 1 for the second, etc. |
### Returns [#returns]
Returns a string containing the content of the message at the specified index, or an empty string if the index is out of bounds.
## Example [#example]
Get the content of the first user message in a GenAI conversation.
**Query**
```kusto
['otel-demo-genai']
| extend first_message = genai_get_content_by_index(['attributes.gen_ai.input.messages'], 1)
| where isnotempty(first_message)
| project _time, first_message
| limit 3
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20first_message%20%3D%20genai_get_content_by_index\(%5B%27attributes.gen_ai.input.messages%27%5D%2C%201\)%20%7C%20where%20isnotempty\(first_message\)%20%7C%20project%20_time%2C%20first_message%20%7C%20limit%203%22%7D)
{/* vale off */}
**Output**
| \_time | first\_message |
| -------------------- | ------------------------------------ |
| 2024-01-15T10:30:00Z | Hello, I need help with my account. |
| 2024-01-15T10:31:00Z | Can you tell me about your services? |
{/* vale on */}
This query helps you understand how users typically start conversations, which can inform greeting messages and initial prompts.
## List of related functions [#list-of-related-functions]
* [genai\_get\_content\_by\_role](/apl/scalar-functions/genai-functions/genai-get-content-by-role): Gets content filtered by role. Use this when you need messages from a specific role rather than a specific position.
* [genai\_get\_role](/apl/scalar-functions/genai-functions/genai-get-role): Gets the role at a specific index. Combine with this function to understand both role and content at positions.
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns array length. Use this to check message count before accessing by index.
* [genai\_extract\_user\_prompt](/apl/scalar-functions/genai-functions/genai-extract-user-prompt): Extracts the last user prompt. Use this when you need the most recent user message instead of a specific index.
* [genai\_extract\_assistant\_response](/apl/scalar-functions/genai-functions/genai-extract-assistant-response): Extracts the last assistant response. Use this when you need the most recent AI response.
## Other query languages [#other-query-languages]
In Splunk SPL, you would use `mvindex` to access array elements by position.
```sql Splunk example
| eval message_content=mvindex(messages, 2)
```
```kusto APL equivalent
['ai-logs']
| extend message_content = genai_get_content_by_index(messages, 2)
```
In ANSI SQL, you would unnest the array and use `OFFSET` to access specific positions.
```sql SQL example
SELECT
conversation_id,
content as message_content
FROM conversations
CROSS JOIN UNNEST(messages) WITH OFFSET AS pos
WHERE pos = 2
```
```kusto APL equivalent
['ai-logs']
| extend message_content = genai_get_content_by_index(messages, 2)
```
---
# genai_get_content_by_role
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-get-content-by-role
The `genai_get_content_by_role` function retrieves the content of a message with a specific role from a GenAI messages array. It returns the first message matching the specified role (such as 'user', 'assistant', 'system', or 'tool').
You can use this function to extract messages by role, filter conversations by participant type, analyze specific role patterns, or process messages from particular conversation participants.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_get_content_by_role(messages, role)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
| messages | dynamic | Yes | An array of message objects from a GenAI conversation. Each message typically contains `role` and `content` fields. |
| role | string | Yes | The role to filter by. Common values include 'user', 'assistant', 'system', 'tool', or 'function'. |
### Returns [#returns]
Returns a string containing the content of the first message with the specified role, or an empty string if no matching message is found.
## Example [#example]
Extract the content of the system message from a GenAI conversation.
**Query**
```kusto
['otel-demo-genai']
| extend system_content = genai_get_content_by_role(['attributes.gen_ai.input.messages'], 'system')
| where isnotempty(system_content)
| summarize conversation_count = count() by system_content
| top 3 by conversation_count
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20system_content%20%3D%20genai_get_content_by_role\(%5B%27attributes.gen_ai.input.messages%27%5D%2C%20%27system%27\)%20%7C%20where%20isnotempty\(system_content\)%20%7C%20summarize%20conversation_count%20%3D%20count\(\)%20by%20system_content%20%7C%20top%203%20by%20conversation_count%22%7D)
**Output**
| system\_content | conversation\_count |
| ------------------------------------- | ------------------- |
| You are a helpful shopping assistant. | 1250 |
| You are a technical support expert. | 845 |
This query shows the distribution of system prompts being used, helping ensure configuration consistency.
## List of related functions [#list-of-related-functions]
* [genai\_get\_content\_by\_index](/apl/scalar-functions/genai-functions/genai-get-content-by-index): Gets content by position. Use this when you need a message at a specific index rather than by role.
* [genai\_extract\_user\_prompt](/apl/scalar-functions/genai-functions/genai-extract-user-prompt): Extracts the last user prompt. Use this shorthand when you specifically need the most recent user message.
* [genai\_extract\_assistant\_response](/apl/scalar-functions/genai-functions/genai-extract-assistant-response): Extracts the last assistant response. Use this shorthand when you specifically need the most recent AI response.
* [genai\_extract\_system\_prompt](/apl/scalar-functions/genai-functions/genai-extract-system-prompt): Extracts the system prompt. Use this shorthand when you specifically need the system message.
* [genai\_message\_roles](/apl/scalar-functions/genai-functions/genai-message-roles): Lists all roles in the conversation. Use this to understand what roles are present before extracting by role.
## Other query languages [#other-query-languages]
In Splunk SPL, you would use `mvfilter` to filter by role and then extract the content.
```sql Splunk example
| eval filtered_msgs=mvfilter(match(role, "system"))
| eval content=mvindex(filtered_msgs, 0)
```
```kusto APL equivalent
['ai-logs']
| extend content = genai_get_content_by_role(messages, 'system')
```
In ANSI SQL, you would unnest the array, filter by role, and limit to the first result.
```sql SQL example
SELECT
conversation_id,
content
FROM conversations
CROSS JOIN UNNEST(messages)
WHERE role = 'system'
LIMIT 1
```
```kusto APL equivalent
['ai-logs']
| extend content = genai_get_content_by_role(messages, 'system')
```
---
# genai_get_pricing
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-get-pricing
The `genai_get_pricing` function retrieves the pricing information for a specific AI model. It returns a dynamic object containing the input token price and output token price per million tokens, which you can use for cost calculations and budgeting.
You can use this function to display pricing information, create cost calculators, audit pricing data, or understand the cost structure of different AI models.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_get_pricing(model)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------ | -------- | ------------------------------------------------------------------------------------------- |
| model | string | Yes | The name of the AI model (for example, 'gpt-4', 'claude-3-opus-20240229', 'gpt-3.5-turbo'). |
### Returns [#returns]
Returns a dynamic object containing pricing information with the following structure:
```json
{
"input_price_per_million": ,
"output_price_per_million":
}
```
Returns null if the model isn't recognized.
## Example [#example]
Get pricing information for a GenAI model to understand cost structure.
**Query**
```kusto
['otel-demo-genai']
| extend model = ['attributes.gen_ai.request.model']
| extend pricing = genai_get_pricing(model)
| where isnotnull(pricing)
| summarize request_count = count() by model, pricing = tostring(pricing)
| top 3 by request_count
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20model%20%3D%20%5B%27attributes.gen_ai.request.model%27%5D%20%7C%20extend%20pricing%20%3D%20genai_get_pricing\(model\)%20%7C%20where%20isnotnull\(pricing\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20model%2C%20pricing%20%3D%20tostring\(pricing\)%20%7C%20top%203%20by%20request_count%22%7D)
**Output**
| model | pricing | request\_count |
| ------------- | --------------------------------------------------------------------- | -------------- |
| gpt-4 | `{"input_price_per_million": 30.0, "output_price_per_million": 60.0}` | 450 |
| gpt-3.5-turbo | `{"input_price_per_million": 0.5, "output_price_per_million": 1.5}` | 1250 |
This query shows which models are being used and their associated pricing, helping teams make informed decisions.
## List of related functions [#list-of-related-functions]
* [genai\_cost](/apl/scalar-functions/genai-functions/genai-cost): Calculates total cost for a conversation. Use this for actual cost calculation after retrieving pricing information.
* [genai\_input\_cost](/apl/scalar-functions/genai-functions/genai-input-cost): Calculates input token cost. This function uses genai\_get\_pricing internally to determine input costs.
* [genai\_output\_cost](/apl/scalar-functions/genai-functions/genai-output-cost): Calculates output token cost. This function uses genai\_get\_pricing internally to determine output costs.
* [genai\_estimate\_tokens](/apl/scalar-functions/genai-functions/genai-estimate-tokens): Estimates token count. Combine with pricing information to predict costs before making API calls.
## Other query languages [#other-query-languages]
In Splunk SPL, you would typically use a lookup table to retrieve pricing information by model name.
```sql Splunk example
| lookup model_pricing model OUTPUT input_price output_price
```
```kusto APL equivalent
['ai-logs']
| extend pricing = genai_get_pricing(model)
```
In ANSI SQL, you would join with a pricing table to get model costs.
```sql SQL example
SELECT
l.*,
p.input_price,
p.output_price
FROM ai_logs l
JOIN model_pricing p ON l.model = p.model_name
```
```kusto APL equivalent
['ai-logs']
| extend pricing = genai_get_pricing(model)
```
---
# genai_get_role
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-get-role
The `genai_get_role` function retrieves the role of a message at a specific position in a GenAI messages array. This allows you to understand who sent a particular message in the conversation (user, assistant, system, tool, etc.).
You can use this function to validate conversation structure, analyze message patterns, verify conversation flow, or process conversations based on role sequences.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_get_role(messages, index)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| messages | dynamic | Yes | An array of message objects from a GenAI conversation. Each message typically contains `role` and `content` fields. |
| index | long | Yes | The zero-based position of the message whose role you want to retrieve. Use 0 for the first message, 1 for the second, etc. |
### Returns [#returns]
Returns a string containing the role of the message at the specified index (such as 'user', 'assistant', 'system', 'tool', 'function'), or an empty string if the index is out of bounds.
## Example [#example]
Get the role of the first message in a GenAI conversation.
**Query**
```kusto
['otel-demo-genai']
| extend first_role = genai_get_role(['attributes.gen_ai.input.messages'], 0)
| summarize conversations_with_system = countif(first_role == 'system'), total_conversations = count()
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20first_role%20%3D%20genai_get_role\(%5B%27attributes.gen_ai.input.messages%27%5D%2C%200\)%20%7C%20summarize%20conversations_with_system%20%3D%20countif\(first_role%20%3D%3D%20%27system%27\)%2C%20total_conversations%20%3D%20count\(\)%22%7D)
**Output**
| conversations\_with\_system | total\_conversations |
| --------------------------- | -------------------- |
| 1250 | 1450 |
This query verifies that most conversations are properly initialized with system prompts.
## List of related functions [#list-of-related-functions]
* [genai\_get\_content\_by\_index](/apl/scalar-functions/genai-functions/genai-get-content-by-index): Gets content at a specific index. Combine with genai\_get\_role to understand both role and content at positions.
* [genai\_message\_roles](/apl/scalar-functions/genai-functions/genai-message-roles): Lists all roles in the conversation. Use this to get a complete picture of all roles rather than checking individual positions.
* [genai\_get\_content\_by\_role](/apl/scalar-functions/genai-functions/genai-get-content-by-role): Gets content filtered by role. Use this when you need content from a specific role type.
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the total number of messages. Use this to validate index bounds before accessing positions.
## Other query languages [#other-query-languages]
In Splunk SPL, you would use `mvindex` to access the role field at a specific position.
```sql Splunk example
| eval message_role=mvindex(role, 2)
```
```kusto APL equivalent
['ai-logs']
| extend message_role = genai_get_role(messages, 2)
```
In ANSI SQL, you would unnest the array and access the role at a specific offset.
```sql SQL example
SELECT
conversation_id,
role as message_role
FROM conversations
CROSS JOIN UNNEST(messages) WITH OFFSET AS pos
WHERE pos = 2
```
```kusto APL equivalent
['ai-logs']
| extend message_role = genai_get_role(messages, 2)
```
---
# genai_has_tool_calls
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-has-tool-calls
The `genai_has_tool_calls` function checks whether a GenAI messages array contains any tool calls or function calls. It returns a boolean value indicating if the AI model requested to use external tools or functions during the conversation.
You can use this function to filter conversations that use function calling, monitor tool usage patterns, identify integration opportunities, or track feature adoption of function calling capabilities.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_has_tool_calls(messages)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
| messages | dynamic | Yes | An array of message objects from a GenAI conversation. Each message typically contains `role` and `content` fields. |
### Returns [#returns]
Returns a boolean value: `true` if the messages contain tool calls, `false` otherwise.
## Example [#example]
Check if a GenAI conversation contains any tool calls or function calls.
**Query**
```kusto
['otel-demo-genai']
| extend has_tools = genai_has_tool_calls(['attributes.gen_ai.input.messages'])
| summarize
conversations_with_tools = countif(has_tools),
total_conversations = count(),
adoption_rate = round(100.0 * countif(has_tools) / count(), 2)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20has_tools%20%3D%20genai_has_tool_calls\(%5B%27attributes.gen_ai.input.messages%27%5D\)%20%7C%20summarize%20conversations_with_tools%20%3D%20countif\(has_tools\)%2C%20total_conversations%20%3D%20count\(\)%2C%20adoption_rate%20%3D%20round\(100.0%20*%20countif\(has_tools\)%20%2F%20count\(\)%2C%202\)%22%7D)
**Output**
| conversations\_with\_tools | total\_conversations | adoption\_rate |
| -------------------------- | -------------------- | -------------- |
| 345 | 1450 | 23.79 |
This query tracks function calling adoption, helping you understand feature usage trends.
## List of related functions [#list-of-related-functions]
* [genai\_extract\_tool\_calls](/apl/scalar-functions/genai-functions/genai-extract-tool-calls): Extracts the actual tool calls. Use this after confirming tool calls exist to analyze what tools are being called.
* [genai\_extract\_function\_results](/apl/scalar-functions/genai-functions/genai-extract-function-results): Extracts function results. Use this to analyze the outcomes of tool calls.
* [genai\_message\_roles](/apl/scalar-functions/genai-functions/genai-message-roles): Lists all message roles. Use this to understand conversation structure when tool calls are present.
* [genai\_conversation\_turns](/apl/scalar-functions/genai-functions/genai-conversation-turns): Counts conversation turns. Analyze this alongside tool usage to understand complexity.
## Other query languages [#other-query-languages]
In Splunk SPL, you would check if tool-related fields exist in the messages.
```sql Splunk example
| eval has_tools=if(isnotnull(tool_calls), "true", "false")
```
```kusto APL equivalent
['ai-logs']
| extend has_tools = genai_has_tool_calls(messages)
```
In ANSI SQL, you would check for existence of tool calls in the messages array.
```sql SQL example
SELECT
conversation_id,
EXISTS(
SELECT 1 FROM UNNEST(messages)
WHERE JSON_EXTRACT(content, '$.tool_calls') IS NOT NULL
) as has_tools
FROM conversations
```
```kusto APL equivalent
['ai-logs']
| extend has_tools = genai_has_tool_calls(messages)
```
---
# genai_input_cost
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-input-cost
The `genai_input_cost` function calculates the cost of input tokens (prompt tokens) for a GenAI API call based on the model name and number of input tokens. This helps you understand and track the cost of prompts separately from responses.
You can use this function to analyze prompt costs, optimize prompt engineering for cost efficiency, track input spending separately, or create detailed cost breakdowns.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_input_cost(model, input_tokens)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------------- | ------ | -------- | ---------------------------------------------------------------------------------- |
| model | string | Yes | The name of the AI model (for example, 'gpt-4', 'claude-3-opus', 'gpt-3.5-turbo'). |
| input\_tokens | long | Yes | The number of input tokens (prompt tokens) used in the API call. |
### Returns [#returns]
Returns a real number representing the cost in dollars (USD) for the input tokens based on the model's pricing.
## Example [#example]
Calculate the cost of input tokens for a GenAI chat operation.
**Query**
```kusto
['otel-demo-genai']
| extend model = ['attributes.gen_ai.request.model']
| extend input_tokens = tolong(['attributes.gen_ai.usage.input_tokens'])
| extend input_cost = genai_input_cost(model, input_tokens)
| summarize total_input_cost = sum(input_cost), avg_input_cost = avg(input_cost)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20model%20%3D%20%5B%27attributes.gen_ai.request.model%27%5D%20%7C%20extend%20input_tokens%20%3D%20tolong\(%5B%27attributes.gen_ai.usage.input_tokens%27%5D\)%20%7C%20extend%20input_cost%20%3D%20genai_input_cost\(model%2C%20input_tokens\)%20%7C%20summarize%20total_input_cost%20%3D%20sum\(input_cost\)%2C%20avg_input_cost%20%3D%20avg\(input_cost\)%22%7D)
**Output**
| total\_input\_cost | avg\_input\_cost |
| ------------------ | ---------------- |
| 45.67 | 0.0187 |
This query calculates the total and average cost of input tokens, helping you understand prompt spending patterns.
## List of related functions [#list-of-related-functions]
* [genai\_output\_cost](/apl/scalar-functions/genai-functions/genai-output-cost): Calculates output token cost. Use this alongside input costs to understand the full cost breakdown.
* [genai\_cost](/apl/scalar-functions/genai-functions/genai-cost): Calculates total cost (input + output). Use this when you need combined costs.
* [genai\_get\_pricing](/apl/scalar-functions/genai-functions/genai-get-pricing): Gets pricing information. Use this to understand the pricing structure behind cost calculations.
* [genai\_estimate\_tokens](/apl/scalar-functions/genai-functions/genai-estimate-tokens): Estimates token count from text. Combine with input cost to predict prompt costs before API calls.
## Other query languages [#other-query-languages]
In Splunk SPL, you would need to lookup pricing and calculate costs manually.
```sql Splunk example
| lookup model_pricing model OUTPUT input_price
| eval input_cost=(input_tokens * input_price / 1000000)
```
```kusto APL equivalent
['ai-logs']
| extend input_cost = genai_input_cost(model, input_tokens)
```
In ANSI SQL, you would join with a pricing table and calculate input costs.
```sql SQL example
SELECT
l.*,
(l.input_tokens * p.input_price / 1000000) as input_cost
FROM ai_logs l
JOIN model_pricing p ON l.model = p.model_name
```
```kusto APL equivalent
['ai-logs']
| extend input_cost = genai_input_cost(model, input_tokens)
```
---
# genai_is_truncated
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-is-truncated
The `genai_is_truncated` function checks whether an AI model response was truncated due to reaching token limits or other constraints. It analyzes the finish reason returned by the API to determine if the response was cut short.
You can use this function to identify incomplete responses, monitor quality issues, detect token limit problems, or track when conversations need continuation.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_is_truncated(messages, finish_reason)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
| messages | dynamic | Yes | An array of message objects from a GenAI conversation. Each message typically contains `role` and `content` fields. |
| finish\_reason | string | Yes | The finish reason returned by the AI API (such as 'stop', 'length', 'content\_filter', 'tool\_calls'). |
### Returns [#returns]
Returns a boolean value: `true` if the response was truncated (typically when finish\_reason is 'length'), `false` otherwise.
## Example [#example]
Check if a GenAI response was truncated due to token limits.
**Query**
```kusto
['otel-demo-genai']
| extend finish_reason = ['attributes.gen_ai.response.finish_reasons']
| extend is_truncated = genai_is_truncated(['attributes.gen_ai.input.messages'], finish_reason)
| summarize
truncated_count = countif(is_truncated),
total_count = count(),
truncation_rate = round(100.0 * countif(is_truncated) / count(), 2)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20finish_reason%20%3D%20%5B%27attributes.gen_ai.response.finish_reasons%27%5D%20%7C%20extend%20is_truncated%20%3D%20genai_is_truncated\(%5B%27attributes.gen_ai.input.messages%27%5D%2C%20finish_reason\)%20%7C%20summarize%20truncated_count%20%3D%20countif\(is_truncated\)%2C%20total_count%20%3D%20count\(\)%2C%20truncation_rate%20%3D%20round\(100.0%20*%20countif\(is_truncated\)%20%2F%20count\(\)%2C%202\)%22%7D)
**Output**
| truncated\_count | total\_count | truncation\_rate |
| ---------------- | ------------ | ---------------- |
| 45 | 1450 | 3.10 |
This query tracks the rate of truncated responses, helping you identify when token limits are causing quality issues.
## List of related functions [#list-of-related-functions]
* [genai\_estimate\_tokens](/apl/scalar-functions/genai-functions/genai-estimate-tokens): Estimates token count. Use this to predict if responses might be truncated before making API calls.
* [genai\_conversation\_turns](/apl/scalar-functions/genai-functions/genai-conversation-turns): Counts conversation turns. Analyze this alongside truncation to understand context length issues.
* [genai\_extract\_assistant\_response](/apl/scalar-functions/genai-functions/genai-extract-assistant-response): Extracts assistant responses. Use this to examine truncated responses.
* [strlen](/apl/scalar-functions/string-functions#strlen): Returns string length. Use this to analyze the length of truncated responses.
## Other query languages [#other-query-languages]
In Splunk SPL, you would check the finish\_reason field manually.
```sql Splunk example
| eval is_truncated=if(finish_reason="length", "true", "false")
```
```kusto APL equivalent
['ai-logs']
| extend is_truncated = genai_is_truncated(messages, finish_reason)
```
In ANSI SQL, you would check the finish\_reason field value.
```sql SQL example
SELECT
conversation_id,
CASE WHEN finish_reason = 'length' THEN true ELSE false END as is_truncated
FROM ai_logs
```
```kusto APL equivalent
['ai-logs']
| extend is_truncated = genai_is_truncated(messages, finish_reason)
```
---
# genai_message_roles
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-message-roles
The `genai_message_roles` function extracts an array of all message roles from a GenAI conversation. This provides a sequence view of conversation participants, showing the order and types of messages (user, assistant, system, tool, etc.).
You can use this function to analyze conversation patterns, validate conversation structure, detect role sequences, or understand conversation flow and complexity.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_message_roles(messages)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
| messages | dynamic | Yes | An array of message objects from a GenAI conversation. Each message typically contains `role` and `content` fields. |
### Returns [#returns]
Returns a dynamic array containing all the roles in the conversation in their original order (for example, `['system', 'user', 'assistant', 'user', 'assistant']`).
## Example [#example]
Extract all message roles from a GenAI conversation to understand the conversation structure.
**Query**
```kusto
['otel-demo-genai']
| extend roles = genai_message_roles(['attributes.gen_ai.input.messages'])
| extend role_sequence = tostring(roles)
| summarize conversation_count = count() by role_sequence
| top 5 by conversation_count
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20roles%20%3D%20genai_message_roles\(%5B%27attributes.gen_ai.input.messages%27%5D\)%20%7C%20extend%20role_sequence%20%3D%20tostring\(roles\)%20%7C%20summarize%20conversation_count%20%3D%20count\(\)%20by%20role_sequence%20%7C%20top%205%20by%20conversation_count%22%7D)
**Output**
| role\_sequence | conversation\_count |
| -------------------------------------------------- | ------------------- |
| `["system","user","assistant"]` | 850 |
| `["system","user","assistant","user","assistant"]` | 345 |
| `["user","assistant"]` | 189 |
This query identifies the most common conversation patterns, helping you understand typical user interaction flows.
## List of related functions [#list-of-related-functions]
* [genai\_get\_role](/apl/scalar-functions/genai-functions/genai-get-role): Gets the role at a specific index. Use this when you need a specific role rather than the full sequence.
* [genai\_conversation\_turns](/apl/scalar-functions/genai-functions/genai-conversation-turns): Counts conversation turns. Use this for a numerical metric of conversation length.
* [genai\_get\_content\_by\_role](/apl/scalar-functions/genai-functions/genai-get-content-by-role): Gets content for a specific role. Use this after identifying roles of interest.
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the number of messages. Apply this to the roles array to count messages.
* [array\_index\_of](/apl/scalar-functions/array-functions/array-index-of): Finds the position of a role. Use this to detect if specific roles exist in the conversation.
## Other query languages [#other-query-languages]
In Splunk SPL, you would extract the role field from all messages in an array.
```sql Splunk example
| eval roles=mvindex(role, 0, mvcount(role))
```
```kusto APL equivalent
['ai-logs']
| extend roles = genai_message_roles(messages)
```
In ANSI SQL, you would unnest the array and collect roles into an array.
```sql SQL example
SELECT
conversation_id,
ARRAY_AGG(role ORDER BY msg_index) as roles
FROM conversations
CROSS JOIN UNNEST(messages) WITH OFFSET AS msg_index
GROUP BY conversation_id
```
```kusto APL equivalent
['ai-logs']
| extend roles = genai_message_roles(messages)
```
---
# genai_output_cost
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-output-cost
The `genai_output_cost` function calculates the cost of output tokens (completion tokens) for a GenAI API call based on the model name and number of output tokens. This helps you understand and track the cost of generated responses separately from prompts.
You can use this function to analyze generation costs, optimize response length for cost efficiency, track output spending separately, or create detailed cost breakdowns.
## Usage [#usage]
### Syntax [#syntax]
```kusto
genai_output_cost(model, output_tokens)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------------- | ------ | -------- | ---------------------------------------------------------------------------------- |
| model | string | Yes | The name of the AI model (for example, 'gpt-4', 'claude-3-opus', 'gpt-3.5-turbo'). |
| output\_tokens | long | Yes | The number of output tokens (completion tokens) generated by the API call. |
### Returns [#returns]
Returns a real number representing the cost in dollars (USD) for the output tokens based on the model's pricing.
## Example [#example]
Calculate the cost of output tokens for a GenAI chat operation.
**Query**
```kusto
['otel-demo-genai']
| extend model = ['attributes.gen_ai.response.model']
| extend output_tokens = tolong(['attributes.gen_ai.usage.output_tokens'])
| extend output_cost = genai_output_cost(model, output_tokens)
| summarize total_output_cost = sum(output_cost), avg_output_cost = avg(output_cost)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-genai%27%5D%20%7C%20extend%20model%20%3D%20%5B%27attributes.gen_ai.response.model%27%5D%20%7C%20extend%20output_tokens%20%3D%20tolong\(%5B%27attributes.gen_ai.usage.output_tokens%27%5D\)%20%7C%20extend%20output_cost%20%3D%20genai_output_cost\(model%2C%20output_tokens\)%20%7C%20summarize%20total_output_cost%20%3D%20sum\(output_cost\)%2C%20avg_output_cost%20%3D%20avg\(output_cost\)%22%7D)
**Output**
| total\_output\_cost | avg\_output\_cost |
| ------------------- | ----------------- |
| 78.34 | 0.0321 |
This query calculates the total and average cost of output tokens, helping you understand generation spending patterns.
## List of related functions [#list-of-related-functions]
* [genai\_input\_cost](/apl/scalar-functions/genai-functions/genai-input-cost): Calculates input token cost. Use this alongside output costs to understand the full cost breakdown.
* [genai\_cost](/apl/scalar-functions/genai-functions/genai-cost): Calculates total cost (input + output). Use this when you need combined costs.
* [genai\_get\_pricing](/apl/scalar-functions/genai-functions/genai-get-pricing): Gets pricing information. Use this to understand the pricing structure behind cost calculations.
* [genai\_extract\_assistant\_response](/apl/scalar-functions/genai-functions/genai-extract-assistant-response): Extracts the response text. Combine with output costs to analyze cost per response.
* [genai\_is\_truncated](/apl/scalar-functions/genai-functions/genai-is-truncated): Checks if responses were truncated. Use this to understand if token limits affected output costs.
## Other query languages [#other-query-languages]
In Splunk SPL, you would need to lookup pricing and calculate costs manually.
```sql Splunk example
| lookup model_pricing model OUTPUT output_price
| eval output_cost=(output_tokens * output_price / 1000000)
```
```kusto APL equivalent
['ai-logs']
| extend output_cost = genai_output_cost(model, output_tokens)
```
In ANSI SQL, you would join with a pricing table and calculate output costs.
```sql SQL example
SELECT
l.*,
(l.output_tokens * p.output_price / 1000000) as output_cost
FROM ai_logs l
JOIN model_pricing p ON l.model = p.model_name
```
```kusto APL equivalent
['ai-logs']
| extend output_cost = genai_output_cost(model, output_tokens)
```
---
# column_ifexists
Source: https://axiom.co/docs/apl/scalar-functions/metadata-functions/column-ifexists
Use `column_ifexists()` to make your queries resilient to schema changes. The function checks if a field with a given name exists in the dataset. If it does, the function returns it. If not, it returns a fallback field or expression that you provide.
This is especially useful when working with datasets that evolve over time or come from multiple sources with different schemas. Instead of failing when a field is missing, your query continues running by using a default. Use this function to safely handle queries where the presence of a field isn’t guaranteed.
## Usage [#usage]
### Syntax [#syntax]
```kusto
column_ifexists(FieldName, DefaultValue)
```
### Parameters [#parameters]
* `FieldName`: The name of the field to return as a string.
* `DefaultValue`: The fallback value to return if `FieldName` doesn’t exist. This can be another field or a literal.
### Returns [#returns]
Returns the field specified by `FieldName` if it exists in the table schema. Otherwise, returns the result of `DefaultValue`.
## Use case examples [#use-case-examples]
You want to examine HTTP logs, and your schema might have a `geo.region` field in some environments and not in others. You fall back to `geo.country` when `geo.region` is missing.
**Query**
```kusto
['sample-http-logs']
| project _time, location = column_ifexists('geo.region', ['geo.country'])
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20project%20_time%2C%20location%20%3D%20column_ifexists\('geo.region'%2C%20%5B'geo.country'%5D\)%22%7D)
**Output**
| \_time | location |
| -------------------- | -------------- |
| 2025-04-28T12:04:10Z | United States |
| 2025-04-28T12:04:12Z | Canada |
| 2025-04-28T12:04:15Z | United Kingdom |
The query returns `geo.region` if it exists; otherwise, it falls back to `geo.country`.
You analyze OpenTelemetry traces and you’re not sure if your data contains `status_code` and `status` fields. You fall back to `100` when it’s missing.
**Query**
```kusto
['otel-demo-traces']
| extend status_code_field = column_ifexists('status_code', '100')
| extend status_field = column_ifexists('status', 100)
| project _time, trace_id, span_id, status_code_field, status_field
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20status_code_field%20%3D%20column_ifexists\('status_code'%2C%20'100'\)%20%7C%20extend%20status_field%20%3D%20column_ifexists\('status'%2C%20100\)%20%7C%20project%20_time%2C%20trace_id%2C%20span_id%2C%20status_code_field%2C%20status_field%22%7D)
**Output**
| \_time | trace\_id | span\_id | status\_code\_field | status\_field |
| -------------------- | --------- | -------- | ------------------- | ------------- |
| 2025-04-28T10:30:12Z | abc123 | span567 | nil | 100 |
| 2025-04-28T10:30:15Z | def456 | span890 | 200 | 100 |
The query returns the `status_code` field if it exists. Otherwise, it falls back to `100`.
You inspect logs for suspicious activity. In some datasets, a `threat_level` field exists, but not in all. You use the `status` field as a fallback.
**Query**
```kusto
['sample-http-logs']
| project _time, id, threat = column_ifexists('threat_level', status)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20project%20_time%2C%20id%2C%20threat%20%3D%20column_ifexists\('threat_level'%2C%20status\)%22%7D)
**Output**
| \_time | id | threat |
| -------------------- | ---- | ------ |
| 2025-04-28T13:22:11Z | u123 | 200 |
| 2025-04-28T13:22:13Z | u456 | 403 |
The function avoids breaking the query if `threat_level` doesn’t exist by defaulting to `status`.
## List of related functions [#list-of-related-functions]
* [coalesce](/apl/scalar-functions/string-functions#coalesce): Returns the first non-null value from a list of expressions. Use when you want to handle null values, not missing fields.
* [iff](/apl/scalar-functions/conditional-function#iff): Performs conditional logic based on a boolean expression. Use when you want explicit control over evaluation.
* [isnull](/apl/scalar-functions/string-functions#isnull): Checks if a value is null. Useful when combined with other functions for fine-grained control.
* [case](/apl/scalar-functions/conditional-function#case): Allows multiple conditional branches. Use when fallback logic depends on multiple conditions.
* [project](/apl/tabular-operators/project-operator): Selects and transforms fields. Use with `column_ifexists()` to build resilient field projections.
## Other query languages [#other-query-languages]
In Splunk, field selection is strict—missing fields typically return `null` in results, but conditional logic for fallback fields requires using `eval` or `coalesce`. In APL, `column_ifexists()` directly substitutes the fallback field at query-time based on schema.
```sql Splunk example
... | eval field=if(isnull(Capital), State, Capital)
```
```kusto APL equivalent
StormEvents | project column_ifexists('Capital', State)
```
In SQL, you need to check for the existence of a field using system views or error handling. `column_ifexists()` in APL simplifies this by allowing fallback behavior inline without needing procedural code.
```sql SQL example
SELECT CASE
WHEN EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'StormEvents' AND COLUMN_NAME = 'Capital')
THEN Capital ELSE State END AS Result
FROM StormEvents
```
```kusto APL equivalent
StormEvents | project column_ifexists('Capital', State)
```
---
# cursor_current
Source: https://axiom.co/docs/apl/scalar-functions/metadata-functions/cursor-current
Use the `cursor_current` function in APL to retrieve a cursor string that represents the current point in the query execution. A cursor is a unique identifier that marks a specific position in your data stream, allowing you to resume queries from that exact point in subsequent executions.
You use `cursor_current` when implementing incremental data processing, change data capture (CDC) patterns, or any scenario where you need to track the last processed position in your data. This is particularly useful for building efficient data synchronization pipelines, continuous monitoring systems, and incremental analytics workflows.
## Usage [#usage]
### Syntax [#syntax]
```kusto
cursor_current()
```
### Parameters [#parameters]
This function takes no parameters.
### Returns [#returns]
A `string` representing a cursor that marks the current position in the query execution. This cursor can be stored and used in subsequent queries to resume processing from the same point.
## Use case examples [#use-case-examples]
Use `cursor_current` to track the last processed position when implementing incremental log processing pipelines.
**Query**
```kusto
['sample-http-logs']
| extend processing_cursor = cursor_current()
| where status != '200'
| summarize error_count = count(), last_cursor = make_list(processing_cursor)[0] by bin(_time, 5m)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20processing_cursor%20%3D%20cursor_current\(\)%20%7C%20where%20status%20!%3D%20'200'%20%7C%20summarize%20error_count%20%3D%20count\(\)%2C%20last_cursor%20%3D%20make_list\(processing_cursor\)\[0]%20by%20bin\(_time%2C%205m\)%20%7C%20take%205%22%7D)
**Output**
| error\_count | last\_cursor |
| ------------ | --------------------------------------- |
| 343,769 | 0ddnv2035n4lc-082f23975a003f6b-00006d10 |
This query captures cursor positions alongside error counts, allowing you to resume processing from the last successful position in case of failures or for incremental updates.
Use `cursor_current` to implement incremental trace processing for building derived metrics or alerts based on span data.
**Query**
```kusto
['otel-demo-traces']
| extend trace_cursor = cursor_current()
| where kind == 'server'
| summarize span_count = count(), latest_cursor = make_list(trace_cursor)[0] by ['service.name'], bin(_time, 5m)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20trace_cursor%20%3D%20cursor_current\(\)%20%7C%20where%20kind%20%3D%3D%20'server'%20%7C%20summarize%20span_count%20%3D%20count\(\)%2C%20latest_cursor%20%3D%20make_list\(trace_cursor\)\[0]%20by%20%5B'service.name'%5D%2C%20bin\(_time%2C%205m\)%20%7C%20take%205%22%7D)
**Output**
| \['service.name'] | span\_count | latest\_cursor |
| ----------------- | ----------- | ----------------- |
| frontend | 234 | cur\_xyz456abc789 |
| cart | 187 | cur\_mno123pqr456 |
This query tracks the processing position for each service, enabling resumable trace analysis and incremental metric computation.
## List of related functions [#list-of-related-functions]
* [ingestion\_time](/apl/scalar-functions/metadata-functions/ingestion-time): Use `ingestion_time` to get the time when data was ingested. Use `cursor_current` for resumable query execution tracking.
* [now](/apl/scalar-functions/datetime-functions#now): Use `now` to get the current query execution time. Use `cursor_current` for position tracking in data streams.
* [bin](/apl/scalar-functions/rounding-functions#bin): Use `bin` to group data into time buckets. Use `cursor_current` alongside `bin` for checkpointed time-series processing.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use time-based bookmarks or the `_indextime` field to track processing positions. APL's `cursor_current` provides a more robust cursor mechanism that works across query executions and handles distributed data more reliably.
```sql Splunk example
| stats max(_indextime) as last_processed
```
```kusto APL equivalent
['sample-http-logs']
| extend cursor = cursor_current()
| summarize max(_time), last_cursor = make_list(cursor)[0]
```
In ANSI SQL, you typically use `MAX(timestamp)` or row IDs to track the last processed record. APL's `cursor_current` provides a system-level cursor that captures the exact query execution position, which is more reliable for distributed systems.
```sql SQL example
SELECT MAX(timestamp) as last_processed_time
FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend cursor = cursor_current()
| summarize max(_time), processing_cursor = make_list(cursor)[0]
```
---
# ingestion_time
Source: https://axiom.co/docs/apl/scalar-functions/metadata-functions/ingestion-time
Use the `ingestion_time` function to retrieve the timestamp of when each record was ingested into Axiom. This function helps you distinguish between the original event time (as captured in the `_time` field) and the time the data was actually received by Axiom.
You can use `ingestion_time` to:
* Detect delays or lags in data ingestion.
* Filter events based on their ingestion window.
* Audit data pipelines by comparing event time with ingestion time.
This function is especially useful when working with streaming or event-based data sources where ingestion delays are common and might affect alerting, dashboarding, or correlation accuracy.
## Usage [#usage]
### Syntax [#syntax]
```kusto
ingestion_time()
```
### Parameters [#parameters]
This function doesn’t take any parameters.
### Returns [#returns]
A `datetime` value that represents when each record was ingested into Axiom.
## Use case examples [#use-case-examples]
Use `ingestion_time` to identify delays between when an HTTP request occurred and when it was ingested into Axiom.
**Query**
```kusto
['sample-http-logs']
| extend ingest_time = ingestion_time()
| extend delay = datetime_diff('second', ingest_time, _time)
| where delay > 1
| project _time, ingest_time, delay, method, uri, status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20ingest_time%20%3D%20ingestion_time\(\)%20%7C%20extend%20delay%20%3D%20datetime_diff\('second'%2C%20ingest_time%2C%20_time\)%20%7C%20where%20delay%20%3E%201%20%7C%20project%20_time%2C%20ingest_time%2C%20delay%2C%20method%2C%20uri%2C%20status%22%7D)
**Output**
| \_time | ingest\_time | delay | method | uri | status |
| -------------------- | -------------------- | ----- | ------ | ------------- | ------ |
| 2025-06-10T12:00:00Z | 2025-06-10T12:01:30Z | 90 | GET | /api/products | 200 |
| 2025-06-10T12:05:00Z | 2025-06-10T12:06:10Z | 70 | POST | /api/cart/add | 201 |
This query calculates the difference between the ingestion time and event time, highlighting entries with more than 60 seconds delay.
Use `ingestion_time` to monitor ingestion lags for spans generated by services, helping identify pipeline slowdowns or delivery issues.
**Query**
```kusto
['otel-demo-traces']
| extend ingest_time = ingestion_time()
| extend delay = datetime_diff('second', ingest_time, _time)
| summarize avg_delay = avg(delay) by ['service.name'], kind
| order by avg_delay desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20ingest_time%20%3D%20ingestion_time\(\)%20%7C%20extend%20delay%20%3D%20datetime_diff\('second'%2C%20ingest_time%2C%20_time\)%20%7C%20summarize%20avg_delay%20%3D%20avg\(delay\)%20by%20%5B'service.name'%5D%2C%20kind%20%7C%20order%20by%20avg_delay%20desc%22%7D)
**Output**
| service.name | kind | avg\_delay |
| --------------- | -------- | ---------- |
| checkoutservice | server | 45 |
| cartservice | client | 30 |
| frontend | internal | 12 |
This query calculates the average ingestion delay per service and kind to identify services affected by delayed ingestion.
Use `ingestion_time` to identify recently ingested suspicious activity, even if the event occurred earlier.
**Query**
```kusto
['sample-http-logs']
| extend ingest_time = ingestion_time()
| where status == '401' and ingest_time > ago(1h)
| project _time, ingest_time, id, method, uri, ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20ingest_time%20%3D%20ingestion_time\(\)%20%7C%20where%20status%20%3D%3D%20'401'%20and%20ingest_time%20%3E%20ago\(1h\)%20%7C%20project%20_time%2C%20ingest_time%2C%20id%2C%20method%2C%20uri%2C%20%5B'geo.country'%5D%22%7D)
**Output**
| \_time | ingest\_time | id | method | uri | geo.country |
| -------------------- | -------------------- | ------- | ------ | ------------------ | ----------- |
| 2025-06-11T09:15:00Z | 2025-06-11T10:45:00Z | user123 | GET | /admin/login | US |
| 2025-06-11T08:50:00Z | 2025-06-11T10:30:00Z | user456 | POST | /api/session/start | DE |
This query surfaces failed login attempts that were ingested in the last hour, regardless of when the request actually occurred.
## Other query languages [#other-query-languages]
Splunk provides the `_indextime` field, which represents when an event was indexed. In APL, the equivalent concept is accessed using the `ingestion_time` function, which must be called explicitly.
```sql Splunk example
... | eval ingest_time=_indextime
```
```kusto APL equivalent
...
| extend ingest_time = ingestion_time()
```
ANSI SQL doesn’t have a standard equivalent to `ingestion_time`, since SQL databases typically don’t distinguish ingestion time from event time. APL provides `ingestion_time` for observability-specific workflows where the arrival time of data is important.
```sql SQL example
SELECT event_time, CURRENT_TIMESTAMP AS ingest_time FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend ingest_time = ingestion_time()
```
---
# abs
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/abs
Use the `abs` function in APL to compute the absolute value of a numeric expression or timespan. The function removes the sign from the input, so it always returns a non-negative result.
`abs` is useful whenever you care about the magnitude of a deviation rather than its direction. For example, you can use it to measure how far a request latency strays from a baseline, or how large a fluctuation in a metric is regardless of whether it's above or below the expected value.
## Usage [#usage]
### Syntax [#syntax]
```kusto
abs(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---------------------- | -------- | ------------------------------------------- |
| `x` | int, real, or timespan | Yes | The value to compute the absolute value of. |
### Returns [#returns]
The absolute value of `x`. The return type matches the input type.
## Example [#example]
Use `abs` to find how far each request's duration deviates from a 200 ms baseline.
**Query**
```kusto
['sample-http-logs']
| extend deviation = abs(req_duration_ms - 200)
| project _time, id, req_duration_ms, deviation
| order by deviation desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20deviation%20%3D%20abs%28req_duration_ms%20-%20200%29%20%7C%20project%20_time%2C%20id%2C%20req_duration_ms%2C%20deviation%20%7C%20order%20by%20deviation%20desc%22%7D)
**Output**
| \_time | id | req\_duration\_ms | deviation |
| ------------------- | ------ | ----------------- | --------- |
| 2024-11-14 10:00:00 | user-1 | 450.0 | 250.0 |
| 2024-11-14 10:01:00 | user-2 | 80.0 | 120.0 |
| 2024-11-14 10:02:00 | user-3 | 205.0 | 5.0 |
## List of related functions [#list-of-related-functions]
* [round](/apl/scalar-functions/mathematical-functions/round): Rounds a value to a specified number of decimal places. Use it when you want to reduce precision rather than compute magnitude.
* [sign](/apl/scalar-functions/mathematical-functions/sign): Returns the sign of a numeric value (+1, 0, or -1). Use it when you want to know direction rather than magnitude.
* [sqrt](/apl/scalar-functions/mathematical-functions/sqrt): Returns the square root. Use it to compute the root-mean-square of deviations for standard deviation calculations.
* [pow](/apl/scalar-functions/mathematical-functions/pow): Raises a value to a power. Use it to square deviations when computing variance.
* [log](/apl/scalar-functions/mathematical-functions/log): Returns the natural logarithm. Use it when you need to work on a logarithmic scale rather than with raw magnitudes.
## Other query languages [#other-query-languages]
In Splunk SPL, the `abs()` function works identically: it takes a single numeric argument and returns its absolute value.
```sql Splunk example
| eval deviation = abs(req_duration_ms - 100)
```
```kusto APL equivalent
['sample-http-logs']
| extend deviation = abs(req_duration_ms - 100)
```
In ANSI SQL, `ABS()` is a standard built-in function with the same semantics as in APL.
```sql SQL example
SELECT ABS(req_duration_ms - 100) AS deviation FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend deviation = abs(req_duration_ms - 100)
```
---
# acos
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/acos
Use the `acos` function in APL to compute the arc cosine (inverse cosine) of a numeric expression. The function returns the angle, in radians, whose cosine equals the input value. The input must be in the range \[-1, 1]; values outside this range return `null`.
`acos` is useful when you work with normalized ratios or rates that fall in the \[-1, 1] range and you need to encode them as angular values. For example, you can use `acos` to convert a normalized success rate into a phase angle for cyclic analysis or signal processing.
## Usage [#usage]
### Syntax [#syntax]
```kusto
acos(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | ------------------------------------ |
| `x` | real | Yes | A real number in the range \[-1, 1]. |
### Returns [#returns]
* The arc cosine of `x` in radians, in the range \[0, π].
* `null` if `x` \< -1 or `x` > 1.
## Example [#example]
Use `acos` to compute the arccosine of a value and return the angle in radians.
**Query**
```kusto
print result = acos(0.5)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22print%20result%20%3D%20acos%280.5%29%22%7D)
**Output**
| result |
| ------ |
| 1.0472 |
## List of related functions [#list-of-related-functions]
* [asin](/apl/scalar-functions/mathematical-functions/asin): Returns the arc sine. Use it when the input value maps to a sine rather than a cosine.
* [atan](/apl/scalar-functions/mathematical-functions/atan): Returns the arc tangent. Use it when the input isn't constrained to \[-1, 1].
* [cos](/apl/scalar-functions/mathematical-functions/cos): Returns the cosine. Use it to apply the forward transformation before using `acos` as the inverse.
* [radians](/apl/scalar-functions/mathematical-functions/radians): Converts degrees to radians. Use it to convert angle output from `acos` into degrees.
* [degrees](/apl/scalar-functions/mathematical-functions/degrees): Converts radians to degrees. Use it if you need the `acos` result expressed in degrees.
## Other query languages [#other-query-languages]
In Splunk SPL, `acos()` is available in the `eval` command and works identically: it returns the arc cosine in radians for inputs in \[-1, 1].
```sql Splunk example
| eval angle = acos(normalized_rate)
```
```kusto APL equivalent
['sample-http-logs']
| extend angle = acos(normalized_rate)
```
In ANSI SQL, `ACOS()` is a standard mathematical function with the same semantics. Pass a value in \[-1, 1] to receive the arc cosine in radians.
```sql SQL example
SELECT ACOS(normalized_rate) AS angle FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend angle = acos(normalized_rate)
```
---
# asin
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/asin
Use the `asin` function in APL to compute the arc sine (inverse sine) of a numeric expression. The function returns the angle, in radians, whose sine equals the input value. The input must be in the range \[-1, 1]; values outside this range return `null`.
`asin` is useful when you work with normalized ratios or rates and need to map them to angular values. For example, you can use `asin` to encode success or error rates as phase angles for cyclic signal analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
asin(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | ------------------------------------ |
| `x` | real | Yes | A real number in the range \[-1, 1]. |
### Returns [#returns]
* The arc sine of `x` in radians, in the range \[-π/2, π/2].
* `null` if `x` \< -1 or `x` > 1.
## Example [#example]
Use `asin` to compute the arcsine of a value and return the angle in radians.
**Query**
```kusto
print result = asin(0.5)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22print%20result%20%3D%20asin%280.5%29%22%7D)
**Output**
| result |
| ------ |
| 0.5236 |
## List of related functions [#list-of-related-functions]
* [acos](/apl/scalar-functions/mathematical-functions/acos): Returns the arc cosine. Use it when the cosine encoding is more appropriate for your data.
* [atan](/apl/scalar-functions/mathematical-functions/atan): Returns the arc tangent. Use it when the input isn't bounded to \[-1, 1].
* [sin](/apl/scalar-functions/mathematical-functions/sin): Returns the sine. Use it to apply the forward transformation before using `asin` as the inverse.
* [radians](/apl/scalar-functions/mathematical-functions/radians): Converts degrees to radians. Use it if your angle data is in degrees before passing it to `asin`.
* [degrees](/apl/scalar-functions/mathematical-functions/degrees): Converts radians to degrees. Use it to convert the `asin` result into degrees.
## Other query languages [#other-query-languages]
In Splunk SPL, `asin()` is available in the `eval` command with the same behavior: it returns the arc sine in radians for an input in \[-1, 1].
```sql Splunk example
| eval angle = asin(normalized_rate)
```
```kusto APL equivalent
['sample-http-logs']
| extend angle = asin(normalized_rate)
```
In ANSI SQL, `ASIN()` is a standard mathematical function. It accepts a value in \[-1, 1] and returns the arc sine in radians.
```sql SQL example
SELECT ASIN(normalized_rate) AS angle FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend angle = asin(normalized_rate)
```
---
# atan
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/atan
Use the `atan` function in APL to compute the arc tangent (inverse tangent) of a numeric expression. The function returns the angle, in radians, whose tangent equals the input value. Unlike `asin` and `acos`, `atan` accepts any real number as input.
`atan` is useful when you want to map a numeric value from an unbounded range to a bounded angular range (-π/2, π/2). For example, you can use `atan` to normalize latency deviations, scores, or ratios into a smooth angle scale for comparison or encoding.
## Usage [#usage]
### Syntax [#syntax]
```kusto
atan(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | -------------- |
| `x` | real | Yes | A real number. |
### Returns [#returns]
The arc tangent of `x` in radians, in the range (-π/2, π/2).
## Example [#example]
Use `atan` to compute the arctangent of a value and return the angle in radians.
**Query**
```kusto
print result = atan(1)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22print%20result%20%3D%20atan%281%29%22%7D)
**Output**
| result |
| ------ |
| 0.7854 |
## List of related functions [#list-of-related-functions]
* [atan2](/apl/scalar-functions/mathematical-functions/atan2): Returns the arc tangent using two arguments (y, x). Use it when you have separate numerator and denominator values.
* [asin](/apl/scalar-functions/mathematical-functions/asin): Returns the arc sine. Use it when the input is bounded to \[-1, 1].
* [acos](/apl/scalar-functions/mathematical-functions/acos): Returns the arc cosine. Use it when you need the inverse cosine instead.
* [tan](/apl/scalar-functions/mathematical-functions/tan): Returns the tangent. Use it to apply the forward transformation before using `atan` as the inverse.
* [radians](/apl/scalar-functions/mathematical-functions/radians): Converts degrees to radians. Use it when your angle data is in degrees.
## Other query languages [#other-query-languages]
In Splunk SPL, `atan()` is available in the `eval` command with the same behavior: it returns the arc tangent in radians for any real number input.
```sql Splunk example
| eval angle = atan(normalized)
```
```kusto APL equivalent
['sample-http-logs']
| extend angle = atan(normalized)
```
In ANSI SQL, `ATAN()` is a standard mathematical function with identical semantics. It accepts any real number and returns the arc tangent in radians.
```sql SQL example
SELECT ATAN(normalized) AS angle FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend angle = atan(normalized)
```
---
# atan2
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/atan2
Use the `atan2` function in APL to compute the angle, in radians, between the positive x-axis and the point (y, x). Unlike `atan`, which takes a single ratio, `atan2` takes separate y and x components, which avoids division-by-zero issues and preserves the correct quadrant information.
`atan2` is useful when you have two independent counts or values and want to express their ratio as an angle. For example, you can use `atan2` to encode the balance between error count and success count as a direction vector, or to analyze the ratio of two traffic volumes.
## Usage [#usage]
### Syntax [#syntax]
```kusto
atan2(y, x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | -------------------------------------------- |
| `y` | real | Yes | The y-coordinate of the point (numerator). |
| `x` | real | Yes | The x-coordinate of the point (denominator). |
### Returns [#returns]
The angle in radians between the positive x-axis and the point (y, x), in the range (-π, π].
## Example [#example]
Use `atan2` to compute the angle between the positive x-axis and the point (x, y).
**Query**
```kusto
print result = atan2(1, 1)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22print%20result%20%3D%20atan2%281%2C%201%29%22%7D)
**Output**
| result |
| ------ |
| 0.7854 |
## List of related functions [#list-of-related-functions]
* [atan](/apl/scalar-functions/mathematical-functions/atan): Returns the arc tangent from a single ratio. Use it when you have a pre-computed ratio instead of separate y and x components.
* [asin](/apl/scalar-functions/mathematical-functions/asin): Returns the arc sine. Use it when the input maps to a sine value in \[-1, 1].
* [acos](/apl/scalar-functions/mathematical-functions/acos): Returns the arc cosine. Use it when the input maps to a cosine value in \[-1, 1].
* [tan](/apl/scalar-functions/mathematical-functions/tan): Returns the tangent. Use it to apply the forward transformation before using `atan2` as the inverse.
* [degrees](/apl/scalar-functions/mathematical-functions/degrees): Converts radians to degrees. Use it if you prefer the `atan2` result in degrees.
## Other query languages [#other-query-languages]
In Splunk SPL, `atan2(y, x)` is available in the `eval` command with the same argument order and semantics.
```sql Splunk example
| eval angle = atan2(errors, successes)
```
```kusto APL equivalent
['sample-http-logs']
| extend angle = atan2(todouble(errors), todouble(successes))
```
In ANSI SQL, the function is typically named `ATN2(y, x)` in SQL Server or `ATAN2(y, x)` in PostgreSQL. The argument order (y first, then x) is the same as in APL.
```sql SQL example
SELECT ATN2(errors, successes) AS angle FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend angle = atan2(todouble(errors), todouble(successes))
```
---
# cos
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/cos
Use the `cos` function in APL to compute the cosine of an angle expressed in radians. The function accepts any real number and returns a value in the range \[-1, 1].
`cos` is useful in observability and log analysis when you need to encode time-of-day or other cyclic patterns as a continuous numeric feature. For example, you can combine `cos` and `sin` to represent hour-of-day as a pair of cyclic coordinates, which preserves the circular distance between hours for anomaly detection or grouping.
## Usage [#usage]
### Syntax [#syntax]
```kusto
cos(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | --------------------- |
| `x` | real | Yes | The angle in radians. |
### Returns [#returns]
The cosine of `x`, a real number in the range \[-1, 1].
## Example [#example]
Use `cos` to compute the cosine of an angle in radians.
**Query**
```kusto
print result = cos(pi())
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22print%20result%20%3D%20cos%28pi%28%29%29%22%7D)
**Output**
| result |
| ------ |
| -1 |
## List of related functions [#list-of-related-functions]
* [sin](/apl/scalar-functions/mathematical-functions/sin): Returns the sine. Use `sin` and `cos` together to produce a two-dimensional cyclic encoding of angles.
* [tan](/apl/scalar-functions/mathematical-functions/tan): Returns the tangent. Use it when you need the ratio of sine to cosine.
* [acos](/apl/scalar-functions/mathematical-functions/acos): Returns the arc cosine. Use it as the inverse of `cos`.
* [pi](/apl/scalar-functions/mathematical-functions/pi): Returns the constant π. Use it to compute angle values in radians.
* [radians](/apl/scalar-functions/mathematical-functions/radians): Converts degrees to radians. Use it to prepare angle inputs before calling `cos`.
## Other query languages [#other-query-languages]
In Splunk SPL, `cos()` is available in the `eval` command with the same behavior: it takes an angle in radians and returns the cosine.
```sql Splunk example
| eval cos_val = cos(angle_rad)
```
```kusto APL equivalent
['sample-http-logs']
| extend cos_val = cos(angle_rad)
```
In ANSI SQL, `COS()` is a standard mathematical function with identical semantics.
```sql SQL example
SELECT COS(angle_rad) AS cos_val FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend cos_val = cos(angle_rad)
```
---
# cot
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/cot
Use the `cot` function in APL to compute the cotangent of an angle expressed in radians. The cotangent is the reciprocal of the tangent: `cot(x) = cos(x) / sin(x)`. The function is undefined at multiples of π, where the sine is zero.
`cot` is useful when you work with angular or cyclic calculations and need the cotangent as part of a larger formula. In observability, you might use it to transform normalized metric ratios into angular coordinates for signal analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
cot(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | -------------------------------------------------- |
| `x` | real | Yes | The angle in radians. Must not be a multiple of π. |
### Returns [#returns]
The cotangent of `x`. Returns infinity or `null` when `sin(x)` equals zero (that is, when `x` is a multiple of π).
## Example [#example]
Use `cot` to compute the cotangent of an angle in radians.
**Query**
```kusto
print result = cot(pi() / 4)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22print%20result%20%3D%20cot%28pi%28%29%20/%204%29%22%7D)
**Output**
| result |
| ------ |
| 1.0000 |
## List of related functions [#list-of-related-functions]
* [tan](/apl/scalar-functions/mathematical-functions/tan): Returns the tangent, the reciprocal of `cot`. Use it as the primary trigonometric ratio when cotangent isn't needed.
* [sin](/apl/scalar-functions/mathematical-functions/sin): Returns the sine. Use it along with `cos` to compute cotangent manually as `cos(x)/sin(x)`.
* [cos](/apl/scalar-functions/mathematical-functions/cos): Returns the cosine. Use it in combination with `sin` for manual cotangent calculation.
* [atan](/apl/scalar-functions/mathematical-functions/atan): Returns the arc tangent. Use it as the inverse of `tan` when you need to recover an angle.
* [radians](/apl/scalar-functions/mathematical-functions/radians): Converts degrees to radians. Use it to prepare degree-based angle inputs before calling `cot`.
## Other query languages [#other-query-languages]
Splunk SPL doesn't include a built-in `cot()` function. You compute it as the reciprocal of `tan()`.
```sql Splunk example
| eval cot_val = 1 / tan(angle_rad)
```
```kusto APL equivalent
['sample-http-logs']
| extend cot_val = cot(angle_rad)
```
ANSI SQL does not define a standard `COT()` function, though SQL Server provides one. In PostgreSQL and other databases, you compute it as `1 / TAN(x)`.
```sql SQL example
SELECT 1.0 / TAN(angle_rad) AS cot_val FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend cot_val = cot(angle_rad)
```
---
# degrees
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/degrees
Use the `degrees` function in APL to convert an angle from radians to degrees. The conversion formula is `degrees = (180 / π) × angle_in_radians`.
`degrees` is useful whenever you compute angles using trigonometric or inverse trigonometric functions, which return values in radians, but need the result expressed in degrees for display or comparison. For example, you can convert the output of `atan2` or `acos` into a degree value that's easier to interpret.
## Usage [#usage]
### Syntax [#syntax]
```kusto
degrees(a)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | -------------------------------- |
| `a` | real | Yes | The angle in radians to convert. |
### Returns [#returns]
The angle in degrees corresponding to the input radian value.
## Example [#example]
Use `degrees` to convert an angle in radians to degrees.
**Query**
```kusto
print result = degrees(pi())
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22print%20result%20%3D%20degrees%28pi%28%29%29%22%7D)
**Output**
| result |
| ------ |
| 180 |
## List of related functions [#list-of-related-functions]
* [radians](/apl/scalar-functions/mathematical-functions/radians): Converts degrees to radians. Use it for the inverse conversion before passing values to trigonometric functions.
* [pi](/apl/scalar-functions/mathematical-functions/pi): Returns the constant π. Use it in manual radian-to-degree conversions as an alternative to `degrees`.
* [cos](/apl/scalar-functions/mathematical-functions/cos): Returns the cosine of an angle in radians. Use `degrees` to convert its output for display.
* [sin](/apl/scalar-functions/mathematical-functions/sin): Returns the sine of an angle in radians. Use `degrees` to convert angular results.
* [atan2](/apl/scalar-functions/mathematical-functions/atan2): Returns an angle in radians from two coordinates. Use `degrees` to convert its output to degrees.
## Other query languages [#other-query-languages]
Splunk SPL doesn't include a built-in `degrees()` function. You compute the conversion manually by multiplying the radian value by `180 / pi()`.
```sql Splunk example
| eval angle_deg = angle_rad * (180 / pi())
```
```kusto APL equivalent
['sample-http-logs']
| extend angle_deg = degrees(angle_rad)
```
In SQL Server and PostgreSQL, `DEGREES()` is a built-in function with identical semantics to the APL version.
```sql SQL example
SELECT DEGREES(angle_rad) AS angle_deg FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend angle_deg = degrees(angle_rad)
```
---
# exp
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/exp
Use the `exp` function in APL to compute the base-e exponential of a value: e^x. It's the inverse of the natural logarithm function `log`.
`exp` is useful when you work with log-transformed data and need to recover the original scale. For example, if you have computed the average of log-transformed latencies and want the geometric mean on the original scale, apply `exp` to the result. You can also use `exp` to model exponential growth or decay in metric data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
exp(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | ------------------- |
| `x` | real | Yes | The exponent value. |
### Returns [#returns]
The base-e exponential of `x`: e^x.
## Example [#example]
Use `exp` to compute the geometric mean of request durations from log-transformed values.
**Query**
```kusto
['sample-http-logs']
| where req_duration_ms > 0
| summarize geometric_mean = exp(avg(log(req_duration_ms))) by bin(_time, 1h)
| project _time, geometric_mean
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%20%7C%20where%20req_duration_ms%20%3E%200%20%7C%20summarize%20geometric_mean%20%3D%20exp%28avg%28log%28req_duration_ms%29%29%29%20by%20bin%28_time%2C%201h%29%20%7C%20project%20_time%2C%20geometric_mean%22%7D)
**Output**
| \_time | geometric\_mean |
| ------------------- | --------------- |
| 2024-11-14 10:00:00 | 85.3 |
| 2024-11-14 11:00:00 | 92.7 |
| 2024-11-14 12:00:00 | 78.1 |
## List of related functions [#list-of-related-functions]
* [log](/apl/scalar-functions/mathematical-functions/log): Returns the natural logarithm. Use it as the inverse of `exp` or to apply log transformations before aggregating.
* [exp2](/apl/scalar-functions/mathematical-functions/exp2): Returns 2^x. Use it instead of `exp` when working with binary (base-2) scales.
* [exp10](/apl/scalar-functions/mathematical-functions/exp10): Returns 10^x. Use it instead of `exp` when working with base-10 (decibel or order-of-magnitude) scales.
* [pow](/apl/scalar-functions/mathematical-functions/pow): Raises any base to a power. Use it when the base isn't e, 2, or 10.
* [sqrt](/apl/scalar-functions/mathematical-functions/sqrt): Returns the square root. Use it for simpler power-of-0.5 calculations rather than `exp(0.5 * log(x))`.
## Other query languages [#other-query-languages]
In Splunk SPL, `exp()` works the same way: it computes e^x for a numeric argument.
```sql Splunk example
| eval result = exp(log_value)
```
```kusto APL equivalent
['sample-http-logs']
| extend result = exp(log_value)
```
In ANSI SQL, `EXP()` is a standard function with identical semantics.
```sql SQL example
SELECT EXP(log_value) AS result FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend result = exp(log_value)
```
---
# exp10
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/exp10
Use the `exp10` function in APL to compute the base-10 exponential of a value: 10^x. It's the inverse of the common (base-10) logarithm function `log10`.
`exp10` is useful when you work with data on a logarithmic scale expressed in powers of ten, such as decibel values, orders of magnitude, or `log10`-transformed metrics. Apply `exp10` to reverse a `log10` transformation and return to the original scale.
## Usage [#usage]
### Syntax [#syntax]
```kusto
exp10(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | ------------------- |
| `x` | real | Yes | The exponent value. |
### Returns [#returns]
The base-10 exponential of `x`: 10^x.
## Example [#example]
Use `exp10` to recover the geometric mean of request durations from a log10-transformed average.
**Query**
```kusto
['sample-http-logs']
| where req_duration_ms > 0
| summarize geometric_mean = exp10(avg(log10(req_duration_ms))) by bin(_time, 1h)
| project _time, geometric_mean
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%20%7C%20where%20req_duration_ms%20%3E%200%20%7C%20summarize%20geometric_mean%20%3D%20exp10%28avg%28log10%28req_duration_ms%29%29%29%20by%20bin%28_time%2C%201h%29%20%7C%20project%20_time%2C%20geometric_mean%22%7D)
**Output**
| \_time | geometric\_mean |
| ------------------- | --------------- |
| 2024-11-14 10:00:00 | 85.3 |
| 2024-11-14 11:00:00 | 92.7 |
| 2024-11-14 12:00:00 | 78.1 |
## List of related functions [#list-of-related-functions]
* [log10](/apl/scalar-functions/mathematical-functions/log10): Returns the base-10 logarithm. Use it as the inverse of `exp10`.
* [exp](/apl/scalar-functions/mathematical-functions/exp): Returns e^x. Use it for natural-log-scale transformations.
* [exp2](/apl/scalar-functions/mathematical-functions/exp2): Returns 2^x. Use it for binary-scale transformations.
* [pow](/apl/scalar-functions/mathematical-functions/pow): Raises any base to a power. Use it when the base isn't 10.
* [log](/apl/scalar-functions/mathematical-functions/log): Returns the natural logarithm. Use it together with `exp` for natural-log-scale analysis.
## Other query languages [#other-query-languages]
Splunk SPL doesn't include a built-in `exp10()` function. You compute it using `pow(10, x)`.
```sql Splunk example
| eval result = pow(10, x)
```
```kusto APL equivalent
['sample-http-logs']
| extend result = exp10(x)
```
Standard SQL does not define `EXP10()`. You compute it using `POWER(10, x)`.
```sql SQL example
SELECT POWER(10, x) AS result FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend result = exp10(x)
```
---
# exp2
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/exp2
Use the `exp2` function in APL to compute the base-2 exponential of a value: 2^x. It's the inverse of the base-2 logarithm function `log2`.
`exp2` is useful when you work with data measured on a binary scale, such as memory sizes, network bandwidths, or binary tree depths. You can also use it to reverse a `log2` transformation and recover values on the original scale.
## Usage [#usage]
### Syntax [#syntax]
```kusto
exp2(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | ------------------- |
| `x` | real | Yes | The exponent value. |
### Returns [#returns]
The base-2 exponential of `x`: 2^x.
## Example [#example]
Use `exp2` to recover the geometric mean of request durations from a log2-transformed average.
**Query**
```kusto
['sample-http-logs']
| where req_duration_ms > 0
| summarize geometric_mean = exp2(avg(log2(req_duration_ms))) by bin(_time, 1h)
| project _time, geometric_mean
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%20%7C%20where%20req_duration_ms%20%3E%200%20%7C%20summarize%20geometric_mean%20%3D%20exp2%28avg%28log2%28req_duration_ms%29%29%29%20by%20bin%28_time%2C%201h%29%20%7C%20project%20_time%2C%20geometric_mean%22%7D)
**Output**
| \_time | geometric\_mean |
| ------------------- | --------------- |
| 2024-11-14 10:00:00 | 85.3 |
| 2024-11-14 11:00:00 | 92.7 |
| 2024-11-14 12:00:00 | 78.1 |
## List of related functions [#list-of-related-functions]
* [log2](/apl/scalar-functions/mathematical-functions/log2): Returns the base-2 logarithm. Use it as the inverse of `exp2`.
* [exp](/apl/scalar-functions/mathematical-functions/exp): Returns e^x. Use it for natural-log-scale transformations instead of base-2.
* [exp10](/apl/scalar-functions/mathematical-functions/exp10): Returns 10^x. Use it for base-10 (order-of-magnitude) scales.
* [pow](/apl/scalar-functions/mathematical-functions/pow): Raises any base to a power. Use it when neither e, 2, nor 10 is the intended base.
* [log](/apl/scalar-functions/mathematical-functions/log): Returns the natural logarithm. Use it together with `exp` for natural-log-scale analysis.
## Other query languages [#other-query-languages]
Splunk SPL doesn't include a built-in `exp2()` function. You compute it as `pow(2, x)`.
```sql Splunk example
| eval result = pow(2, x)
```
```kusto APL equivalent
['sample-http-logs']
| extend result = exp2(x)
```
Standard SQL does not define `EXP2()`. You compute it using `POWER(2, x)`.
```sql SQL example
SELECT POWER(2, x) AS result FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend result = exp2(x)
```
---
# gamma
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/gamma
Use the `gamma` function in APL to compute the [gamma function](https://en.wikipedia.org/wiki/Gamma_function) of a numeric value. For positive integers, `gamma(n)` equals `(n-1)!`. The gamma function generalizes the factorial to real and complex numbers.
`gamma` is useful in statistical calculations such as computing combinatorial coefficients, probability distributions, and Bayesian models. In observability, you might use it in custom anomaly scoring or when implementing statistical tests directly in APL.
## Usage [#usage]
### Syntax [#syntax]
```kusto
gamma(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | -------------------------------------------------------- |
| `x` | real | Yes | The input value. Must not be zero or a negative integer. |
### Returns [#returns]
* The gamma function of `x`.
* Returns `null` when `x` is zero or a negative integer.
* For large inputs, the result may overflow to infinity.
## Example [#example]
Use `gamma` to compute the gamma function value for a request duration in seconds.
**Query**
```kusto
['sample-http-logs']
| where req_duration_ms > 0
| extend duration_s = req_duration_ms / 1000.0
| where duration_s < 5
| extend gamma_val = gamma(duration_s)
| where isfinite(gamma_val)
| project _time, id, req_duration_ms, gamma_val
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%20%7C%20where%20req_duration_ms%20%3E%200%20%7C%20extend%20duration_s%20%3D%20req_duration_ms%20/%201000.0%20%7C%20where%20duration_s%20%3C%205%20%7C%20extend%20gamma_val%20%3D%20gamma%28duration_s%29%20%7C%20where%20isfinite%28gamma_val%29%20%7C%20project%20_time%2C%20id%2C%20req_duration_ms%2C%20gamma_val%22%7D)
**Output**
| \_time | id | req\_duration\_ms | gamma\_val |
| ------------------- | ------ | ----------------- | ---------- |
| 2024-11-14 10:00:00 | user-1 | 1000.0 | 1.0000 |
| 2024-11-14 10:01:00 | user-2 | 2000.0 | 1.0000 |
| 2024-11-14 10:02:00 | user-3 | 3000.0 | 2.0000 |
## List of related functions [#list-of-related-functions]
* [loggamma](/apl/scalar-functions/mathematical-functions/loggamma): Returns the natural log of the absolute gamma function value. Use it to avoid numeric overflow when working with large inputs.
* [log](/apl/scalar-functions/mathematical-functions/log): Returns the natural logarithm. Use it when you want to work in log space rather than applying `gamma` directly.
* [exp](/apl/scalar-functions/mathematical-functions/exp): Returns e^x. Use it to exponentiate log-space results from `loggamma` back to the original scale.
* [pow](/apl/scalar-functions/mathematical-functions/pow): Raises a value to a power. Use it for simpler power calculations that don't require the general gamma function.
* [isfinite](/apl/scalar-functions/mathematical-functions/isfinite): Returns whether a value is finite. Use it to filter out overflow results from `gamma` on large inputs.
## Other query languages [#other-query-languages]
Splunk SPL doesn't include a built-in `gamma()` function. You need to implement the gamma function using external lookup tables or the Machine Learning Toolkit.
```sql Splunk example
| eval gamma_val = null()
```
```kusto APL equivalent
['sample-http-logs']
| extend gamma_val = gamma(x)
```
Standard SQL does not define a `GAMMA()` function. You typically implement it in application code or through database-specific extensions.
```sql SQL example
-- No standard SQL equivalent; use application code
SELECT NULL AS gamma_val FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend gamma_val = gamma(x)
```
---
# isfinite
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/isfinite
Use the `isfinite` function in APL to check whether a numeric value is finite, meaning it's neither positive infinity, negative infinity, nor NaN (Not a Number). The function returns `true` for any real number that has a well-defined, bounded value.
`isfinite` is essential for data quality checks. Division by zero, logarithm of a non-positive number, or other edge cases can produce infinity or NaN in your computed columns. Use `isfinite` to identify or filter these invalid values before aggregating or visualizing data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
isfinite(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | --------------------------- |
| `x` | real | Yes | The numeric value to check. |
### Returns [#returns]
`true` if `x` is a finite real number (not infinity and not NaN). `false` otherwise.
## Example [#example]
Use `isfinite` to check whether a computed value is finite before using it in aggregations.
**Query**
```kusto
['sample-http-logs']
| extend log_duration = log(req_duration_ms)
| extend is_valid = isfinite(log_duration)
| project _time, id, req_duration_ms, log_duration, is_valid
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20log_duration%20%3D%20log%28req_duration_ms%29%20%7C%20extend%20is_valid%20%3D%20isfinite%28log_duration%29%20%7C%20project%20_time%2C%20id%2C%20req_duration_ms%2C%20log_duration%2C%20is_valid%22%7D)
**Output**
| \_time | id | req\_duration\_ms | log\_duration | is\_valid |
| ------------------- | ------ | ----------------- | ------------- | --------- |
| 2024-11-14 10:00:00 | user-1 | 120.0 | 4.7875 | true |
| 2024-11-14 10:01:00 | user-2 | 0.0 | -inf | false |
| 2024-11-14 10:02:00 | user-3 | 80.0 | 4.3820 | true |
## List of related functions [#list-of-related-functions]
* [isinf](/apl/scalar-functions/mathematical-functions/isinf): Returns `true` only for infinite values. Use it when you want to distinguish infinity from NaN.
* [isnan](/apl/scalar-functions/mathematical-functions/isnan): Returns `true` only for NaN values. Use it when you want to detect only Not-a-Number results.
* [isint](/apl/scalar-functions/mathematical-functions/isint): Returns `true` for integer values. Use it to check whether a numeric value is an integer rather than a floating-point result.
* [log](/apl/scalar-functions/mathematical-functions/log): Returns the natural logarithm. Combine with `isfinite` to validate log computations on potentially non-positive inputs.
* [gamma](/apl/scalar-functions/mathematical-functions/gamma): Returns the gamma function. Use `isfinite` to filter out overflow results when computing `gamma` on large values.
## Other query languages [#other-query-languages]
Splunk SPL doesn't have a direct `isfinite()` function. You typically check for null or use `isnum()` to verify that a value is a valid number, but these checks don't cover infinity.
```sql Splunk example
| eval is_valid = if(isnum(value) AND value != 'Infinity', 1, 0)
```
```kusto APL equivalent
['sample-http-logs']
| extend is_valid = isfinite(value)
```
Standard SQL does not define an `ISFINITE()` function. You typically check for `IS NOT NULL` and whether the value falls within a valid range, but you cannot directly detect infinity in most SQL dialects.
```sql SQL example
SELECT CASE WHEN value IS NOT NULL AND value BETWEEN -1e308 AND 1e308 THEN 1 ELSE 0 END AS is_valid FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend is_valid = isfinite(value)
```
---
# isinf
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/isinf
Use the `isinf` function in APL to check whether a numeric value is positive or negative infinity. The function returns `true` only when the value is ±∞ and `false` for all finite values and NaN.
`isinf` is useful for detecting overflow conditions or division-by-zero results in computed columns. Unlike `isfinite`, which also catches NaN values, `isinf` specifically targets infinite results, allowing you to distinguish overflow from undefined computations.
## Usage [#usage]
### Syntax [#syntax]
```kusto
isinf(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | --------------------------- |
| `x` | real | Yes | The numeric value to check. |
### Returns [#returns]
`true` if `x` is positive or negative infinity. `false` for finite values and NaN.
## List of related functions [#list-of-related-functions]
* [isfinite](/apl/scalar-functions/mathematical-functions/isfinite): Returns `true` for values that are neither infinite nor NaN. Use it for a broader check covering all invalid float states.
* [isnan](/apl/scalar-functions/mathematical-functions/isnan): Returns `true` only for NaN values. Use it alongside `isinf` to detect the other class of invalid float.
* [isint](/apl/scalar-functions/mathematical-functions/isint): Returns `true` for integer values. Use it when you need to validate that a value is a whole number.
* [not](/apl/scalar-functions/mathematical-functions/not): Reverses a boolean value. Use `not(isinf(x))` as a shorthand for `isfinite(x)` when NaN values aren't a concern.
* [log](/apl/scalar-functions/mathematical-functions/log): Returns the natural logarithm. Compute `log` on potentially zero or negative values and use `isinf` to detect the resulting `-inf`.
## Other query languages [#other-query-languages]
Splunk SPL doesn't have a direct `isinf()` function. You typically check whether a value equals a specific infinity representation, which isn't directly available in most SPL expressions.
```sql Splunk example
| eval is_inf = if(value = 'Infinity' OR value = '-Infinity', 1, 0)
```
```kusto APL equivalent
['sample-http-logs']
| extend is_inf = isinf(value)
```
Standard SQL does not define an `ISINF()` function. Most SQL databases raise an error on division by zero rather than producing infinity, so there is no direct equivalent.
```sql SQL example
-- Division by zero raises an error in most SQL dialects; no direct ISINF() equivalent
SELECT CASE WHEN value > 1e308 OR value < -1e308 THEN 1 ELSE 0 END AS is_inf FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend is_inf = isinf(value)
```
---
# isint
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/isint
Use the `isint` function in APL to check whether a numeric value is an integer, meaning it has no fractional component. The function returns `true` for both positive and negative integer values and `false` for floating-point values, NaN, or infinity.
`isint` is useful for data validation and type-checking in observability queries. For example, you can use it to verify that computed fields or imported values are whole numbers before performing integer-specific operations such as array indexing or factorial-based calculations.
## Usage [#usage]
### Syntax [#syntax]
```kusto
isint(expression)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------------ | ---- | -------- | --------------------------- |
| `expression` | real | Yes | The numeric value to check. |
### Returns [#returns]
`true` if the value is a positive or negative integer (no fractional part). `false` for non-integer real numbers, NaN, and infinity.
## Example [#example]
Use `isint` to check whether a value is of integer type.
**Query**
```kusto
print a = isint(42), b = isint(4.2)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22print%20a%20%3D%20isint%2842%29%2C%20b%20%3D%20isint%284.2%29%22%7D)
**Output**
| a | b |
| ---- | ----- |
| true | false |
## List of related functions [#list-of-related-functions]
* [isfinite](/apl/scalar-functions/mathematical-functions/isfinite): Returns `true` for values that are neither infinite nor NaN. Use it for a broader check covering all valid float states.
* [isinf](/apl/scalar-functions/mathematical-functions/isinf): Returns `true` only for infinite values. Use it to detect overflow results specifically.
* [isnan](/apl/scalar-functions/mathematical-functions/isnan): Returns `true` only for NaN values. Use it to detect undefined computation results.
* [round](/apl/scalar-functions/mathematical-functions/round): Rounds a value to a given precision. Use it to produce integer-valued results before applying `isint`.
* [sign](/apl/scalar-functions/mathematical-functions/sign): Returns the sign of a value. Use it alongside `isint` when you need both the sign and the integer-status of a value.
## Other query languages [#other-query-languages]
Splunk SPL doesn't have a direct `isint()` function, but you can approximate the check by comparing a value to its floored counterpart.
```sql Splunk example
| eval is_int = if(floor(value) == value, 1, 0)
```
```kusto APL equivalent
['sample-http-logs']
| extend is_int = isint(value)
```
Standard SQL does not define an `ISINT()` function. You typically compare a value to its floored equivalent to determine whether it is a whole number.
```sql SQL example
SELECT CASE WHEN FLOOR(value) = value THEN 1 ELSE 0 END AS is_int FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend is_int = isint(value)
```
---
# isnan
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/isnan
Use the `isnan` function in APL to check whether a numeric value is NaN (Not a Number). The function returns `true` when the value is NaN and `false` for all finite values and infinities.
`isnan` is essential for data quality work. Operations such as `log` of a negative number, `sqrt` of a negative number, or `0 / 0` produce NaN in APL. Use `isnan` to detect and filter these invalid values before aggregating, charting, or alerting on your data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
isnan(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | --------------------------- |
| `x` | real | Yes | The numeric value to check. |
### Returns [#returns]
`true` if `x` is NaN. `false` for finite values and infinity.
## List of related functions [#list-of-related-functions]
* [isfinite](/apl/scalar-functions/mathematical-functions/isfinite): Returns `true` for values that are neither infinite nor NaN. Use it for a combined check covering all invalid float states.
* [isinf](/apl/scalar-functions/mathematical-functions/isinf): Returns `true` only for infinite values. Use it alongside `isnan` to cover all edge cases.
* [isint](/apl/scalar-functions/mathematical-functions/isint): Returns `true` for integer values. Use it for whole-number validation rather than float validity.
* [log](/apl/scalar-functions/mathematical-functions/log): Returns the natural logarithm. Use `isnan` to catch NaN results when `log` is applied to non-positive values.
* [sqrt](/apl/scalar-functions/mathematical-functions/sqrt): Returns the square root. Use `isnan` to detect NaN results from `sqrt` on negative inputs.
## Other query languages [#other-query-languages]
Splunk SPL includes the `isnan()` function, which works the same way as in APL: it returns `true` (1) when the value is NaN.
```sql Splunk example
| eval is_nan = isnan(value)
```
```kusto APL equivalent
['sample-http-logs']
| extend is_nan = isnan(value)
```
Standard SQL does not define an `ISNAN()` function. In most dialects, `NaN` is not a concept; division by zero raises an error instead. However, some databases (such as PostgreSQL) do support `NaN` as a float value, and you check for it with `value = 'NaN'`.
```sql SQL example
SELECT CASE WHEN value = 'NaN' THEN 1 ELSE 0 END AS is_nan FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend is_nan = isnan(value)
```
---
# log
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/log
Use the `log` function in APL to compute the natural logarithm (base-e) of a positive numeric value. The function is the inverse of `exp`.
`log` is one of the most commonly used mathematical functions in observability. Log-transforming latency, error counts, or request rates compresses wide value ranges into a more manageable scale, reduces the influence of extreme outliers, and can reveal patterns that are linear on a log scale. It's also the basis for computing geometric means with `exp(avg(log(x)))`.
## Usage [#usage]
### Syntax [#syntax]
```kusto
log(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | ------------------------------- |
| `x` | real | Yes | A positive real number (x > 0). |
### Returns [#returns]
* The natural logarithm of `x`.
* `null` if `x` is negative, zero, or can't be converted to a real value.
## Example [#example]
Use `log` to compress request durations onto a natural log scale.
**Query**
```kusto
['sample-http-logs']
| where req_duration_ms > 0
| extend log_duration = log(req_duration_ms)
| project _time, id, req_duration_ms, log_duration
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%20%7C%20where%20req_duration_ms%20%3E%200%20%7C%20extend%20log_duration%20%3D%20log%28req_duration_ms%29%20%7C%20project%20_time%2C%20id%2C%20req_duration_ms%2C%20log_duration%22%7D)
**Output**
| \_time | id | req\_duration\_ms | log\_duration |
| ------------------- | ------ | ----------------- | ------------- |
| 2024-11-14 10:00:00 | user-1 | 1.0 | 0.0000 |
| 2024-11-14 10:01:00 | user-2 | 100.0 | 4.6052 |
| 2024-11-14 10:02:00 | user-3 | 10000.0 | 9.2103 |
## List of related functions [#list-of-related-functions]
* [exp](/apl/scalar-functions/mathematical-functions/exp): Returns e^x. Use it as the inverse of `log` to return to the original scale.
* [log2](/apl/scalar-functions/mathematical-functions/log2): Returns the base-2 logarithm. Use it for binary-scale analysis such as bit depth or memory sizing.
* [log10](/apl/scalar-functions/mathematical-functions/log10): Returns the base-10 logarithm. Use it for order-of-magnitude analysis or decibel calculations.
* [loggamma](/apl/scalar-functions/mathematical-functions/loggamma): Returns the log of the absolute value of the gamma function. Use it to avoid overflow when computing `gamma` on large inputs.
* [sqrt](/apl/scalar-functions/mathematical-functions/sqrt): Returns the square root. Use it as a lighter alternative to `log` for compressing small value ranges.
## Other query languages [#other-query-languages]
In Splunk SPL, the natural logarithm is called `ln()` rather than `log()`. The SPL `log()` function computes the base-10 logarithm by default. In APL, `log()` always means the natural logarithm.
```sql Splunk example
| eval ln_duration = ln(req_duration_ms)
```
```kusto APL equivalent
['sample-http-logs']
| extend ln_duration = log(req_duration_ms)
```
In ANSI SQL, `LN()` computes the natural logarithm and `LOG()` computes the base-10 logarithm in most dialects. In APL, `log()` means the natural logarithm, matching SQL's `LN()`.
```sql SQL example
SELECT LN(req_duration_ms) AS ln_duration FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend ln_duration = log(req_duration_ms)
```
---
# log10
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/log10
Use the `log10` function in APL to compute the common (base-10) logarithm of a positive numeric value. The function returns `null` for zero or negative inputs.
`log10` is useful for order-of-magnitude analysis. Because each unit increase in `log10(x)` represents a tenfold increase in `x`, you can use it to compare values that span multiple orders of magnitude, build decibel-scale metrics, or bucket data by powers of ten. It's also the basis for recovering values with `exp10`.
## Usage [#usage]
### Syntax [#syntax]
```kusto
log10(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | ------------------------------- |
| `x` | real | Yes | A positive real number (x > 0). |
### Returns [#returns]
* The base-10 logarithm of `x`.
* `null` if `x` is negative, zero, or can't be converted to a real value.
## Example [#example]
Use `log10` to express request durations on a base-10 log scale where each integer step represents an order of magnitude.
**Query**
```kusto
['sample-http-logs']
| where req_duration_ms > 0
| extend log10_duration = log10(req_duration_ms)
| project _time, id, req_duration_ms, log10_duration
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%20%7C%20where%20req_duration_ms%20%3E%200%20%7C%20extend%20log10_duration%20%3D%20log10%28req_duration_ms%29%20%7C%20project%20_time%2C%20id%2C%20req_duration_ms%2C%20log10_duration%22%7D)
**Output**
| \_time | id | req\_duration\_ms | log10\_duration |
| ------------------- | ------ | ----------------- | --------------- |
| 2024-11-14 10:00:00 | user-1 | 1.0 | 0.0000 |
| 2024-11-14 10:01:00 | user-2 | 100.0 | 2.0000 |
| 2024-11-14 10:02:00 | user-3 | 10000.0 | 4.0000 |
## List of related functions [#list-of-related-functions]
* [exp10](/apl/scalar-functions/mathematical-functions/exp10): Returns 10^x. Use it as the inverse of `log10` to recover original values.
* [log](/apl/scalar-functions/mathematical-functions/log): Returns the natural logarithm. Use it when you want the mathematically natural base rather than base-10.
* [log2](/apl/scalar-functions/mathematical-functions/log2): Returns the base-2 logarithm. Use it for binary-scale analysis.
* [pow](/apl/scalar-functions/mathematical-functions/pow): Raises a value to a power. Use it to compute powers of 10 directly as an alternative to `exp10`.
* [round](/apl/scalar-functions/mathematical-functions/round): Rounds a value. Use it after `log10` to bucket values by integer order of magnitude.
## Other query languages [#other-query-languages]
In Splunk SPL, `log10()` works identically: it returns the base-10 logarithm.
```sql Splunk example
| eval log10_duration = log10(req_duration_ms)
```
```kusto APL equivalent
['sample-http-logs']
| extend log10_duration = log10(req_duration_ms)
```
In ANSI SQL, `LOG10()` is a standard function in SQL Server and PostgreSQL with the same behavior as APL's `log10`.
```sql SQL example
SELECT LOG10(req_duration_ms) AS log10_duration FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend log10_duration = log10(req_duration_ms)
```
---
# log2
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/log2
Use the `log2` function in APL to compute the base-2 logarithm of a positive numeric value. The function is the inverse of `exp2`.
`log2` is useful when working with data measured on a binary scale, such as memory sizes, network packet lengths, or binary tree structures. Because each unit increase in `log2(x)` corresponds to a doubling of `x`, `log2` is a natural choice for analyzing values that grow or shrink by factors of two. You can also use it to compute geometric means in base-2 space.
## Usage [#usage]
### Syntax [#syntax]
```kusto
log2(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | ------------------------------- |
| `x` | real | Yes | A positive real number (x > 0). |
### Returns [#returns]
* The base-2 logarithm of `x`.
* `null` if `x` is negative, zero, or can't be converted to a real value.
## Example [#example]
Use `log2` to express request durations on a binary log scale where each unit represents a doubling.
**Query**
```kusto
['sample-http-logs']
| where req_duration_ms > 0
| extend log2_duration = log2(req_duration_ms)
| project _time, id, req_duration_ms, log2_duration
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%20%7C%20where%20req_duration_ms%20%3E%200%20%7C%20extend%20log2_duration%20%3D%20log2%28req_duration_ms%29%20%7C%20project%20_time%2C%20id%2C%20req_duration_ms%2C%20log2_duration%22%7D)
**Output**
| \_time | id | req\_duration\_ms | log2\_duration |
| ------------------- | ------ | ----------------- | -------------- |
| 2024-11-14 10:00:00 | user-1 | 1.0 | 0.000 |
| 2024-11-14 10:01:00 | user-2 | 64.0 | 6.000 |
| 2024-11-14 10:02:00 | user-3 | 1024.0 | 10.000 |
## List of related functions [#list-of-related-functions]
* [exp2](/apl/scalar-functions/mathematical-functions/exp2): Returns 2^x. Use it as the inverse of `log2`.
* [log](/apl/scalar-functions/mathematical-functions/log): Returns the natural logarithm. Use it when you prefer the natural base rather than base-2.
* [log10](/apl/scalar-functions/mathematical-functions/log10): Returns the base-10 logarithm. Use it for order-of-magnitude analysis instead of binary analysis.
* [pow](/apl/scalar-functions/mathematical-functions/pow): Raises a value to a power. Use it to compute powers of two directly as an alternative to `exp2`.
* [round](/apl/scalar-functions/mathematical-functions/round): Rounds a value. Use it after `log2` to bucket data into binary power groups.
## Other query languages [#other-query-languages]
Splunk SPL doesn't include a built-in `log2()` function. You compute it as `ln(x) / ln(2)` or `log(x) / log(2)`.
```sql Splunk example
| eval log2_duration = ln(req_duration_ms) / ln(2)
```
```kusto APL equivalent
['sample-http-logs']
| extend log2_duration = log2(req_duration_ms)
```
Standard SQL does not define a `LOG2()` function. In SQL Server you compute it as `LOG(x) / LOG(2)`. PostgreSQL offers `LOG(2, x)` for base-2 logarithms.
```sql SQL example
SELECT LOG(req_duration_ms) / LOG(2) AS log2_duration FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend log2_duration = log2(req_duration_ms)
```
---
# loggamma
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/loggamma
Use the `loggamma` function in APL to compute the natural logarithm of the absolute value of the [gamma function](https://en.wikipedia.org/wiki/Gamma_function). This is equivalent to `log(abs(gamma(x)))` but avoids the numeric overflow that occurs when `gamma(x)` itself becomes too large to represent as a floating-point number.
`loggamma` is useful when you work with large inputs to the gamma function in statistical calculations, such as log-likelihood computations, Bayesian modeling, or custom anomaly scoring. Use it whenever `gamma(x)` would overflow to infinity.
## Usage [#usage]
### Syntax [#syntax]
```kusto
loggamma(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | -------------------------------------------------------- |
| `x` | real | Yes | The input value. Must not be zero or a negative integer. |
### Returns [#returns]
The natural logarithm of the absolute value of the gamma function of `x`.
## Example [#example]
Use `loggamma` to compute the log-gamma value of a duration in seconds without risking numeric overflow.
**Query**
```kusto
['sample-http-logs']
| where req_duration_ms > 0
| extend duration_s = req_duration_ms / 1000.0
| extend loggamma_val = loggamma(duration_s)
| project _time, id, req_duration_ms, loggamma_val
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%20%7C%20where%20req_duration_ms%20%3E%200%20%7C%20extend%20duration_s%20%3D%20req_duration_ms%20/%201000.0%20%7C%20extend%20loggamma_val%20%3D%20loggamma%28duration_s%29%20%7C%20project%20_time%2C%20id%2C%20req_duration_ms%2C%20loggamma_val%22%7D)
**Output**
| \_time | id | req\_duration\_ms | loggamma\_val |
| ------------------- | ------ | ----------------- | ------------- |
| 2024-11-14 10:00:00 | user-1 | 1000.0 | 0.0000 |
| 2024-11-14 10:01:00 | user-2 | 2000.0 | 0.0000 |
| 2024-11-14 10:02:00 | user-3 | 10000.0 | 16.1181 |
## List of related functions [#list-of-related-functions]
* [gamma](/apl/scalar-functions/mathematical-functions/gamma): Returns the gamma function directly. Use it for small inputs where overflow isn't a concern.
* [log](/apl/scalar-functions/mathematical-functions/log): Returns the natural logarithm. Use it for general log-transformation without the gamma relationship.
* [exp](/apl/scalar-functions/mathematical-functions/exp): Returns e^x. Use it to exponentiate `loggamma` results back to the original gamma scale when the values are small enough.
* [isfinite](/apl/scalar-functions/mathematical-functions/isfinite): Returns whether a value is finite. Use it to verify that `loggamma` results are valid for downstream calculations.
* [abs](/apl/scalar-functions/mathematical-functions/abs): Returns the absolute value. Note that `loggamma(x)` equals `log(abs(gamma(x)))`, so `abs` is implicitly applied to `gamma(x)` before the log.
## Other query languages [#other-query-languages]
Splunk SPL doesn't include a built-in `loggamma()` function. You typically implement it using external libraries or the Machine Learning Toolkit.
```sql Splunk example
| eval loggamma_val = null()
```
```kusto APL equivalent
['sample-http-logs']
| extend loggamma_val = loggamma(x)
```
Standard SQL does not define a `LOGGAMMA()` function. You need to implement it in application code or through database-specific extensions.
```sql SQL example
-- No standard SQL equivalent; use application code
SELECT NULL AS loggamma_val FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend loggamma_val = loggamma(x)
```
---
# max_of
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/max-of
Use the `max_of` function in APL (Axiom Processing Language) to return the maximum value from a list of scalar expressions. You can use it when you need to compute the maximum of a fixed set of values within each row, rather than across rows like with [aggregation functions](/apl/aggregation-function/statistical-functions). It’s especially useful when the values you want to compare come from different columns or are dynamically calculated within the same row.
Use `max_of` when you want to:
* Compare multiple fields in a single event to determine the highest value.
* Perform element-wise maximum calculations in datasets where values are spread across columns.
* Evaluate conditional values and select the highest one on a per-row basis.
* Ensure a minimum value. For example, `max_of(value, 0)` always returns greater than 0.
## Usage [#usage]
### Syntax [#syntax]
```kusto
max_of(Expr1, Expr2, ..., ExprN)
```
### Parameters [#parameters]
The function takes a comma-separated list of expressions to compare. All values must be of the same type.
### Returns [#returns]
The function returns the maximum value among the input expressions. The type of the result matches the type of the input expressions. All expressions must be of the same or compatible types.
## Use case example [#use-case-example]
You have two data points for the size of HTTP responses: header size and body size. You want to find the maximum of these two values for each event.
**Query**
```kusto
['sample-http-logs']
| extend max_size = max_of(resp_header_size_bytes, resp_body_size_bytes)
| project _time, id, resp_header_size_bytes, resp_body_size_bytes, max_size
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20max_size%20%3D%20max_of\(resp_header_size_bytes%2C%20resp_body_size_bytes\)%20%7C%20project%20_time%2C%20id%2C%20resp_header_size_bytes%2C%20resp_body_size_bytes%2C%20max_size%22%7D)
**Output**
| \_time | id | resp\_header\_size\_bytes | resp\_body\_size\_bytes | max\_size |
| ---------------- | -------------------------------------- | ------------------------- | ----------------------- | --------- |
| May 15, 11:18:53 | 4baad81e-2bca-408f-8a47-092065274037 | 39 B | 2,805 B | 2,805 |
| May 15, 11:18:53 | 05b257c0-8f9d-4b23-8901-c5f288abc30b | 24 B | 988 B | 988 |
| May 15, 11:18:53 | `b34d937c-527a-4a05-b88f-5f3dba645de6` | 72 B | 4,399 B | 4,399 |
| May 15, 11:18:53 | 12a623ec-8b0d-4149-a9eb-d3e18ad5b1cd | 34 B | 1,608 B | 1,608 |
| May 15, 11:18:53 | `d24f22a7-8748-4d3d-a815-ed93081fd5d1` | 84 B | 4,080 B | 4,080 |
| May 15, 11:18:53 | 3cc68be1-bb9a-4199-bf75-62eef59e3a09 | 76 B | 5,117 B | 5,117 |
| May 15, 11:18:53 | `abadabac-a6c0-4ff2-80a1-11143d7c408b` | 41 B | 2,845 B | 2,845 |
## Other query languages [#other-query-languages]
Splunk doesn’t provide a direct function equivalent to `max_of`. However, you can use the `eval` command with nested `if` statements or custom logic to emulate similar functionality on a per-event basis.
```sql Splunk example
eval max_value=if(a > b and a > c, a, if(b > c, b, c))
```
```kusto APL equivalent
extend max_value = max_of(a, b, c)
```
ANSI SQL doesn’t offer a built-in function like `max_of` to compute the maximum across expressions in a single row. Instead, you typically use `GREATEST`, which serves a similar purpose.
```sql SQL example
SELECT GREATEST(a, b, c) AS max_value FROM table
```
```kusto APL equivalent
extend max_value = max_of(a, b, c)
```
---
# min_of
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/min-of
Use the `min_of` function in APL to determine the minimum value among two or more scalar values. The function returns the smallest of its arguments, making it especially useful when you want to compare metrics, constants, or calculated expressions in queries.
You typically use `min_of` when you want to:
* Compare numeric or time-based values across multiple fields or constants.
* Apply conditional logic in summarization or filtering steps.
* Normalize or bound values when computing metrics.
Unlike aggregation functions such as `min()`, which work across rows in a group, `min_of` operates on values within a single row or context.
## Usage [#usage]
### Syntax [#syntax]
```kusto
min_of(Expr1, Expr2, ..., ExprN)
```
### Parameters [#parameters]
The function takes a comma-separated list of expressions to compare. All values must be of the same type.
### Returns [#returns]
The function returns the smallest of the provided values. The type of the return value matches the type of the input arguments.
## Use case example [#use-case-example]
You have two data points for the size of HTTP responses: header size and body size. You want to find the minimum of these two values for each event.
**Query**
```kusto
['sample-http-logs']
| extend min_size = min_of(resp_header_size_bytes, resp_body_size_bytes)
| project _time, id, resp_header_size_bytes, resp_body_size_bytes, min_size
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20min_size%20%3D%20min_of\(resp_header_size_bytes%2C%20resp_body_size_bytes\)%20%7C%20project%20_time%2C%20id%2C%20resp_header_size_bytes%2C%20resp_body_size_bytes%2C%20min_size%22%7D)
**Output**
| \_time | id | resp\_header\_size\_bytes | resp\_body\_size\_bytes | min\_size |
| ---------------- | -------------------------------------- | ------------------------- | ----------------------- | --------- |
| May 15, 11:31:05 | 739b0433-39aa-4891-a5e0-3bde3cb40386 | 41 B | 3,410 B | 41 |
| May 15, 11:31:05 | 3016c439-ea30-454b-858b-06f0a66f44b9 | 53 B | 5,333 B | 53 |
| May 15, 11:31:05 | b26b0a5c-bc73-4693-86ad-be9e0cc767d6 | 60 B | 2,936 B | 60 |
| May 15, 11:31:05 | `8d939423-26ae-43f7-9927-13499e7cc7d3` | 60 B | 2,896 B | 60 |
| May 15, 11:31:05 | 10c37b1a-5639-4c99-a232-c8295e3ce664 | 63 B | 4,871 B | 63 |
| May 15, 11:31:05 | 4aa1821a-6906-4ede-9417-3097efb76b89 | 78 B | 1,729 B | 78 |
| May 15, 11:31:05 | `6325de66-0033-4133-b2f3-99fa70f8c9c0` | 96 B | 4,232 B | 96 |
## Other query languages [#other-query-languages]
In Splunk, you often use the `eval` command with the `min` function to compare multiple values. APL’s `min_of` is similar, but used as a scalar function directly in expressions.
```sql Splunk example
eval smallest=min(field1, field2)
```
```kusto APL equivalent
extend smallest = min_of(field1, field2)
```
In SQL, you typically use `LEAST()` to find the smallest of multiple values. APL’s `min_of` is the equivalent of `LEAST()`.
```sql SQL example
SELECT LEAST(col1, col2, col3) AS min_val FROM table;
```
```kusto APL equivalent
extend min_val = min_of(col1, col2, col3)
```
---
# not
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/not
Use the `not` function in APL to reverse the boolean value of an expression. It returns `true` when the input is `false`, and `false` when the input is `true`.
`not` is useful for inverting filter conditions, flagging events that fail a specific test, and building readable logical expressions. It makes queries easier to understand than using `== false` or `!= true` directly.
## Usage [#usage]
### Syntax [#syntax]
```kusto
not(expr)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------ | ---- | -------- | ---------------------------------- |
| `expr` | bool | Yes | The boolean expression to reverse. |
### Returns [#returns]
`true` if `expr` is `false`. `false` if `expr` is `true`.
## Example [#example]
Use `not` to identify requests using non-standard HTTP methods, which can be a sign of reconnaissance or abuse.
**Query**
```kusto
['sample-http-logs']
| extend is_safe_method = (method == 'GET' or method == 'HEAD')
| where not(is_safe_method)
| project _time, id, status, method, uri
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20is_safe_method%20%3D%20%28method%20%3D%3D%20%27GET%27%20or%20method%20%3D%3D%20%27HEAD%27%29%20%7C%20where%20not%28is_safe_method%29%20%7C%20project%20_time%2C%20id%2C%20status%2C%20method%2C%20uri%22%7D)
**Output**
| \_time | id | status | method | uri |
| ------------------- | ------ | ------ | ------ | ------------ |
| 2024-11-14 10:00:00 | user-9 | 200 | POST | /api/data |
| 2024-11-14 10:01:00 | user-5 | 403 | DELETE | /admin/users |
POST, PUT, DELETE, and other non-GET/HEAD methods appear here. Unexpected DELETE or PUT requests to sensitive endpoints may warrant investigation.
## List of related functions [#list-of-related-functions]
* [isfinite](/apl/scalar-functions/mathematical-functions/isfinite): Returns `true` for finite values. Combine with `not` as `not(isfinite(x))` to filter out invalid numeric results.
* [isinf](/apl/scalar-functions/mathematical-functions/isinf): Returns `true` for infinite values. Use `not(isinf(x))` as an alternative to `isfinite` when NaN values aren't a concern.
* [isnan](/apl/scalar-functions/mathematical-functions/isnan): Returns `true` for NaN. Use `not(isnan(x))` to keep only valid numeric rows.
* [isint](/apl/scalar-functions/mathematical-functions/isint): Returns `true` for integers. Use `not(isint(x))` to keep only non-integer values.
* [sign](/apl/scalar-functions/mathematical-functions/sign): Returns the sign of a value. Use it when you need a numeric result rather than a boolean negation.
## Other query languages [#other-query-languages]
In Splunk SPL, `NOT` is a keyword used in `search` or `where` clauses. In APL, `not()` is a function that wraps a boolean expression and can be used in `extend`, `where`, and `project` operators.
```sql Splunk example
| where NOT status='500'
```
```kusto APL equivalent
['sample-http-logs']
| where not(status == '500')
```
In ANSI SQL, `NOT` is a keyword that negates a boolean expression. In APL, `not()` is a function with the same effect.
```sql SQL example
SELECT * FROM logs WHERE NOT status = '500'
```
```kusto APL equivalent
['sample-http-logs']
| where not(status == '500')
```
---
# pi
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/pi
Use the `pi` function in APL to return the mathematical constant π (pi), approximately equal to 3.14159265358979. The function takes no arguments and always returns the same double-precision floating-point value.
`pi` is most commonly used to construct radian angle values for trigonometric functions. For example, you can encode the hour of day as a cyclic coordinate using `(hour * 2 * pi()) / 24`, or convert between degrees and radians using `radians = degrees * pi() / 180`.
## Usage [#usage]
### Syntax [#syntax]
```kusto
pi()
```
### Parameters [#parameters]
None.
### Returns [#returns]
The double-precision value of π: approximately 3.14159265358979.
## Example [#example]
Use `pi` to encode the hour of day as a cyclic angle for time-based analysis.
**Query**
```kusto
['sample-http-logs']
| extend hour_angle = (hourofday(_time) * 2 * pi()) / 24
| extend sin_hour = sin(hour_angle)
| extend cos_hour = cos(hour_angle)
| project _time, id, sin_hour, cos_hour
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20hour_angle%20%3D%20%28hourofday%28_time%29%20%2A%202%20%2A%20pi%28%29%29%20/%2024%20%7C%20extend%20sin_hour%20%3D%20sin%28hour_angle%29%20%7C%20extend%20cos_hour%20%3D%20cos%28hour_angle%29%20%7C%20project%20_time%2C%20id%2C%20sin_hour%2C%20cos_hour%22%7D)
**Output**
| \_time | id | sin\_hour | cos\_hour |
| ------------------- | ------ | --------- | --------- |
| 2024-11-14 00:00:00 | user-1 | 0.0000 | 1.0000 |
| 2024-11-14 06:00:00 | user-2 | 1.0000 | 0.0000 |
| 2024-11-14 12:00:00 | user-3 | 0.0000 | -1.0000 |
## List of related functions [#list-of-related-functions]
* [sin](/apl/scalar-functions/mathematical-functions/sin): Returns the sine of an angle in radians. Combine with `pi` to encode cyclic signals.
* [cos](/apl/scalar-functions/mathematical-functions/cos): Returns the cosine of an angle in radians. Use together with `sin` and `pi` for cyclic coordinate encoding.
* [radians](/apl/scalar-functions/mathematical-functions/radians): Converts degrees to radians. Use it as an alternative to manually multiplying by `pi() / 180`.
* [degrees](/apl/scalar-functions/mathematical-functions/degrees): Converts radians to degrees. Use it to express results from inverse trig functions in degrees instead of radians.
* [atan2](/apl/scalar-functions/mathematical-functions/atan2): Returns the angle in radians between two coordinates. Its output range is (-π, π], so `pi` is a natural companion.
## Other query languages [#other-query-languages]
In Splunk SPL, `pi()` returns the same constant and works identically.
```sql Splunk example
| eval angle = 2 * pi()
```
```kusto APL equivalent
print angle = 2 * pi()
```
In ANSI SQL, `PI()` is a standard function in SQL Server and PostgreSQL that returns π.
```sql SQL example
SELECT PI() AS pi_value
```
```kusto APL equivalent
print pi_value = pi()
```
---
# pow
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/pow
Use the `pow` function in APL to raise a base value to a given exponent: base^exponent. The function accepts any real base and exponent.
`pow` is useful whenever you need to apply a power function to a metric, such as squaring deviations for variance calculations, computing exponential growth factors, scaling values by a fractional power, or inverting a power transformation. It's more flexible than `exp`, `exp2`, or `exp10` because you can specify any base.
## Usage [#usage]
### Syntax [#syntax]
```kusto
pow(base, exponent)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---------- | ---- | -------- | ---------------------------------- |
| `base` | real | Yes | The base value. |
| `exponent` | real | Yes | The exponent to raise the base to. |
### Returns [#returns]
`base` raised to the power `exponent`: base^exponent.
## Example [#example]
Use `pow` to square the deviation of each request's duration from a 200 ms baseline.
**Query**
```kusto
['sample-http-logs']
| extend deviation = req_duration_ms - 200.0
| extend squared_deviation = pow(deviation, 2)
| project _time, id, req_duration_ms, squared_deviation
| order by squared_deviation desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20deviation%20%3D%20req_duration_ms%20-%20200.0%20%7C%20extend%20squared_deviation%20%3D%20pow%28deviation%2C%202%29%20%7C%20project%20_time%2C%20id%2C%20req_duration_ms%2C%20squared_deviation%20%7C%20order%20by%20squared_deviation%20desc%22%7D)
**Output**
| \_time | id | req\_duration\_ms | squared\_deviation |
| ------------------- | ------ | ----------------- | ------------------ |
| 2024-11-14 10:00:00 | user-1 | 1200.0 | 1000000.0 |
| 2024-11-14 10:01:00 | user-2 | 80.0 | 14400.0 |
| 2024-11-14 10:02:00 | user-3 | 205.0 | 25.0 |
## List of related functions [#list-of-related-functions]
* [sqrt](/apl/scalar-functions/mathematical-functions/sqrt): Returns the square root of a value. This is equivalent to `pow(x, 0.5)` and is more readable for the square-root case.
* [exp](/apl/scalar-functions/mathematical-functions/exp): Returns e^x. Use it when the base is e rather than an arbitrary number.
* [exp2](/apl/scalar-functions/mathematical-functions/exp2): Returns 2^x. Use it when the base is always 2.
* [exp10](/apl/scalar-functions/mathematical-functions/exp10): Returns 10^x. Use it when the base is always 10.
* [log](/apl/scalar-functions/mathematical-functions/log): Returns the natural logarithm. Use it to undo a `pow` transformation in log space.
## Other query languages [#other-query-languages]
In Splunk SPL, `pow(base, exponent)` works identically to APL's `pow`. You can also use the `^` operator for the same purpose.
```sql Splunk example
| eval squared = pow(req_duration_ms, 2)
```
```kusto APL equivalent
['sample-http-logs']
| extend squared = pow(req_duration_ms, 2)
```
In ANSI SQL, `POWER(base, exponent)` provides the same functionality as APL's `pow`.
```sql SQL example
SELECT POWER(req_duration_ms, 2) AS squared FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend squared = pow(req_duration_ms, 2)
```
---
# radians
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/radians
Use the `radians` function in APL to convert an angle from degrees to radians. The conversion formula is `radians = (π / 180) × angle_in_degrees`.
`radians` is useful as a preprocessing step before calling trigonometric functions such as `sin`, `cos`, or `tan`, which all expect their input in radians. If you have angle data in degrees, pass it through `radians` before applying any trigonometric operation.
## Usage [#usage]
### Syntax [#syntax]
```kusto
radians(a)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | -------------------------------- |
| `a` | real | Yes | The angle in degrees to convert. |
### Returns [#returns]
The angle in radians corresponding to the input degree value.
## Example [#example]
Use `radians` to convert an angle in degrees to radians.
**Query**
```kusto
print result = radians(180)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22print%20result%20%3D%20radians%28180%29%22%7D)
**Output**
| result |
| ------ |
| 3.1416 |
## List of related functions [#list-of-related-functions]
* [degrees](/apl/scalar-functions/mathematical-functions/degrees): Converts radians to degrees. Use it for the inverse conversion after trigonometric computations.
* [pi](/apl/scalar-functions/mathematical-functions/pi): Returns the constant π. Use it when you want to compute radian conversions manually instead of using `radians`.
* [sin](/apl/scalar-functions/mathematical-functions/sin): Returns the sine of an angle in radians. Use `radians` to prepare degree inputs before calling `sin`.
* [cos](/apl/scalar-functions/mathematical-functions/cos): Returns the cosine of an angle in radians. Use `radians` to prepare degree inputs before calling `cos`.
* [tan](/apl/scalar-functions/mathematical-functions/tan): Returns the tangent of an angle in radians. Use `radians` to prepare degree inputs before calling `tan`.
## Other query languages [#other-query-languages]
Splunk SPL doesn't include a built-in `radians()` function. You compute the conversion manually by multiplying the degree value by `pi() / 180`.
```sql Splunk example
| eval angle_rad = angle_deg * (pi() / 180)
```
```kusto APL equivalent
['sample-http-logs']
| extend angle_rad = radians(angle_deg)
```
In SQL Server and PostgreSQL, `RADIANS()` is a built-in function with identical semantics to the APL version.
```sql SQL example
SELECT RADIANS(angle_deg) AS angle_rad FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend angle_rad = radians(angle_deg)
```
---
# rand
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/rand
Use the `rand` function in APL to generate pseudo-random numbers. This function is useful when you want to introduce randomness into your queries. For example, to sample a subset of data, generate test data, or simulate probabilistic scenarios.
## Usage [#usage]
### Syntax [#syntax]
```kusto
rand()
rand(range)
```
### Parameters [#parameters]
| Name | Type | Description |
| ------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `range` | integer | Optional: A positive integer that specifies the upper exclusive limit of the range where you want to generate pseudo-random integers. The lower inclusive limit is 0. |
### Returns [#returns]
Without `range`: A real number in the range between 0 (inclusive) and 1 (exclusive). Each call returns a pseudo-random float.
With `range`: An integer in the range between 0 (inclusive) and `range` (exclusive).
## Example [#example]
**Query**
```kusto
print random = rand()
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22print%20random%20%3D%20rand\(\)%22%7D)
**Output**
| \_time | random |
| -------------------- | ------------------ |
| 2024-07-10T14:32:00Z | 0.6324890121902683 |
## Other query languages [#other-query-languages]
In Splunk SPL, the `random()` function returns a pseudo-random integer between 0 and 2^31-1. You often divide this value to produce a float between 0 and 1. In APL, the `rand()` function directly returns a float in the range `[0, 1)`, so there’s no need to divide or scale.
```sql Splunk example
| eval r=random()/2147483647
```
```kusto APL equivalent
print r = rand()
```
ANSI SQL uses the `RAND()` function to generate a float between 0 and 1. However, SQL typically doesn’t generate a new random value for every row unless you call `RAND()` inside a subquery or use it with a specific expression. In APL, `rand()` behaves like a row-level function and produces a new value for each row automatically.
```sql SQL example
SELECT RAND() as r;
```
```kusto APL equivalent
print r = rand()
```
---
# range
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/range
Use the `range` function in APL to create a dynamic array of evenly spaced values. You can generate numeric, datetime, or timespan sequences that increase by a constant step, which defaults to 1 for numbers and 1 hour for time-based types. The function stops once the value exceeds the specified endpoint or the maximum result size.
`range` is useful when you want to produce test values, synthetic sequences, time intervals, or loop-like constructs without relying on input data. It helps you populate arrays that can be expanded or joined with real data for further analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
range(start, stop [, step])
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ----------------------------------- | -------- | ------------------------------------------------------------------------- |
| start | scalar (number, datetime, timespan) | ✓ | First value in the array. |
| stop | scalar (same type as `start`) | ✓ | Upper bound of the array. The last value is less than or equal to `stop`. |
| step | scalar (same type as `start`) | | Difference between values. Defaults to 1 (numeric) or `1h` (time). |
### Returns [#returns]
A dynamic array that includes values starting at `start`, incremented by `step`, up to and including `stop` (if it aligns exactly with a step). The array truncates if it reaches the system limit of 1,048,576 elements.
## Use case examples [#use-case-examples]
Generate an array of durations to help classify HTTP request latencies.
**Query**
```kusto
print r = range(100, 500, 100)
| project r
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22print%20r%20%3D%20range\(100%2C%20500%2C%20100\)%20%7C%20project%20r%22%7D)
**Output**
```json
[
100,
200,
300,
400,
500
]
```
This creates an array of thresholds that you can use to bucket or filter request durations in `['sample-http-logs']`.
Build a set of time intervals to align span activity over a fixed period.
**Query**
```kusto
print intervals = range(datetime(2025-07-29T12:00:00Z), datetime(2025-07-29T13:00:00Z), 15m)
| project intervals
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22print%20intervals%20%3D%20range\(datetime\(2025-07-29T12%3A00%3A00Z\)%2C%20datetime\(2025-07-29T13%3A00%3A00Z\)%2C%2015m\)%20%7C%20project%20intervals%22%7D)
**Output**
```json
[
"2025-07-29T12:00:00Z",
"2025-07-29T12:15:00Z",
"2025-07-29T12:30:00Z",
"2025-07-29T12:45:00Z",
"2025-07-29T13:00:00Z"
]
```
This array helps divide the hour into 15-minute blocks for analyzing activity in `['otel-demo-traces']`.
Create a list of expected hourly intervals to detect missing logs.
**Query**
```kusto
print expected = range(datetime(2025-07-29T00:00:00Z), datetime(2025-07-29T05:00:00Z), 1h)
| project expected
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22print%20expected%20%3D%20range\(datetime\(2025-07-29T00%3A00%3A00Z\)%2C%20datetime\(2025-07-29T05%3A00%3A00Z\)%2C%201h\)%20%7C%20project%20expected%22%7D)
**Output**
```json
[
"2025-07-29T00:00:00Z",
"2025-07-29T01:00:00Z",
"2025-07-29T02:00:00Z",
"2025-07-29T03:00:00Z",
"2025-07-29T04:00:00Z",
"2025-07-29T05:00:00Z"
]
```
This produces an array of expected log collection times for the `['sample-http-logs']` dataset. You can join this with actual data to detect missing records.
## Other query languages [#other-query-languages]
In SPL, generating sequences often involves `makeresults` combined with `streamstats` or manual iteration logic. APL’s `range` function simplifies this by producing arrays of equally spaced values directly.
```sql Splunk example
| makeresults count=10
| streamstats count as x
```
```kusto APL equivalent
print r = range(1, 10, 1)
```
In ANSI SQL, generating a series of numbers usually involves recursive CTEs. APL’s `range` is more concise and efficient for creating sequences without writing complex recursion logic.
```sql SQL example
WITH RECURSIVE seq AS (
SELECT 1 AS x
UNION ALL
SELECT x + 1 FROM seq WHERE x < 10
)
SELECT * FROM seq;
```
```kusto APL equivalent
print r = range(1, 10, 1)
```
---
# round
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/round
Use the `round` function in APL to round a numeric value to a specified number of decimal places. When no precision is provided, the function rounds to the nearest integer.
`round` is useful for reducing noise in aggregated metrics, normalizing reported values to a readable precision, and comparing floating-point results that are intended to be equal but differ by tiny rounding errors. For example, you can round average latencies to two decimal places for display, or round computed percentages to integers for bucketing.
## Usage [#usage]
### Syntax [#syntax]
```kusto
round(source [, Precision])
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----------- | ---- | -------- | ---------------------------------------------------- |
| `source` | real | Yes | The value to round. |
| `Precision` | int | No | Number of decimal places to round to. Defaults to 0. |
### Returns [#returns]
The `source` value rounded to the specified number of decimal places.
## Example [#example]
Use `round` to round the average request duration to two decimal places per hour.
**Query**
```kusto
['sample-http-logs']
| summarize avg_duration = avg(req_duration_ms) by bin(_time, 1h)
| extend avg_rounded = round(avg_duration, 2)
| project _time, avg_duration, avg_rounded
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%20%7C%20summarize%20avg_duration%20%3D%20avg%28req_duration_ms%29%20by%20bin%28_time%2C%201h%29%20%7C%20extend%20avg_rounded%20%3D%20round%28avg_duration%2C%202%29%20%7C%20project%20_time%2C%20avg_duration%2C%20avg_rounded%22%7D)
**Output**
| \_time | avg\_duration | avg\_rounded |
| ------------------- | ------------- | ------------ |
| 2024-11-14 10:00:00 | 123.4567 | 123.46 |
| 2024-11-14 11:00:00 | 98.1234 | 98.12 |
| 2024-11-14 12:00:00 | 200.0001 | 200.00 |
## List of related functions [#list-of-related-functions]
* [abs](/apl/scalar-functions/mathematical-functions/abs): Returns the absolute value. Use it to remove sign before rounding if direction is irrelevant.
* [sign](/apl/scalar-functions/mathematical-functions/sign): Returns the sign of a value. Use it to check direction after rounding.
* [log10](/apl/scalar-functions/mathematical-functions/log10): Returns the base-10 logarithm. Use `round(log10(x))` to bucket values by integer order of magnitude.
* [pow](/apl/scalar-functions/mathematical-functions/pow): Raises a value to a power. Use it to scale values before rounding when working with non-unit precision.
* [sqrt](/apl/scalar-functions/mathematical-functions/sqrt): Returns the square root. Combine with `round` to produce a rounded standard deviation or root value.
## Other query languages [#other-query-languages]
In Splunk SPL, `round(X, Y)` rounds `X` to `Y` decimal places, just like APL's `round`.
```sql Splunk example
| eval avg_duration_rounded = round(avg_duration, 2)
```
```kusto APL equivalent
['sample-http-logs']
| extend avg_duration_rounded = round(avg_duration, 2)
```
In ANSI SQL, `ROUND(x, precision)` works the same as APL's `round`.
```sql SQL example
SELECT ROUND(avg_duration, 2) AS avg_duration_rounded FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend avg_duration_rounded = round(avg_duration, 2)
```
---
# set_difference
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/set-difference
Use the `set_difference` function in APL to compute the distinct elements in one array that aren’t present in another. This function helps you filter out shared values between two arrays, producing a new array that includes only the unique values from the first input array.
Use `set_difference` when you need to identify new or missing elements, such as:
* Users who visited today but not yesterday.
* Error codes that occurred in one region but not another.
* Service calls that appear in staging but not production.
## Usage [#usage]
### Syntax [#syntax]
```kusto
set_difference(Array1, Array2)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ----- | ---------------------------------------------------- |
| `Array1` | array | The array to subtract from. |
| `Array2` | array | The array containing values to remove from `Array1`. |
### Returns [#returns]
An array that includes all values from `Array1` that aren’t present in `Array2`. The result doesn’t include duplicates.
## Example [#example]
Use `set_difference` to return the difference between two arrays.
**Query**
```kusto
['sample-http-logs']
| extend difference = set_difference(dynamic([1, 2, 3]), dynamic([2, 3, 4, 5]))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20difference%20%3D%20set_difference\(dynamic\(%5B1%2C%202%2C%203%5D\)%2C%20dynamic\(%5B2%2C%203%2C%204%2C%205%5D\)\)%22%7D)
**Output**
| \_time | difference |
| ---------------- | ---------- |
| May 22, 11:42:52 | \[5, 1, 4] |
## List of related functions [#list-of-related-functions]
* [set\_has\_element](/apl/scalar-functions/mathematical-functions/set-has-element): Tests whether a set contains a specific value. Prefer it when you only need a Boolean result.
* [set\_union](/apl/scalar-functions/mathematical-functions/set-union): Returns the union of two or more sets. Use it when you need any element that appears in at least one set instead of every set.
## Other query languages [#other-query-languages]
In Splunk SPL, similar logic often uses the `setdiff` function from the `mv` (multivalue) function family. APL’s `set_difference` behaves similarly, returning values that are only in the first multivalue field.
```sql Splunk example
| eval a=mvappend("a", "b", "c"), b=mvappend("b", "c")
| eval diff=mvfilter(NOT match(a, b))
```
```kusto APL equivalent
print a=dynamic(['a', 'b', 'c']), b=dynamic(['b', 'c'])
| extend diff=set_difference(a, b)
```
ANSI SQL doesn’t support array operations directly, but you can emulate set difference with `EXCEPT` when working with rows, not arrays. APL provides native array functions like `set_difference` for this purpose.
```sql SQL example
SELECT value FROM array1
EXCEPT
SELECT value FROM array2;
```
```kusto APL equivalent
print a=dynamic(['a', 'b', 'c']), b=dynamic(['b', 'c'])
| extend diff=set_difference(a, b)
```
---
# set_has_element
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/set-has-element
`set_has_element` returns true when a dynamic array contains a specific element and false when it doesn’t. Use it to perform fast membership checks on values that you have already aggregated into a set with functions such as `make_set`.
## Usage [#usage]
### Syntax [#syntax]
```kusto
set_has_element(set, value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ------- | ------- | ------------------------------------------------------------------------------------------ |
| `set` | dynamic | The array to search. |
| `value` | scalar | The element to look for. Accepts `long`, `real`, `datetime`, `timespan`, `string`, `bool`. |
### Returns [#returns]
A `bool` that’s true when `value` exists in `set` and false otherwise.
## Example [#example]
Use `set_has_element` to determine if a set contains a specific value.
**Query**
```kusto
['sample-http-logs']
| extend hasElement = set_has_element(dynamic([1, 2, 3]), 2)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20hasElement%20%3D%20set_has_element\(dynamic\(%5B1%2C%202%2C%203%5D\)%2C%202\)%22%7D)
**Output**
| \_time | hasElement |
| ---------------- | ---------- |
| May 22, 11:42:52 | true |
## List of related functions [#list-of-related-functions]
* [set\_difference](/apl/scalar-functions/mathematical-functions/set-difference): Returns elements in the first array that aren’t in the second. Use it to find exclusions.
* [set\_union](/apl/scalar-functions/mathematical-functions/set-union): Returns the union of two or more sets. Use it when you need any element that appears in at least one set instead of every set.
## Other query languages [#other-query-languages]
In Splunk, you usually call `in` for scalar membership or use multivalue functions such as `mvfind` for arrays. `set_has_element` plays the role of those helpers after you build a multivalue field with `stats values`.
```sql Splunk example
index=web
| stats values(uri) AS uris BY id
| where "/checkout" in uris
```
```kusto APL equivalent
['sample-http-logs']
| summarize uris=make_set(uri) by id
| where set_has_element(uris, '/checkout')
```
Standard SQL has no built-in array type, but dialects that implement arrays (for example PostgreSQL) use the `ANY` or `member of` operators. `set_has_element` is the APL counterpart and is applied after you build an array with `ARRAY_AGG` equivalents such as `make_set`.
```sql SQL example
SELECT id
FROM sample_http_logs
GROUP BY id
HAVING 'US' = ANY(ARRAY_AGG(country));
```
```kusto APL equivalent
['sample-http-logs']
| summarize countries=make_set(['geo.country']) by id
| where set_has_element(countries, 'US')
```
---
# set_intersect
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/set-intersect
Use the `set_intersect` function in APL to find common elements between two dynamic arrays. This function returns a new array that contains only the elements that appear in both input arrays, preserving the order from the first array and eliminating duplicates.
You can use `set_intersect` when you need to compare sets of values—for example, to find users who accessed two different URLs, or to identify traces that passed through multiple services. This function is especially useful for working with dynamic fields generated during aggregations or transformations.
## Usage [#usage]
### Syntax [#syntax]
```kusto
set_intersect(Array1, Array2)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ------- | ---------------------------- |
| `Array1` | dynamic | The first array to compare. |
| `Array2` | dynamic | The second array to compare. |
### Returns [#returns]
A dynamic array containing elements that exist in both `Array1` and `Array2`, in the order they appear in `Array1`, with duplicates removed.
## Example [#example]
Use `set_intersect` to return the intersection of two arrays.
**Query**
```kusto
['sample-http-logs']
| extend intersect = set_intersect(dynamic([1, 2, 3]), dynamic([2, 3, 4, 5]))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20intersect%20%3D%20set_intersect\(dynamic\(%5B1%2C%202%2C%203%5D\)%2C%20dynamic\(%5B2%2C%203%2C%204%2C%205%5D\)\)%22%7D)
**Output**
| \_time | together |
| ---------------- | -------- |
| May 22, 11:42:52 | \[2, 3] |
## List of related functions [#list-of-related-functions]
* [set\_difference](/apl/scalar-functions/mathematical-functions/set-difference): Returns elements in the first array that aren’t in the second. Use it to find exclusions.
* [set\_has\_element](/apl/scalar-functions/mathematical-functions/set-has-element): Tests whether a set contains a specific value. Prefer it when you only need a Boolean result.
* [set\_union](/apl/scalar-functions/mathematical-functions/set-union): Returns the union of two or more sets. Use it when you need any element that appears in at least one set instead of every set.
## Other query languages [#other-query-languages]
Splunk SPL doesn’t have a direct equivalent to `set_intersect`, but you can achieve similar functionality using `mvfilter` with conditions based on a lookup or manually defined set. APL simplifies this process by offering a built-in array intersection function.
```sql Splunk example
| eval A=split("apple,banana,cherry", ",")
| eval B=split("banana,cherry,dragonfruit", ",")
| eval C=mvfilter(match(A, B))
```
```kusto APL equivalent
print A=dynamic(['apple', 'banana', 'cherry']), B=dynamic(['banana', 'cherry', 'dragonfruit'])
| extend C = set_intersect(A, B)
```
ANSI SQL doesn’t natively support array data types or set operations over arrays. To perform an intersection, you usually need to normalize the arrays using `UNNEST` or `JOIN`, which can be verbose. In APL, `set_intersect` performs this in a single step.
```sql SQL example
-- Using PostgreSQL syntax
SELECT ARRAY(
SELECT unnest(array['apple','banana','cherry'])
INTERSECT
SELECT unnest(array['banana','cherry','dragonfruit'])
);
```
```kusto APL equivalent
print A=dynamic(['apple', 'banana', 'cherry']), B=dynamic(['banana', 'cherry', 'dragonfruit'])
| extend C = set_intersect(A, B)
```
---
# set_union
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/set-union
Use the `set_union` function in APL to combine two dynamic arrays into one, returning a new array that includes all distinct elements from both. The order of elements in the result isn’t guaranteed and may differ from the original input arrays.
You can use `set_union` when you need to merge two arrays and eliminate duplicates. It’s especially useful in scenarios where you need to perform set-based logic, such as comparing user activity across multiple sources, correlating IPs from different datasets, or combining traces or log attributes from different events.
## Usage [#usage]
### Syntax [#syntax]
```kusto
set_union(Array1, Array2)
```
### Parameters [#parameters]
| Name | Type | Description |
| ------ | ------- | -------------------------- |
| Array1 | dynamic | The first array to merge. |
| Array2 | dynamic | The second array to merge. |
### Returns [#returns]
A dynamic array that contains the distinct elements of both input arrays.
## Example [#example]
Use `set_union` to return the union of two arrays.
**Query**
```kusto
['sample-http-logs']
| extend together = set_union(dynamic([1, 2, 3]), dynamic([2, 3, 4, 5]))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20together%20%3D%20set_union\(dynamic\(%5B1%2C%202%2C%203%5D\)%2C%20dynamic\(%5B2%2C%203%2C%204%2C%205%5D\)\)%22%7D)
**Output**
| \_time | together |
| ---------------- | ----------------- |
| May 22, 11:42:52 | \[1, 2, 3, 4, 5 ] |
## List of related functions [#list-of-related-functions]
* [set\_difference](/apl/scalar-functions/mathematical-functions/set-difference): Returns elements in the first array that aren’t in the second. Use it to find exclusions.
* [set\_has\_element](/apl/scalar-functions/mathematical-functions/set-has-element): Tests whether a set contains a specific value. Prefer it when you only need a Boolean result.
* [set\_union](/apl/scalar-functions/mathematical-functions/set-union): Returns the union of two or more sets. Use it when you need any element that appears in at least one set instead of every set.
## Other query languages [#other-query-languages]
APL’s `set_union` works similarly to using `mvappend` followed by `mvdedup` in SPL. While SPL stores multivalue fields and uses field-based manipulation, APL focuses on dynamic arrays. You need to explicitly apply set logic in APL using functions like `set_union`.
```sql Splunk example
| eval result=mvappend(array1, array2)
| eval result=mvdedup(result)
```
```kusto APL equivalent
extend result = set_union(array1, array2)
```
Standard SQL doesn’t support arrays as first-class types or set functions like `set_union`. However, conceptually, `set_union` behaves like applying `UNION` between two subqueries that return one column each, followed by a `DISTINCT`.
```sql SQL example
SELECT value FROM (
SELECT value FROM table1
UNION
SELECT value FROM table2
)
```
```kusto APL equivalent
extend result = set_union(array1, array2)
```
---
# sign
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/sign
Use the `sign` function in APL to return the sign of a numeric expression: +1 for positive values, 0 for zero, and -1 for negative values.
`sign` is useful when you care about the direction of a value rather than its magnitude. For example, you can use `sign` to classify whether a request latency is above or below a baseline, detect whether a metric is trending up or down, or encode deviations as ternary labels for downstream classification.
## Usage [#usage]
### Syntax [#syntax]
```kusto
sign(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | -------------- |
| `x` | real | Yes | A real number. |
### Returns [#returns]
* `+1` if `x` is positive.
* `0` if `x` is zero.
* `-1` if `x` is negative.
## Example [#example]
Use `sign` to classify each request as faster (-1), on-target (0), or slower (+1) than a 200 ms baseline.
**Query**
```kusto
['sample-http-logs']
| extend deviation = req_duration_ms - 200.0
| extend deviation_sign = sign(deviation)
| project _time, id, req_duration_ms, deviation_sign
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20deviation%20%3D%20req_duration_ms%20-%20200.0%20%7C%20extend%20deviation_sign%20%3D%20sign%28deviation%29%20%7C%20project%20_time%2C%20id%2C%20req_duration_ms%2C%20deviation_sign%22%7D)
**Output**
| \_time | id | req\_duration\_ms | deviation\_sign |
| ------------------- | ------ | ----------------- | --------------- |
| 2024-11-14 10:00:00 | user-1 | 450.0 | 1 |
| 2024-11-14 10:01:00 | user-2 | 80.0 | -1 |
| 2024-11-14 10:02:00 | user-3 | 200.0 | 0 |
## List of related functions [#list-of-related-functions]
* [abs](/apl/scalar-functions/mathematical-functions/abs): Returns the absolute value. Use it to get magnitude when direction from `sign` isn't enough.
* [round](/apl/scalar-functions/mathematical-functions/round): Rounds a value. Use it to normalize values before comparing signs.
* [not](/apl/scalar-functions/mathematical-functions/not): Reverses a boolean. Use it alongside `sign` for boolean-style conditions on direction.
* [pow](/apl/scalar-functions/mathematical-functions/pow): Raises a value to a power. Use `pow(x, 2)` together with `sign(x)` to keep direction while amplifying magnitude.
* [sqrt](/apl/scalar-functions/mathematical-functions/sqrt): Returns the square root. Use it to compute magnitude, and `sign` to determine direction, when you need a signed root.
## Other query languages [#other-query-languages]
Splunk SPL uses `signum()` rather than `sign()`. The semantics are identical: it returns +1, 0, or -1.
```sql Splunk example
| eval direction = signum(deviation)
```
```kusto APL equivalent
['sample-http-logs']
| extend direction = sign(deviation)
```
In ANSI SQL, `SIGN()` works the same as in APL: it returns +1, 0, or -1 for positive, zero, and negative inputs respectively.
```sql SQL example
SELECT SIGN(deviation) AS direction FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend direction = sign(deviation)
```
---
# sin
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/sin
Use the `sin` function in APL to compute the sine of an angle expressed in radians. The function accepts any real number and returns a value in the range \[-1, 1].
`sin` is most commonly used in observability to encode time-of-day or other periodic signals as cyclic coordinates. When combined with `cos`, it produces a two-dimensional representation of periodic phenomena that preserves circular distance. For example, you can encode hour-of-day cyclically so that hour 23 and hour 0 are treated as adjacent.
## Usage [#usage]
### Syntax [#syntax]
```kusto
sin(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | --------------------- |
| `x` | real | Yes | The angle in radians. |
### Returns [#returns]
The sine of `x`, a real number in the range \[-1, 1].
## Example [#example]
Use `sin` to compute the sine of an angle in radians.
**Query**
```kusto
print result = sin(pi() / 2)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22print%20result%20%3D%20sin%28pi%28%29%20/%202%29%22%7D)
**Output**
| result |
| ------ |
| 1 |
## List of related functions [#list-of-related-functions]
* [cos](/apl/scalar-functions/mathematical-functions/cos): Returns the cosine. Use `sin` and `cos` together to produce a two-dimensional cyclic encoding of angles.
* [tan](/apl/scalar-functions/mathematical-functions/tan): Returns the tangent. Use it when you need the ratio of sine to cosine.
* [asin](/apl/scalar-functions/mathematical-functions/asin): Returns the arc sine. Use it as the inverse of `sin`.
* [pi](/apl/scalar-functions/mathematical-functions/pi): Returns the constant π. Use it to construct radian values before calling `sin`.
* [radians](/apl/scalar-functions/mathematical-functions/radians): Converts degrees to radians. Use it to prepare degree inputs before calling `sin`.
## Other query languages [#other-query-languages]
In Splunk SPL, `sin()` takes an angle in radians and returns the sine, just like APL.
```sql Splunk example
| eval sin_val = sin(angle_rad)
```
```kusto APL equivalent
['sample-http-logs']
| extend sin_val = sin(angle_rad)
```
In ANSI SQL, `SIN()` is a standard function with identical semantics.
```sql SQL example
SELECT SIN(angle_rad) AS sin_val FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend sin_val = sin(angle_rad)
```
---
# sqrt
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/sqrt
Use the `sqrt` function in APL to compute the square root of a non-negative numeric value.
`sqrt` is useful whenever you want to compress large numeric ranges, compute root-mean-square values, normalize metrics, or undo a squared transformation. It's equivalent to `pow(x, 0.5)` but is more concise for the square-root case.
If the input is negative, `sqrt` returns NaN (Not a Number). APL represents NaN as `null` in query results. Use [isnan](/apl/scalar-functions/mathematical-functions/isnan) to filter these values before aggregating or charting.
## Usage [#usage]
### Syntax [#syntax]
```kusto
sqrt(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------------------------------------------------------------------------------- |
| `x` | real | Yes | The value to compute the square root of. Must be non-negative for a defined result. |
### Returns [#returns]
Returns the square root of `x`.
If `x` is negative, the function returns NaN, which APL represents as `null` in query results. Use [isnan](/apl/scalar-functions/mathematical-functions/isnan) to check for this condition.
## Examples [#examples]
### Compute the square root of request duration [#compute-the-square-root-of-request-duration]
Use `sqrt` to normalize request durations by taking their square root, compressing the range of large values.
**Query**
```kusto
['sample-http-logs']
| extend root_duration = sqrt(req_duration_ms)
| project _time, id, req_duration_ms, root_duration
| order by root_duration desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20root_duration%20%3D%20sqrt\(req_duration_ms\)%20%7C%20project%20_time%2C%20id%2C%20req_duration_ms%2C%20root_duration%20%7C%20order%20by%20root_duration%20desc%22%7D)
**Output**
| \_time | id | req\_duration\_ms | root\_duration |
| ------------------- | ------ | ----------------- | -------------- |
| 2024-11-14 10:00:00 | user-1 | 1600.0 | 40.0 |
| 2024-11-14 10:01:00 | user-2 | 400.0 | 20.0 |
| 2024-11-14 10:02:00 | user-3 | 25.0 | 5.0 |
### Detect NaN from negative inputs [#detect-nan-from-negative-inputs]
When input values are negative, `sqrt` returns NaN. APL displays NaN as `null`. Use `isnan` to identify and handle these rows.
**Query**
```kusto
['sample-http-logs']
| extend shifted = req_duration_ms - 500
| extend root_shifted = sqrt(shifted)
| extend is_invalid = isnan(root_shifted)
| project _time, id, shifted, root_shifted, is_invalid
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20shifted%20%3D%20req_duration_ms%20-%20500%20%7C%20extend%20root_shifted%20%3D%20sqrt\(shifted\)%20%7C%20extend%20is_invalid%20%3D%20isnan\(root_shifted\)%20%7C%20project%20_time%2C%20id%2C%20shifted%2C%20root_shifted%2C%20is_invalid%22%7D)
**Output**
| \_time | id | shifted | root\_shifted | is\_invalid |
| ------------------- | ------ | ------- | ------------- | ----------- |
| 2024-11-14 10:00:00 | user-1 | 700.0 | 26.46 | false |
| 2024-11-14 10:01:00 | user-2 | -200.0 | null | true |
| 2024-11-14 10:03:00 | user-3 | -50.0 | null | true |
## List of related functions [#list-of-related-functions]
* [pow](/apl/scalar-functions/mathematical-functions/pow): Raises a value to an arbitrary power. `sqrt(x)` is equivalent to `pow(x, 0.5)`.
* [isnan](/apl/scalar-functions/mathematical-functions/isnan): Returns `true` when a value is NaN. Use it to detect `null` results from `sqrt` on negative inputs.
* [abs](/apl/scalar-functions/mathematical-functions/abs): Returns the absolute value. Use it to ensure inputs are non-negative before passing them to `sqrt`.
* [exp](/apl/scalar-functions/mathematical-functions/exp): Returns e^x. Use it when the inverse operation of `log` is needed rather than a root.
* [log](/apl/scalar-functions/mathematical-functions/log): Returns the natural logarithm. Use it alongside `sqrt` when working in log space.
## Other query languages [#other-query-languages]
In Splunk SPL, `sqrt()` works identically: it takes a single numeric argument and returns its square root.
```sql Splunk example
| eval root_duration = sqrt(req_duration_ms)
```
```kusto APL equivalent
['sample-http-logs']
| extend root_duration = sqrt(req_duration_ms)
```
In ANSI SQL, `SQRT()` is a standard built-in function with the same semantics as in APL.
```sql SQL example
SELECT SQRT(req_duration_ms) AS root_duration FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend root_duration = sqrt(req_duration_ms)
```
---
# tan
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/tan
Use the `tan` function in APL to compute the tangent of an angle expressed in radians. The tangent is defined as the ratio of the sine to the cosine: `tan(x) = sin(x) / cos(x)`. The function is undefined at odd multiples of π/2, where the cosine is zero.
`tan` is useful when you work with angular data or want to convert a ratio into an angle. In observability contexts, it can be used alongside `sin` and `cos` to encode periodic signals or compute angular features for cyclic analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
tan(x)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ---- | -------- | --------------------- |
| `x` | real | Yes | The angle in radians. |
### Returns [#returns]
The tangent of `x`. The result is unbounded, approaching ±∞ near odd multiples of π/2.
## Example [#example]
Use `tan` to compute the tangent of an angle in radians.
**Query**
```kusto
print result = tan(pi() / 4)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%20%22print%20result%20%3D%20tan%28pi%28%29%20/%204%29%22%7D)
**Output**
| result |
| ------ |
| 1.0000 |
## List of related functions [#list-of-related-functions]
* [sin](/apl/scalar-functions/mathematical-functions/sin): Returns the sine. Use `sin` and `cos` together for bounded cyclic encoding instead of `tan`, which is unbounded.
* [cos](/apl/scalar-functions/mathematical-functions/cos): Returns the cosine. It's the denominator in `tan(x) = sin(x) / cos(x)`.
* [cot](/apl/scalar-functions/mathematical-functions/cot): Returns the cotangent, the reciprocal of `tan`. Use it when you need the inverse ratio.
* [atan](/apl/scalar-functions/mathematical-functions/atan): Returns the arc tangent. Use it as the inverse of `tan`.
* [isfinite](/apl/scalar-functions/mathematical-functions/isfinite): Returns whether a value is finite. Use it to filter out `tan` overflow values near π/2 poles.
## Other query languages [#other-query-languages]
In Splunk SPL, `tan()` takes an angle in radians and returns the tangent, just like APL.
```sql Splunk example
| eval tan_val = tan(angle_rad)
```
```kusto APL equivalent
['sample-http-logs']
| extend tan_val = tan(angle_rad)
```
In ANSI SQL, `TAN()` is a standard function with identical behavior.
```sql SQL example
SELECT TAN(angle_rad) AS tan_val FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend tan_val = tan(angle_rad)
```
---
# find_pair
Source: https://axiom.co/docs/apl/scalar-functions/pair-functions/find-pair
Use the `find_pair` function in APL to search an array of key-value pairs and find the first pair that matches specified key and value patterns. This function combines pattern matching with pair extraction, making it easy to locate specific pairs in collections of metadata or tags.
You use `find_pair` when working with arrays of pairs (such as tags, labels, or metadata) where you need to find a specific pair based on pattern matching. This is particularly useful in log analysis, OpenTelemetry traces with custom attributes, and any scenario where data is stored as key-value pair arrays.
## Usage [#usage]
### Syntax [#syntax]
```kusto
find_pair(array, key_pattern, value_pattern)
find_pair(array, key_pattern, value_pattern, separator)
```
### Parameters [#parameters]
| Name | Type | Description |
| --------------- | --------- | ------------------------------------------------------------------------------- |
| `array` | `dynamic` | An array of strings representing key-value pairs to search. |
| `key_pattern` | `string` | A wildcard pattern to match against pair keys. Use `*` for wildcard matching. |
| `value_pattern` | `string` | A wildcard pattern to match against pair values. Use `*` for wildcard matching. |
| `separator` | `string` | (Optional) The separator between keys and values in the pairs. Defaults to `:`. |
### Returns [#returns]
A `dynamic` object representing the first matched pair, with `key`, `value` and `separator` properties. Returns `null` if no matching pair is found.
## Example [#example]
Use `find_pair` to extract specific metadata from HTTP logs stored as tag arrays.
**Query**
```kusto
['sample-http-logs']
| extend tags = dynamic(['server:web01', 'env:production', 'region:us-west'])
| extend server_tag = find_pair(tags, 'server', '*')
| project _time, uri, tags, server_tag
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20tags%20%3D%20dynamic\(%5B'server%3Aweb01'%2C%20'env%3Aproduction'%2C%20'region%3Aus-west'%5D\)%20%7C%20extend%20server_tag%20%3D%20find_pair\(tags%2C%20'server'%2C%20'*'\)%20%7C%20project%20_time%2C%20uri%2C%20tags%2C%20server_tag%20%7C%20take%205%22%7D)
**Output**
| \_time | uri | tags | server\_tag |
| ------------------- | --------- | ------------------------------------------------------ | ------------------------------------------------------- |
| 2025-05-26 08:15:30 | /api/user | `['server:web01', 'env:production', 'region:us-west']` | `{"separator": ":", "value": "web01", "key": "server"}` |
| 2025-05-26 08:16:45 | /api/data | `['server:web01', 'env:production', 'region:us-west']` | `{"separator": ":", "value": "web01", "key": "server"}` |
This query searches tag arrays for server information and extracts the matching pair, making it easy to filter or group by server tags.
## List of related functions [#list-of-related-functions]
* [parse\_pair](/apl/scalar-functions/pair-functions#parse-pair): Use `parse_pair` to parse a single pair string into key and value. Use `find_pair` to search an array of pairs.
* [pair](/apl/scalar-functions/pair-functions#pair): Use `pair` to create a pair string from a key and value. Use `find_pair` to locate existing pairs in arrays.
* [array\_index\_of](/apl/scalar-functions/array-functions/array-index-of): Use `array_index_of` for exact match searches in arrays. Use `find_pair` for pattern-based pair matching.
* [extract](/apl/scalar-functions/string-functions#extract): Use `extract` for regex-based extraction from single strings. Use `find_pair` for structured pair searching in arrays.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically iterate through multi-value fields using `mvfind` or use `spath` for JSON data. APL's `find_pair` provides a specialized function for finding key-value pairs with pattern matching.
```sql Splunk example
| eval found_tag=mvfind(tags, 'host=server.*')
```
```kusto APL equivalent
['sample-http-logs']
| extend tags = dynamic(['host:server1', 'env:prod', 'region:us-west'])
| extend found = find_pair(tags, 'host', 'server*')
```
In ANSI SQL, you typically use `JSON_EXTRACT` or array functions with `LIKE` patterns to search arrays. APL's `find_pair` provides a more direct approach for pair-based searches.
```sql SQL example
SELECT *
FROM logs
WHERE JSON_EXTRACT(tags, '$[*].key') LIKE 'host%'
```
```kusto APL equivalent
['sample-http-logs']
| extend tags = dynamic(['host:server1', 'env:prod'])
| extend found = find_pair(tags, 'host*', '*')
```
---
# pair
Source: https://axiom.co/docs/apl/scalar-functions/pair-functions/pair
Use the `pair` function to create a dynamic object representing a key-value pair from separate key and value components. This function is useful for constructing structured pair objects that you can use with functions like `find_pair` to search arrays of pairs.
Use `pair` when you need to programmatically build key-value pair objects for filtering or matching against pair arrays in your logs. The function returns a dynamic object with `key`, `value`, and `separator` properties.
## Usage [#usage]
### Syntax [#syntax]
```kusto
pair(key, value, [separator])
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----------- | -------- | -------- | ----------------------------------------------------------- |
| `key` | `string` | Required | The key component of the pair. |
| `value` | `string` | Required | The value component of the pair. |
| `separator` | `string` | Optional | The separator to store in the pair object. Defaults to `:`. |
### Returns [#returns]
A dynamic object with the following properties:
* `key`: The key component of the pair.
* `value`: The value component of the pair.
* `separator`: The separator used in the pair.
## Example [#example]
Create pair objects to represent request metadata.
**Query**
```kusto
['sample-http-logs']
| extend method_pair = pair('method', method)
| project _time, uri, method_pair
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20method_pair%20%3D%20pair\('method'%2C%20method\)%20%7C%20project%20_time%2C%20uri%2C%20method_pair%20%7C%20take%203%22%7D)
**Output**
| \_time | uri | method\_pair |
| ------------------- | ----------------------- | ------------------------------------------------------ |
| 2025-01-29 10:48:08 | /api/v1/textdata/change | `{"key": "method", "separator": ":", "value": "GET"}` |
| 2025-01-29 10:48:07 | /api/v1/sell/bucket | `{"key": "method", "separator": ":", "value": "PUT"}` |
| 2025-01-29 10:48:06 | /api/v1/user/notify | `{"key": "method", "separator": ":", "value": "POST"}` |
This query creates pair objects from request fields, storing both the key name and value in a structured format.
## List of related functions [#list-of-related-functions]
* [parse\_pair](/apl/scalar-functions/pair-functions/parse-pair): Parses a pair string into a dynamic object with key and value properties. Use `pair` to create pair objects directly from components.
* [find\_pair](/apl/scalar-functions/pair-functions/find-pair): Searches an array of pairs for a matching key-value pattern. Use `pair` to construct pair objects for comparison.
* [bag\_pack](/apl/scalar-functions/array-functions/bag-pack): Creates a dynamic property bag from key-value pairs. Use `pair` when you specifically need the pair object structure with separator.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically work with key-value pairs as strings. APL's `pair` function creates a structured object instead, which you can use for pattern matching with `find_pair`.
```sql Splunk example
| eval tag = host . ":" . value
```
```kusto APL equivalent
['sample-http-logs']
| extend tag = pair('host', 'server1')
```
In ANSI SQL, you use `CONCAT` to build key-value strings or JSON functions to create objects. APL's `pair` function creates a structured dynamic object directly.
```sql SQL example
SELECT JSON_OBJECT('key', key_col, 'value', value_col) AS tag FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend tag = pair('host', 'server1')
```
---
# parse_pair
Source: https://axiom.co/docs/apl/scalar-functions/pair-functions/parse-pair
Use the `parse_pair` function to parse a string containing a key-value pair into its constituent key and value components. This function is useful when you need to extract structured data from strings that follow a key-value format, such as tags, labels, or configuration entries.
Use `parse_pair` when you have strings like `host:server1` or `env=production` and need to access the key or value individually for filtering, grouping, or analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
parse_pair(pair_string, [separator])
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------------- | -------- | -------- | --------------------------------------------------------- |
| `pair_string` | `string` | Required | The string containing the key-value pair to parse. |
| `separator` | `string` | Optional | The separator between the key and value. Defaults to `:`. |
### Returns [#returns]
A dynamic object with the following properties:
* `key`: The extracted key portion of the pair.
* `value`: The extracted value portion of the pair.
* `separator`: The separator used in the pair.
If the separator isn't found in the input string, the function returns a pair with the entire input as the `value` and an empty `key`.
## Example [#example]
Extract and analyze tag components from HTTP request metadata.
**Query**
```kusto
['sample-http-logs']
| extend tag_string = strcat('method:', method)
| extend parsed = parse_pair(tag_string)
| project _time, uri, tag_string, parsed
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20tag_string%20%3D%20strcat\('method%3A'%2C%20method\)%20%7C%20extend%20parsed%20%3D%20parse_pair\(tag_string\)%20%7C%20project%20_time%2C%20uri%2C%20tag_string%2C%20parsed%20%7C%20take%205%22%7D)
**Output**
| \_time | uri | tag\_string | parsed |
| ------------------- | ---------- | ----------- | ------------------------------------------------------ |
| 2025-01-29 08:15:30 | /api/user | method:GET | `{"key": "method", "separator": ":", "value": "GET"}` |
| 2025-01-29 08:16:45 | /api/data | method:POST | `{"key": "method", "separator": ":", "value": "POST"}` |
| 2025-01-29 08:17:20 | /api/login | method:POST | `{"key": "method", "separator": ":", "value": "POST"}` |
This query constructs tag strings and then parses them to extract individual key and value components for analysis.
## List of related functions [#list-of-related-functions]
* [pair](/apl/scalar-functions/pair-functions/pair): Creates a pair string from key and value components. Use `parse_pair` to decompose existing pairs.
* [find\_pair](/apl/scalar-functions/pair-functions/find-pair): Searches an array of pairs for a matching pattern. Use `parse_pair` when you need to extract components from a single pair string.
* [split](/apl/scalar-functions/string-functions/split): Splits a string by a delimiter into an array. Use `parse_pair` when you specifically need key-value extraction with structured output.
* [extract](/apl/scalar-functions/string-functions/extract): Extracts substrings using regex. Use `parse_pair` for simpler key-value parsing without regex.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use `rex` or `split` commands to extract key-value components from strings. APL's `parse_pair` provides a dedicated function for this common operation.
```sql Splunk example
| rex field=tag "(?[^:]+):(?.*)"
```
```kusto APL equivalent
['sample-http-logs']
| extend parsed = parse_pair('host:server1')
| extend key = parsed.key, value = parsed.value
```
In ANSI SQL, you use `SUBSTRING` with `POSITION` or `SPLIT_PART` to extract key-value components. APL's `parse_pair` simplifies this with a dedicated function.
```sql SQL example
SELECT
SPLIT_PART(tag, ':', 1) AS key,
SPLIT_PART(tag, ':', 2) AS value
FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend parsed = parse_pair('host:server1')
| extend key = parsed.key, value = parsed.value
```
---
# bin_auto
Source: https://axiom.co/docs/apl/scalar-functions/rounding-functions/bin-auto
Use the `bin_auto` function to round datetime values down to fixed-size bins where the bin size is automatically determined by the query's time range. This function simplifies time-series analysis by automatically selecting an appropriate granularity based on the data being queried.
The `bin_auto` function is designed for use with the [summarize operator](/apl/tabular-operators/summarize-operator) and works exclusively with the `_time` column. It automatically adjusts the bin size to provide meaningful aggregation intervals, making it ideal for dashboards and visualizations where the time range varies.
## Usage [#usage]
### Syntax [#syntax]
```kusto
bin_auto(expression)
```
### Parameters [#parameters]
| Name | Type | Description |
| ------------ | ---------- | ------------------------------------------------------------- |
| `expression` | `datetime` | A datetime expression to round. Typically the `_time` column. |
### Returns [#returns]
The nearest multiple of the automatically determined bin size below the input expression. The bin size is calculated based on the query's time range to provide an appropriate number of data points.
## Use case examples [#use-case-examples]
Create a time-series view of HTTP traffic with automatic time granularity.
**Query**
```kusto
['sample-http-logs']
| summarize request_count = count() by bin_auto(_time)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20bin_auto\(_time\)%22%7D)
**Output**
| request\_count |
| -------------- |
| 4520 |
This query automatically groups HTTP requests into time buckets based on the query time range, making it easy to visualize traffic patterns without manually specifying bin sizes.
Monitor span counts over time with adaptive time resolution.
**Query**
```kusto
['otel-demo-traces']
| summarize span_count = count() by bin_auto(_time), ['service.name']
| order by span_count desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20bin_auto\(_time\)%2C%20%5B'service.name'%5D%20%7C%20order%20by%20span_count%20desc%22%7D)
**Output**
| service.name | span\_count |
| ------------ | ----------- |
| frontend | 1250 |
| cart | 430 |
| checkout | 180 |
This query provides a time-series breakdown of span activity per service, with the time granularity automatically adjusted based on the query's time range.
## List of related functions [#list-of-related-functions]
* [bin](/apl/scalar-functions/rounding-functions/bin): Rounds values down to a specified bin size. Use `bin` when you need explicit control over the interval size.
* [floor](/apl/scalar-functions/rounding-functions/floor): Rounds down to the largest integer less than or equal to the input. Use `bin_auto` for datetime-specific binning with automatic sizing.
* [summarize](/apl/tabular-operators/summarize-operator): The `bin_auto` function is designed for use within the `summarize` operator for time-based aggregations.
## Other query languages [#other-query-languages]
In Splunk SPL, automatic time bucketing is handled by the `timechart` command, which automatically selects span sizes. APL's `bin_auto` provides similar automatic binning within the `summarize` operator.
```sql Splunk example
| timechart count
```
```kusto APL equivalent
['sample-http-logs']
| summarize count() by bin_auto(_time)
```
ANSI SQL does not have a direct equivalent to automatic time binning. You typically need to calculate the bin size manually based on the query time range. APL's `bin_auto` handles this automatically.
```sql SQL example
-- Manual calculation required based on time range
SELECT DATE_TRUNC('hour', timestamp) AS time_bucket, COUNT(*)
FROM logs
GROUP BY time_bucket
```
```kusto APL equivalent
['sample-http-logs']
| summarize count() by bin_auto(_time)
```
---
# bin
Source: https://axiom.co/docs/apl/scalar-functions/rounding-functions/bin
Use the `bin` function to round values down to the nearest multiple of a specified bin size. This function is essential for grouping continuous data into discrete intervals, making it invaluable for time-based aggregations, histogram creation, and data bucketing.
The `bin` function works with numbers, dates, and timespans. When combined with the [summarize operator](/apl/tabular-operators/summarize-operator), it enables powerful time-series analysis by grouping events into fixed intervals.
## Usage [#usage]
### Syntax [#syntax]
```kusto
bin(value, bin_size)
```
### Parameters [#parameters]
| Name | Type | Description |
| ---------- | --------------------------------- | ---------------------------------------------------- |
| `value` | `real`, `datetime`, or `timespan` | The value to round down to the nearest bin boundary. |
| `bin_size` | `real`, `datetime`, or `timespan` | The size of each bin. Must be a positive value. |
### Returns [#returns]
The nearest multiple of `bin_size` that's less than or equal to `value`. The return type matches the input type.
## Use case examples [#use-case-examples]
Aggregate HTTP requests into 5-minute intervals to analyze traffic patterns.
**Query**
```kusto
['sample-http-logs']
| summarize request_count = count(), avg_duration = avg(req_duration_ms) by bin(_time, 5m)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20request_count%20%3D%20count\(\)%2C%20avg_duration%20%3D%20avg\(req_duration_ms\)%20by%20bin\(_time%2C%205m\)%22%7D)
**Output**
| request\_count | avg\_duration |
| -------------- | ------------- |
| 581,330 | 0.8631ms |
This query groups all HTTP requests into 5-minute windows, providing a time-series view of traffic volume and average response times.
Analyze trace durations by grouping them into 1-minute intervals per service.
**Query**
```kusto
['otel-demo-traces']
| summarize span_count = count(), p95_duration = percentile(duration, 95) by bin(_time, 1m), ['service.name']
| order by span_count desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20span_count%20%3D%20count\(\)%2C%20p95_duration%20%3D%20percentile\(duration%2C%2095\)%20by%20bin\(_time%2C%201m\)%2C%20%5B'service.name'%5D%20%7C%20order%20by%20span_count%20desc%22%7D)
**Output**
| service.name | span\_count | p95\_duration |
| ------------ | ----------- | ------------- |
| frontend | 520 | 24.2ms |
| cart | 230 | 12.4ms |
| checkout | 85 | 10.2ms |
This query creates a per-minute breakdown of span counts and 95th percentile durations for each service.
## List of related functions [#list-of-related-functions]
* [bin\_auto](/apl/scalar-functions/rounding-functions/bin-auto): Automatically determines bin size based on the query time range. Use `bin` when you need explicit control over the bin size.
* [floor](/apl/scalar-functions/rounding-functions/floor): Rounds down to the largest integer less than or equal to the input. Use `bin` for rounding to arbitrary multiples.
* [ceiling](/apl/scalar-functions/rounding-functions/ceiling): Rounds up to the smallest integer greater than or equal to the input. Use `bin` when you need to round down to specific intervals.
* [summarize](/apl/tabular-operators/summarize-operator): The `bin` function is commonly used within `summarize` for time-based aggregations.
## Other query languages [#other-query-languages]
In Splunk SPL, you use the `bin` command (formerly `bucket`) to group continuous values. APL's `bin` function works similarly but is used as a scalar function within expressions.
```sql Splunk example
| bin span=5m _time
| stats count by _time
```
```kusto APL equivalent
['sample-http-logs']
| summarize count() by bin(_time, 5m)
```
In ANSI SQL, you typically use `FLOOR` with division and multiplication to achieve binning. APL's `bin` function provides this capability directly.
```sql SQL example
SELECT FLOOR(UNIX_TIMESTAMP(timestamp) / 300) * 300 AS time_bucket, COUNT(*)
FROM logs
GROUP BY time_bucket
```
```kusto APL equivalent
['sample-http-logs']
| summarize count() by bin(_time, 5m)
```
---
# ceiling
Source: https://axiom.co/docs/apl/scalar-functions/rounding-functions/ceiling
Use the `ceiling` function to round a numeric value up to the smallest integer greater than or equal to the input. This function is useful when you need to ensure that fractional values always round up, such as when calculating resource allocations, pagination counts, or bucket sizes.
Use `ceiling` when you want to convert decimal numbers to whole numbers by rounding up. For example, if you have 7.2 requests per second and need to provision whole server instances, `ceiling` ensures you allocate 8 instances rather than 7.
## Usage [#usage]
### Syntax [#syntax]
```kusto
ceiling(number)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ------ | ------------------------------ |
| `number` | `real` | The numeric value to round up. |
### Returns [#returns]
An integer representing the smallest whole number greater than or equal to the input value.
## Use case examples [#use-case-examples]
Round request durations up to whole milliseconds for consistent bucketing.
**Query**
```kusto
['sample-http-logs']
| extend duration_bucket = ceiling(req_duration_ms) * 100
| summarize request_count = count() by duration_bucket
| order by duration_bucket asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20duration_bucket%20%3D%20ceiling\(req_duration_ms\)%20*%20100%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20duration_bucket%20%7C%20order%20by%20duration_bucket%20asc%22%7D)
**Output**
| duration\_bucket | request\_count |
| ---------------- | -------------- |
| 100 | 1250 |
| 200 | 3420 |
| 300 | 2180 |
| 400 | 890 |
This query groups requests into 100ms buckets using `ceiling` to ensure all fractional durations round up to the next bucket boundary.
Calculate the minimum number of seconds to allocate for trace processing.
**Query**
```kusto
['otel-demo-traces']
| extend duration_seconds = ceiling(duration / 1s)
| summarize avg_duration_seconds = avg(duration_seconds) by ['service.name']
| order by avg_duration_seconds desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20duration_seconds%20%3D%20ceiling\(duration%20%2F%201s\)%20%7C%20summarize%20avg_duration_seconds%20%3D%20avg\(duration_seconds\)%20by%20%5B'service.name'%5D%20%7C%20order%20by%20avg_duration_seconds%20desc%22%7D)
**Output**
| service.name | avg\_duration\_seconds |
| --------------- | ---------------------- |
| checkout | 5 |
| product-catalog | 3 |
| cart | 2 |
| frontend | 1 |
This query rounds span durations up to whole seconds to estimate resource allocation per service.
## List of related functions [#list-of-related-functions]
* [floor](/apl/scalar-functions/rounding-functions/floor): Rounds down to the largest integer less than or equal to the input. Use `ceiling` when you need to round up instead.
* [bin](/apl/scalar-functions/rounding-functions/bin): Rounds values down to a multiple of a specified bin size. Use `ceiling` for simple upward rounding to integers.
* [round](/apl/scalar-functions/mathematical-functions): Rounds to the nearest integer or specified precision. Use `ceiling` when you always need to round up.
## Other query languages [#other-query-languages]
In Splunk SPL, you use the `ceil` or `ceiling` function to round up values. APL's `ceiling` function works the same way.
```sql Splunk example
| eval rounded_up = ceil(request_time)
```
```kusto APL equivalent
['sample-http-logs']
| extend rounded_up = ceiling(req_duration_ms)
```
In ANSI SQL, the `CEILING` or `CEIL` function performs the same operation. APL's syntax is nearly identical.
```sql SQL example
SELECT CEILING(request_duration) AS rounded_up FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend rounded_up = ceiling(req_duration_ms)
```
---
# floor
Source: https://axiom.co/docs/apl/scalar-functions/rounding-functions/floor
Use the `floor` function to round a numeric value down to the largest integer less than or equal to the input. This function is useful when you need to ensure that fractional values always round down, such as when calculating completed intervals, resource consumption, or discrete counts.
Use `floor` when you want to convert decimal numbers to whole numbers by truncating the fractional part. For example, if a user has completed 2.7 sessions, `floor` returns 2 to represent fully completed sessions.
## Usage [#usage]
### Syntax [#syntax]
```kusto
floor(number)
```
### Parameters [#parameters]
| Name | Type | Description |
| -------- | ------ | -------------------------------- |
| `number` | `real` | The numeric value to round down. |
### Returns [#returns]
An integer representing the largest whole number less than or equal to the input value.
## Use case examples [#use-case-examples]
Calculate completed seconds from millisecond durations.
**Query**
```kusto
['sample-http-logs']
| extend completed_ms = floor(req_duration_ms)
| summarize request_count = count() by completed_ms
| order by completed_ms asc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20completed_ms%20%3D%20floor\(req_duration_ms\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20completed_ms%20%7C%20order%20by%20completed_ms%20asc%22%7D)
**Output**
| completed\_seconds | request\_count |
| ------------------ | -------------- |
| 0 | 8540 |
| 1 | 2310 |
| 2 | 890 |
| 3 | 245 |
This query converts request durations to whole milliseconds by rounding down, then groups requests by their completion time.
Group traces by completed duration intervals.
**Query**
```kusto
['otel-demo-traces']
| extend duration_seconds = floor(duration / 1s)
| summarize trace_count = count() by duration_seconds, ['service.name']
| order by trace_count desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20duration_seconds%20%3D%20floor\(duration%20%2F%201s\)%20%7C%20summarize%20trace_count%20%3D%20count\(\)%20by%20duration_seconds%2C%20%5B'service.name'%5D%20%7C%20order%20by%20trace_count%20desc%22%7D)
**Output**
| duration\_seconds | service.name | trace\_count |
| ----------------- | -------------- | ------------ |
| 0 | frontend | 1850 |
| 0 | frontend-proxy | 580 |
| 599 | load-generator | 420 |
| 600 | cart | 210 |
This query rounds span durations down to whole seconds to analyze the distribution of trace durations per service.
## List of related functions [#list-of-related-functions]
* [ceiling](/apl/scalar-functions/rounding-functions/ceiling): Rounds up to the smallest integer greater than or equal to the input. Use `floor` when you need to round down instead.
* [bin](/apl/scalar-functions/rounding-functions/bin): Rounds values down to a multiple of a specified bin size. Use `floor` for simple downward rounding to integers.
* [round](/apl/scalar-functions/mathematical-functions): Rounds to the nearest integer or specified precision. Use `floor` when you always need to round down.
## Other query languages [#other-query-languages]
In Splunk SPL, you use the `floor` function to round down values. APL's `floor` function works identically.
```sql Splunk example
| eval completed_seconds = floor(duration_ms / 1000)
```
```kusto APL equivalent
['sample-http-logs']
| extend completed_seconds = floor(req_duration_ms / 1000)
```
In ANSI SQL, the `FLOOR` function performs the same operation. APL's syntax is nearly identical.
```sql SQL example
SELECT FLOOR(request_duration / 1000) AS completed_seconds FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend completed_seconds = floor(req_duration_ms / 1000)
```
---
# format_sql
Source: https://axiom.co/docs/apl/scalar-functions/sql-functions/format-sql
## Introduction [#introduction]
The `format_sql` function converts the structured dictionary produced by [`parse_sql`](/apl/scalar-functions/sql-functions/parse-sql) back into a SQL string. Use it to normalize SQL formatting, validate that a parsed query round-trips correctly, or reconstruct a SQL statement after modifying its parsed representation.
`format_sql` is most useful as the second step in a parse-then-reconstruct pipeline: first parse a SQL string into a structured dictionary with `parse_sql`, optionally inspect or transform the result, and then call `format_sql` to produce a clean, normalized SQL string.
## Usage [#usage]
### Syntax [#syntax]
```kusto
format_sql(parsed_sql_model)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------------------ | ---------- | -------- | -------------------------------------------------- |
| parsed\_sql\_model | dictionary | Yes | The structured data model returned by `parse_sql`. |
### Returns [#returns]
A string containing the SQL statement reconstructed from the provided data model.
## Example [#example]
Parse a SQL query representing a slow-query log entry and reconstruct it to verify the round-trip.
**Query**
```kusto wrap
print formatted = format_sql(parse_sql('SELECT id, status, uri FROM requests WHERE req_duration_ms > 1000 ORDER BY req_duration_ms DESC'))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22print%20formatted%20%3D%20format_sql%28parse_sql%28%27SELECT%20id%2C%20status%2C%20uri%20FROM%20requests%20WHERE%20req_duration_ms%20%3E%201000%20ORDER%20BY%20req_duration_ms%20DESC%27%29%29%22%7D)
**Output**
```json wrap
{
"formatted": "select id, status, uri from requests where req_duration_ms > 1000 order by req_duration_ms desc"
}
```
The query parses the SQL string and then reconstructs it with `format_sql`, confirming that the parse-and-reconstruct pipeline preserves the original statement's structure.
## List of related functions [#list-of-related-functions]
* [parse\_sql](/apl/scalar-functions/sql-functions/parse-sql): Parses a SQL statement string into a structured dictionary. `format_sql` is the inverse of `parse_sql`.
* [parse\_json](/apl/scalar-functions/string-functions/parse-json): Parses a JSON string into a dynamic dictionary. Use `parse_json` when your data contains JSON rather than SQL.
## Other query languages [#other-query-languages]
Splunk has no equivalent to `format_sql`. There is no native way in SPL to reconstruct a SQL string from a parsed representation. In APL, `format_sql` takes the output of `parse_sql` and reconstructs the original SQL statement.
```sql Splunk example
-- No direct Splunk equivalent
```
```kusto APL equivalent
['sample-http-logs']
| take 1
| extend parsed = parse_sql('SELECT id, status FROM logs WHERE status = 500')
| project formatted = format_sql(parsed)
```
ANSI SQL has no built-in function to reconstruct a SQL string from a parsed representation. `format_sql` is unique to APL and works as the inverse of `parse_sql`.
```sql SQL example
-- No direct SQL equivalent
```
```kusto APL equivalent
['sample-http-logs']
| take 1
| extend parsed = parse_sql('SELECT id, status FROM logs WHERE status = 500')
| project formatted = format_sql(parsed)
```
---
# parse_sql
Source: https://axiom.co/docs/apl/scalar-functions/sql-functions/parse-sql
## Introduction [#introduction]
The `parse_sql` function parses a SQL statement string and returns a structured dictionary representing its components, such as tables, columns, conditions, and clauses. Use it to analyze SQL queries stored in your observability data, validate query structure, or extract specific parts of a SQL statement for further processing.
`parse_sql` is useful in database monitoring scenarios where SQL queries are captured as strings in audit logs or trace attributes, and you want to understand query patterns, detect anomalies, or inspect query structure at scale.
`parse_sql` supports simple SQL queries. It doesn't support stored procedures, window functions, common table expressions (CTEs), recursive queries, advanced statistical functions, or special join types.
## Usage [#usage]
### Syntax [#syntax]
```kusto
parse_sql(sql_statement)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------------- | ------ | -------- | --------------------------- |
| sql\_statement | string | Yes | The SQL statement to parse. |
### Returns [#returns]
A dictionary representing the structured data model of the SQL statement, including the statement type, selected columns, source tables, conditions, and ordering clauses.
## Use case examples [#use-case-examples]
Parse a SQL query that represents a slow-query log entry to inspect its structure.
**Query**
```kusto wrap
print parsed_query = parse_sql('SELECT id, status, uri FROM requests WHERE req_duration_ms > 1000 ORDER BY req_duration_ms DESC')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22print%20parsed_query%20%3D%20parse_sql%28%27SELECT%20id%2C%20status%2C%20uri%20FROM%20requests%20WHERE%20req_duration_ms%20%3E%201000%20ORDER%20BY%20req_duration_ms%20DESC%27%29%22%7D)
**Output**
```json
{
"parsed_query": {
"columns": [
{
"colname": "id"
},
{
"colname": "status"
},
{
"colname": "uri"
}
],
"from": [
{
"table": "requests"
}
],
"order": [
{
"direction": "desc",
"expr": {
"colname": "req_duration_ms"
}
}
],
"statement": "select",
"where": {
"operator": ">",
"params": [
{
"colname": "req_duration_ms"
},
{
"value": "1000",
"valtype": "integer"
}
]
}
}
}
```
The query parses a slow-query SQL string and returns its structured representation, letting you extract specific clauses with `parsed_query.from` or `parsed_query.columns`.
Parse a SQL query that represents a database span to inspect which tables and columns a service queries.
**Query**
```kusto wrap
print parsed_query = parse_sql('SELECT trace_id, span_id, duration FROM traces ORDER BY duration DESC')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22print%20parsed_query%20%3D%20parse_sql%28%27SELECT%20trace_id%2C%20span_id%2C%20duration%20FROM%20traces%20ORDER%20BY%20duration%20DESC%27%29%22%7D)
**Output**
```json
{
"parsed_query": {
"columns": [
{
"colname": "trace_id"
},
{
"colname": "span_id"
},
{
"colname": "duration"
}
],
"from": [
{
"table": "traces"
}
],
"order": [
{
"direction": "desc",
"expr": {
"colname": "duration"
}
}
],
"statement": "select"
}
}
```
The query parses a database span's SQL string and returns its structured representation so you can programmatically inspect which tables and columns the trace's database operations access.
Parse a SQL statement that contains an authorization filter to verify that the expected WHERE clause is present.
**Query**
```kusto wrap
print parsed_query = parse_sql('SELECT id, status FROM logs WHERE status = 401 OR status = 403')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22print%20parsed_query%20%3D%20parse_sql%28%27SELECT%20id%2C%20status%20FROM%20logs%20WHERE%20status%20%3D%20401%20OR%20status%20%3D%20403%27%29%22%7D)
**Output**
```json
{
"parsed_query": {
"columns": [
{
"colname": "id"
},
{
"colname": "status"
}
],
"from": [
{
"table": "logs"
}
],
"statement": "select",
"where": {
"operator": "or",
"params": [
{
"params": [
{
"colname": "status"
},
{
"value": "401",
"valtype": "integer"
}
],
"operator": "="
},
{
"operator": "=",
"params": [
{
"colname": "status"
},
{
"valtype": "integer",
"value": "403"
}
]
}
]
}
}
}
```
The query parses a SQL string with an OR condition in its WHERE clause. You can then inspect the `where` field to confirm that the expected authorization filters are present.
## List of related functions [#list-of-related-functions]
* [format\_sql](/apl/scalar-functions/sql-functions/format-sql): Converts the dictionary produced by `parse_sql` back into a SQL string. Use `format_sql` to normalize or round-trip a parsed query.
* [parse\_json](/apl/scalar-functions/string-functions/parse-json): Parses a JSON string into a dynamic dictionary. Use `parse_json` when your data contains JSON rather than SQL.
* [extract](/apl/scalar-functions/string-functions/extract): Extracts a substring matching a regular expression from a string. Use `extract` for simple pattern matching when you don't need full SQL parsing.
## Other query languages [#other-query-languages]
Splunk doesn't have a native SQL parser function. You would typically use `rex` with regular expressions to extract parts of a SQL query string. APL's `parse_sql` provides structured access to every clause of the SQL statement in a single call.
```sql Splunk example
... | rex field=sql_query "FROM\s+(?\w+)"
```
```kusto APL equivalent
... | project parsed = parse_sql('SELECT id, status FROM logs WHERE status = 500')
```
ANSI SQL has no built-in SQL parsing functions. `parse_sql` is unique to APL: it accepts a SQL string as input and returns a dictionary of its parsed components, enabling you to inspect and transform SQL statements using APL operators.
```sql SQL example
-- No direct SQL equivalent; you would use application-level string parsing
```
```kusto APL equivalent
... | project parsed = parse_sql('SELECT id, status FROM logs WHERE status = 500')
```
---
# base64_decode_toarray
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/base64-decode-toarray
Use the `base64_decode_toarray` function to decode a Base64-encoded string into an array of bytes. This is especially useful when you need to extract raw binary data from encoded inputs, such as network payloads, authentication tokens, or structured log fields. You can then transform or analyze the resulting byte array using additional APL functions like `array_slice`, `array_length`, or `array_index`.
This function is useful in scenarios where logs or telemetry data include fields that store binary data encoded as Base64, which is common for compact transmission or obfuscation. By decoding these values into byte arrays, you gain visibility into the underlying structure of the data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
base64_decode_toarray(base64_input)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------------- | ------ | -------- | ------------------------ |
| base64\_input | string | ✓ | A Base64-encoded string. |
The input string must be standard Base64 with padding, as defined by RFC 4648. For more information, see the [RFC Series documentation](https://www.rfc-editor.org/rfc/rfc4648).
### Returns [#returns]
An array of integers representing the decoded byte values. If the input string isn't valid Base64, the function returns an empty array.
## Use case examples [#use-case-examples]
You want to decode a Base64-encoded field in logs to inspect raw payloads for debugging or transformation.
**Query**
```kusto
['sample-http-logs']
| extend raw = base64_decode_toarray('aGVsbG8gd29ybGQ=')
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20raw%20%3D%20base64_decode_toarray\('aGVsbG8gd29ybGQ%3D'\)%22%7D)
**Output**
| raw |
| ------------------------------------------------------- |
| \[104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100] |
This query decodes the Base64 string `'aGVsbG8gd29ybGQ='`, which represents the ASCII string `"hello world"`, into an array of byte values.
You receive Base64-encoded trace IDs from an external system and want to decode them for low-level correlation.
**Query**
```kusto
['otel-demo-traces']
| extend trace_bytes = base64_decode_toarray(trace_id)
| project trace_id, trace_bytes
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20trace_bytes%20%3D%20base64_decode_toarray\(trace_id\)%20%7C%20project%20trace_id%2C%20trace_bytes%22%7D)
**Output**
| trace\_id | trace\_bytes |
| -------------------- | -------------------------------------------------------------- |
| dHJhY2UtaWQtZGVtbw== | \[116, 114, 97, 99, 101, 45, 105, 100, 45, 100, 101, 109, 111] |
This query decodes the trace ID from Base64 into its byte-level representation for internal processing or fingerprinting.
## List of related functions [#list-of-related-functions]
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the number of elements in an array. Use after decoding to validate payload length.
* [array\_slice](/apl/scalar-functions/array-functions/array-slice): Extracts a subrange from an array. Use to focus on specific byte segments after decoding.
* [base64\_encode\_fromarray](/apl/scalar-functions/string-functions/base64-encode-fromarray): Converts a sequence of bytes into a Base64-encoded string.
## Other query languages [#other-query-languages]
In Splunk SPL, decoding Base64 requires using `eval` with the `base64decode` function, which returns a string. If you need a byte array representation, you must manually transform it. In APL, `base64_decode_toarray` directly produces an array of bytes, allowing you to work with binary data more precisely.
```sql Splunk example
| eval decoded=base64decode(encodedField)
```
```kusto APL equivalent
['my-dataset']
| extend decoded = base64_decode_toarray(encodedField)
```
Standard ANSI SQL doesn’t include a native function to decode Base64 into byte arrays. You typically need to rely on a UDF or cast the result into `VARBINARY` if the engine supports it. APL provides a built-in function that directly yields an array of integers representing bytes.
```sql SQL example
SELECT CAST(FROM_BASE64(encodedField) AS BINARY) FROM my_table;
```
```kusto APL equivalent
['my-dataset']
| extend decoded = base64_decode_toarray(encodedField)
```
---
# base64_decode_tostring
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/base64-decode-tostring
The `base64_decode_tostring` function decodes a Base64-encoded string back to its original UTF-8 text format. Use this function when you need to decode Base64-encoded data received from APIs, stored in configurations, or logged in encoded format.
## Usage [#usage]
### Syntax [#syntax]
```kusto
base64_decode_tostring(value)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------ | -------- | ------------------------------------------------- |
| value | string | Yes | The Base64-encoded string to be decoded to UTF-8. |
### Returns [#returns]
Returns the decoded UTF-8 string from the Base64-encoded input.
## Use case examples [#use-case-examples]
Decode Base64-encoded messages or tokens in HTTP logs to analyze their content.
**Query**
```kusto
['sample-http-logs']
| extend decoded_message = base64_decode_tostring('VGhpcyBpcyBhIHRlc3QgbWVzc2FnZQ==')
| project _time, decoded_message, status, uri
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20decoded_message%20%3D%20base64_decode_tostring\(%27VGhpcyBpcyBhIHRlc3QgbWVzc2FnZQ%3D%3D%27\)%20%7C%20project%20_time%2C%20decoded_message%2C%20status%2C%20uri%20%7C%20limit%2010%22%7D)
**Output**
| \_time | decoded\_message | status | uri |
| -------------------- | ---------------------- | ------ | ---------- |
| 2024-11-06T10:00:00Z | This is a test message | 200 | /api/data |
| 2024-11-06T10:01:00Z | This is a test message | 200 | /api/users |
This query decodes a Base64-encoded message, which is useful when analyzing encoded payloads or authentication tokens in HTTP requests.
Decode Base64-encoded span attributes or metadata in distributed traces.
**Query**
```kusto
['otel-demo-traces']
| extend decoded_attr = base64_decode_tostring('Y2hlY2tvdXQ=')
| project _time, ['service.name'], decoded_attr, trace_id
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20decoded_attr%20%3D%20base64_decode_tostring\(%27Y2hlY2tvdXQ%3D%27\)%20%7C%20project%20_time%2C%20%5B%27service.name%27%5D%2C%20decoded_attr%2C%20trace_id%20%7C%20limit%2010%22%7D)
**Output**
| \_time | service.name | decoded\_attr | trace\_id |
| -------------------- | ------------ | ------------- | --------- |
| 2024-11-06T10:00:00Z | frontend | checkout | abc123 |
| 2024-11-06T10:01:00Z | cart | checkout | def456 |
This query decodes Base64-encoded attributes in traces, which can be useful when trace metadata is transmitted in encoded format.
Decode Base64-encoded authentication tokens or credentials in security logs for investigation.
**Query**
```kusto
['sample-http-logs']
| extend decoded_token = base64_decode_tostring('YWRtaW46cGFzc3dvcmQ=')
| project _time, decoded_token, status, uri, id
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20decoded_token%20%3D%20base64_decode_tostring\('YWRtaW46cGFzc3dvcmQ%3D'\)%20%7C%20project%20_time%2C%20decoded_token%2C%20status%2C%20uri%2C%20id%20%7C%20limit%2010%22%7D)
**Output**
| \_time | decoded\_token | status | uri | id |
| -------------------- | -------------- | ------ | ---------- | ------- |
| 2024-11-06T10:00:00Z | admin:password | 401 | /api/login | user123 |
| 2024-11-06T10:01:00Z | admin:password | 403 | /admin | user456 |
This query decodes Base64-encoded credentials from failed authentication attempts, which is useful for security investigations and identifying brute-force attack patterns.
## List of related functions [#list-of-related-functions]
* [base64\_encode\_tostring](/apl/scalar-functions/string-functions/base64-encode-tostring): Encodes a UTF-8 string into Base64 format. Use this when you need to encode data for transmission or storage.
* [base64\_decode\_toarray](/apl/scalar-functions/string-functions/base64-decode-toarray): Decodes a Base64 string into an array of bytes. Use this when you need to work with the raw binary representation.
* [base64\_encode\_fromarray](/apl/scalar-functions/string-functions/base64-encode-fromarray): Encodes an array of bytes into a Base64 string. Use this when working with binary data rather than text strings.
* [url\_decode](/apl/scalar-functions/string-functions/url-decode): Decodes a URL-encoded string. Use this when working with URL encoding rather than Base64 encoding.
## Other query languages [#other-query-languages]
In Splunk SPL, you might not have a built-in Base64 decoding function and would typically rely on external scripts. In APL, `base64_decode_tostring` provides native Base64 decoding directly in your queries.
```sql Splunk example
| eval decoded=base64decode(field_name)
```
```kusto APL equivalent
['sample-http-logs']
| extend decoded = base64_decode_tostring(field_name)
```
In ANSI SQL, Base64 decoding typically requires database-specific functions like `FROM_BASE64()` in MySQL or custom functions. APL provides `base64_decode_tostring` as a standard function.
```sql SQL example
SELECT FROM_BASE64(field_name) AS decoded FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend decoded = base64_decode_tostring(field_name)
```
---
# base64_encode_fromarray
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/base64-encode-fromarray
Use the `base64_encode_fromarray` function to convert a sequence of bytes into a Base64-encoded string. This function is useful when you need to transform binary data into a textual representation for safe storage, logging, or transmission—especially over protocols that require plain-text formats.
You can apply this function when working with IP addresses, file contents, or any byte array that needs to be encoded for use in logs or APIs. It accepts a byte array and returns the Base64-encoded string representation of that array.
## Usage [#usage]
### Syntax [#syntax]
```kusto
base64_encode_fromarray(array)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------- | -------- | ----------------------------------------------------------------------- |
| array | dynamic | ✓ | A dynamic array of integers between 0 and 255 representing byte values. |
### Returns [#returns]
If successful, returns a string representing the Base64-encoded version of the input byte array. If the input isn't a valid array of bytes, the result is an empty string.
## Example [#example]
Use this function to encode request metadata for logging or comparison with external systems that require Base64-encoded fields.
**Query**
```kusto
['sample-http-logs']
| extend ip_bytes = dynamic([192, 168, 0, 1])
| extend encoded_ip = base64_encode_fromarray(ip_bytes)
| project _time, id, method, uri, encoded_ip
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20ip_bytes%20%3D%20dynamic\(%5B192%2C%20168%2C%200%2C%201%5D\)%20%7C%20extend%20encoded_ip%20%3D%20base64_encode_fromarray\(ip_bytes\)%20%7C%20project%20_time%2C%20id%2C%20method%2C%20uri%2C%20encoded_ip%22%7D)
**Output**
| \_time | id | method | uri | encoded\_ip |
| -------------------- | ------- | ------ | --------- | ----------- |
| 2025-06-25T08:00:00Z | user123 | GET | /api/data | wKgAAQ== |
Encodes a hardcoded byte representation of an IP address into Base64 for easy string-based comparison or logging.
## List of related functions [#list-of-related-functions]
* [array\_concat](/apl/scalar-functions/array-functions/array-concat): Concatenates arrays of bytes or values. Use this when building byte arrays for Base64 encoding.
* [base64\_decode\_toarray](/apl/scalar-functions/string-functions/base64-decode-toarray): Decode a Base64-encoded string into an array of bytes. Use this when decoding data received from external sources.
* [format\_ipv4\_mask](/apl/scalar-functions/ip-functions/format-ipv4-mask): Formats a raw IPv4 address with an optional prefix into CIDR notation. Use when dealing with IP-to-string transformations.
* [parse\_ipv4](/apl/scalar-functions/ip-functions/parse-ipv4): Parses a string representation of an IP address into its numeric form. Use this before encoding or masking IP addresses.
## Other query languages [#other-query-languages]
Splunk doesn’t provide a native function to directly encode an array of bytes into Base64 in SPL. You would typically write a custom script using an external command or use `eval` with a helper function in an app context.
```sql Splunk example
| eval encoded=custom_base64_encode(byte_array_field)
```
```kusto APL equivalent
datatable(bytes: dynamic)
[
dynamic([192, 168, 1, 1])
]
| extend encoded = base64_encode_fromarray(bytes)
```
ANSI SQL doesn’t define a built-in standard for Base64 encoding from an array of bytes. This is usually handled via vendor-specific functions (e.g., `TO_BASE64()` in MySQL, or `encode()` in PostgreSQL).
```sql SQL example
SELECT TO_BASE64(BINARY 'data') AS encoded;
```
```kusto APL equivalent
datatable(bytes: dynamic)
[
dynamic([192, 168, 1, 1])
]
| extend encoded = base64_encode_fromarray(bytes)
```
---
# base64_encode_tostring
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/base64-encode-tostring
The `base64_encode_tostring` function encodes a string into Base64 format. Use this function when you need to encode data for transmission or storage in systems that only support text-based formats, such as APIs, configuration files, or log analysis pipelines.
## Usage [#usage]
### Syntax [#syntax]
```kusto
base64_encode_tostring(value)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------ | -------- | ----------------------------------------- |
| value | string | Yes | The input string to be encoded as Base64. |
### Returns [#returns]
Returns the input string encoded as a Base64 string.
## Use case examples [#use-case-examples]
Encode HTTP content types for secure transmission or storage in Base64 format.
**Query**
```kusto
['sample-http-logs']
| extend encoded_content_type = base64_encode_tostring(content_type)
| project _time, content_type, encoded_content_type
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20encoded_content_type%20%3D%20base64_encode_tostring\(content_type\)%20%7C%20project%20_time%2C%20content_type%2C%20encoded_content_type%20%7C%20limit%2010%22%7D)
**Output**
| \_time | content\_type | encoded\_content\_type |
| -------------------- | ---------------- | ------------------------ |
| 2024-11-06T10:00:00Z | application/json | YXBwbGljYXRpb24vanNvbg== |
| 2024-11-06T10:01:00Z | text/html | dGV4dC9odG1s |
This query encodes the content type of each HTTP request into Base64 format, which is useful when you need to pass content types through systems that have character restrictions.
Encode service names in traces for compatibility with external systems.
**Query**
```kusto
['otel-demo-traces']
| extend encoded_service = base64_encode_tostring(['service.name'])
| project _time, ['service.name'], encoded_service, trace_id
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20encoded_service%20%3D%20base64_encode_tostring\(%5B%27service.name%27%5D\)%20%7C%20project%20_time%2C%20%5B%27service.name%27%5D%2C%20encoded_service%2C%20trace_id%20%7C%20limit%2010%22%7D)
**Output**
| \_time | service.name | encoded\_service | trace\_id |
| -------------------- | ------------ | ---------------- | --------- |
| 2024-11-06T10:00:00Z | frontend | ZnJvbnRlbmQ= | abc123 |
| 2024-11-06T10:01:00Z | checkout | Y2hlY2tvdXQ= | def456 |
This query encodes service names into Base64 format, which can be useful when transmitting trace metadata to systems with specific encoding requirements.
Encode user IDs or sensitive identifiers in security logs for obfuscation or transmission.
**Query**
```kusto
['sample-http-logs']
| extend encoded_id = base64_encode_tostring(id)
| project _time, id, encoded_id, status, uri
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20encoded_id%20%3D%20base64_encode_tostring\(id\)%20%7C%20project%20_time%2C%20id%2C%20encoded_id%2C%20status%2C%20uri%20%7C%20limit%2010%22%7D)
**Output**
| \_time | id | encoded\_id | status | uri |
| -------------------- | ------- | ------------ | ------ | ---------- |
| 2024-11-06T10:00:00Z | user123 | dXNlcjEyMw== | 401 | /api/login |
| 2024-11-06T10:01:00Z | user456 | dXNlcjQ1Ng== | 403 | /admin |
This query encodes user IDs from failed authentication attempts into Base64 format, which can be useful for secure log transmission or when integrating with systems that require encoded identifiers.
## List of related functions [#list-of-related-functions]
* [base64\_decode\_tostring](/apl/scalar-functions/string-functions/base64-decode-tostring): Decodes a Base64-encoded string back to its original UTF-8 format. Use this when you need to reverse the encoding operation.
* [base64\_encode\_fromarray](/apl/scalar-functions/string-functions/base64-encode-fromarray): Encodes an array of bytes into a Base64 string. Use this when working with binary data rather than text strings.
* [base64\_decode\_toarray](/apl/scalar-functions/string-functions/base64-decode-toarray): Decodes a Base64 string into an array of bytes. Use this when you need to work with the raw binary representation.
* [url\_encode](/apl/scalar-functions/string-functions/url-encode): Encodes a URL string for safe transmission. Use this when working with URLs rather than general text encoding.
## Other query languages [#other-query-languages]
In Splunk SPL, you might not have a built-in Base64 encoding function and would typically rely on external scripts or commands. In APL, `base64_encode_tostring` provides native Base64 encoding directly in your queries.
```sql Splunk example
| eval encoded=base64encode(field_name)
```
```kusto APL equivalent
['sample-http-logs']
| extend encoded = base64_encode_tostring(field_name)
```
In ANSI SQL, Base64 encoding typically requires database-specific functions like `TO_BASE64()` in MySQL or custom functions. APL provides `base64_encode_tostring` as a standard function.
```sql SQL example
SELECT TO_BASE64(field_name) AS encoded FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend encoded = base64_encode_tostring(field_name)
```
---
# coalesce
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/coalesce
The `coalesce` function evaluates a list of expressions and returns the first non-null (or non-empty for strings) value. Use this function to handle missing data, provide default values, or select the first available field from multiple options in your queries.
## Usage [#usage]
### Syntax [#syntax]
```kusto
coalesce(expr1, expr2, ..., exprN)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| -------------------------- | ------ | -------- | ----------------------------------------------------------------------- |
| `expr1, expr2, ..., exprN` | scalar | Yes | A list of expressions to evaluate. At least one expression is required. |
### Returns [#returns]
Returns the value of the first expression that's not null. For string expressions, returns the first non-empty string.
## Use case examples [#use-case-examples]
Provide fallback values when analyzing HTTP logs where certain fields might be missing or empty.
**Query**
```kusto
['sample-http-logs']
| extend location = coalesce(['geo.city'], ['geo.country'], 'Unknown')
| summarize request_count = count() by location
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20location%20%3D%20coalesce\(%5B%27geo.city%27%5D%2C%20%5B%27geo.country%27%5D%2C%20%27Unknown%27\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20location%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| location | request\_count |
| ------------- | -------------- |
| New York | 1523 |
| London | 987 |
| United States | 654 |
| Unknown | 234 |
This query uses `coalesce` to select the city if available, fall back to country if city is missing, and finally use 'Unknown' if both are missing, ensuring comprehensive location tracking.
Handle missing or null span attributes in distributed traces by providing default values.
**Query**
```kusto
['otel-demo-traces']
| extend span_kind = coalesce(kind, 'unknown')
| summarize span_count = count() by span_kind, ['service.name']
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20span_kind%20%3D%20coalesce\(kind%2C%20%27unknown%27\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20span_kind%2C%20%5B%27service.name%27%5D%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| span\_kind | service.name | span\_count |
| ---------- | --------------- | ----------- |
| server | frontend | 2345 |
| client | checkout | 1876 |
| internal | cart | 1234 |
| unknown | product-catalog | 567 |
This query uses `coalesce` to provide a default value for span kinds, ensuring that traces with missing kind information are still included in the analysis.
Ensure user identification in security logs by selecting from multiple possible identifier fields.
**Query**
```kusto
['sample-http-logs']
| extend user_identifier = coalesce(id, uri, 'anonymous')
| summarize failed_attempts = count() by user_identifier, status
| sort by failed_attempts desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20user_identifier%20%3D%20coalesce\(id%2C%20uri%2C%20'anonymous'\)%20%7C%20summarize%20failed_attempts%20%3D%20count\(\)%20by%20user_identifier%2C%20status%20%7C%20sort%20by%20failed_attempts%20desc%20%7C%20limit%2010%22%7D)
**Output**
| user\_identifier | status | failed\_attempts |
| ---------------- | ------ | ---------------- |
| user123 | 401 | 45 |
| user456 | 403 | 32 |
| /admin | 401 | 28 |
| anonymous | 401 | 15 |
This query uses `coalesce` to identify users from failed authentication attempts, trying the user ID first, then falling back to the URI, and finally marking truly anonymous attempts.
## List of related functions [#list-of-related-functions]
* [isnotnull](/apl/scalar-functions/string-functions/isnotnull): Checks if a value isn't null. Use this to explicitly test for null values before using coalesce.
* [isnull](/apl/scalar-functions/string-functions/isnull): Checks if a value is null. Use this to identify which values would be skipped by coalesce.
* [isempty](/apl/scalar-functions/string-functions/isempty): Checks if a string is empty or null. Use this with coalesce when working specifically with string data.
* [isnotempty](/apl/scalar-functions/string-functions/isnotempty): Checks if a string isn't empty and not null. Use this to validate strings before coalescing them.
## Other query languages [#other-query-languages]
In Splunk SPL, you use the `coalesce` command similarly to APL, but the syntax is slightly different. APL's `coalesce` works identically to Splunk's version.
```sql Splunk example
| eval result=coalesce(field1, field2, field3)
```
```kusto APL equivalent
['sample-http-logs']
| extend result = coalesce(field1, field2, field3)
```
In ANSI SQL, `COALESCE` is a standard function with the same behavior. APL's `coalesce` function works identically to SQL's `COALESCE`.
```sql SQL example
SELECT COALESCE(field1, field2, field3) AS result FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend result = coalesce(field1, field2, field3)
```
---
# countof_regex
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/countof-regex
The `countof_regex` function counts occurrences of a regular expression pattern within a string. Use this function when you need to count complex patterns or character classes in log messages, requiring more flexibility than simple substring matching.
## Usage [#usage]
### Syntax [#syntax]
```kusto
countof_regex(regex, text)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------ | -------- | ------------------------------------------------------------- |
| regex | string | Yes | The regular expression pattern to search for within the text. |
| text | string | Yes | The source string where pattern occurrences are counted. |
### Returns [#returns]
Returns the number of times the regex pattern matches in the text.
## Use case examples [#use-case-examples]
Count numeric patterns in URIs to identify parameterized endpoint usage.
**Query**
```kusto
['sample-http-logs']
| extend numeric_params = countof_regex('[0-9]+', uri)
| where numeric_params > 0
| summarize avg_params = avg(numeric_params), request_count = count() by method
| sort by request_count desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20numeric_params%20%3D%20countof_regex\(%27%5B0-9%5D%2B%27%2C%20uri\)%20%7C%20where%20numeric_params%20%3E%200%20%7C%20summarize%20avg_params%20%3D%20avg\(numeric_params\)%2C%20request_count%20%3D%20count\(\)%20by%20method%20%7C%20sort%20by%20request_count%20desc%22%7D)
**Output**
| method | avg\_params | request\_count |
| ------ | ----------- | -------------- |
| GET | 1.8 | 3421 |
| POST | 1.2 | 1876 |
| PUT | 2.1 | 654 |
| DELETE | 1.5 | 234 |
This query counts numeric parameters in request URIs using regex, helping identify how frequently parameterized endpoints are accessed by different HTTP methods.
Count specific character patterns in trace IDs to analyze ID generation patterns.
**Query**
```kusto
['otel-demo-traces']
| extend hex_chars = countof_regex('[a-f]', trace_id)
| summarize avg_hex_chars = avg(hex_chars), trace_count = count() by ['service.name']
| sort by trace_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20hex_chars%20%3D%20countof_regex\(%27%5Ba-f%5D%27%2C%20trace_id\)%20%7C%20summarize%20avg_hex_chars%20%3D%20avg\(hex_chars\)%2C%20trace_count%20%3D%20count\(\)%20by%20%5B%27service.name%27%5D%20%7C%20sort%20by%20trace_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| service.name | avg\_hex\_chars | trace\_count |
| --------------- | --------------- | ------------ |
| frontend | 8.3 | 2345 |
| checkout | 8.1 | 1987 |
| cart | 8.5 | 1654 |
| product-catalog | 7.9 | 1234 |
This query counts hexadecimal characters (a-f) in trace IDs to analyze the distribution of characters, which can help identify issues with trace ID generation.
Identify requests with multiple special characters that might indicate injection attacks.
**Query**
```kusto
['sample-http-logs']
| extend special_chars = countof_regex('[<>%;()&+]', uri)
| where special_chars >= 3
| project _time, uri, special_chars, id, status, method
| sort by special_chars desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20special_chars%20%3D%20countof_regex\('%5B%3C%3E%25%3B\(\)%26%2B%5D'%2C%20uri\)%20%7C%20where%20special_chars%20%3E%3D%203%20%7C%20project%20_time%2C%20uri%2C%20special_chars%2C%20id%2C%20status%2C%20method%20%7C%20sort%20by%20special_chars%20desc%20%7C%20limit%2010%22%7D)
**Output**
| \_time | uri | special\_chars | id | status | method |
| -------------------- | ----------------------------------------- | -------------- | ------- | ------ | ------ |
| 2024-11-06T10:00:00Z | /search?q=\ | 8 | user123 | 403 | GET |
| 2024-11-06T10:01:00Z | /api?param='OR'1'='1 | 6 | user456 | 403 | POST |
This query counts special characters commonly used in injection attacks, helping identify potentially malicious requests that warrant further investigation.
## List of related functions [#list-of-related-functions]
* [countof](/apl/scalar-functions/string-functions/countof): Counts plain substring occurrences. Use this when you need exact string matching without regex complexity.
* [extract](/apl/scalar-functions/string-functions/extract): Extracts the first substring matching a regex. Use this when you need to capture the matched text, not just count occurrences.
* [extract\_all](/apl/scalar-functions/string-functions/extract-all): Extracts all substrings matching a regex. Use this when you need both the count and the actual matched values.
* [replace\_regex](/apl/scalar-functions/string-functions/replace-regex): Replaces all regex matches with another string. Use this when you need to modify matched patterns rather than count them.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `rex` with `max_match` to count regex matches. APL's `countof_regex` provides a more straightforward approach.
```sql Splunk example
| rex field=message max_match=0 "error|warning"
| eval pattern_count=mvcount(rex)
```
```kusto APL equivalent
['sample-http-logs']
| extend pattern_count = countof_regex('error|warning', uri)
```
In ANSI SQL, counting regex matches typically requires database-specific functions. APL's `countof_regex` provides a standard approach.
```sql SQL example
SELECT REGEXP_COUNT(field, 'pattern') AS count FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend count = countof_regex('pattern', field)
```
---
# countof
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/countof
The `countof` function counts the occurrences of a plain substring within a string. Use this function when you need to find how many times a specific text pattern appears in log messages, user input, or any string field without using regular expressions.
## Usage [#usage]
### Syntax [#syntax]
```kusto
countof(search, text)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------ | ------ | -------- | -------------------------------------------------- |
| search | string | Yes | The plain substring to search for within the text. |
| text | string | Yes | The source string where occurrences are counted. |
### Returns [#returns]
Returns the number of times the search string appears in the text.
## Use case examples [#use-case-examples]
Count how many times specific HTTP methods appear in URIs to identify API usage patterns.
**Query**
```kusto
['sample-http-logs']
| extend api_segments = countof('/', uri)
| summarize avg_depth = avg(api_segments), request_count = count() by method
| sort by request_count desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20api_segments%20%3D%20countof\(%27%2F%27%2C%20uri\)%20%7C%20summarize%20avg_depth%20%3D%20avg\(api_segments\)%2C%20request_count%20%3D%20count\(\)%20by%20method%20%7C%20sort%20by%20request_count%20desc%22%7D)
**Output**
| method | avg\_depth | request\_count |
| ------ | ---------- | -------------- |
| GET | 3.2 | 5432 |
| POST | 2.8 | 2341 |
| PUT | 2.5 | 876 |
| DELETE | 2.1 | 234 |
This query counts the number of forward slashes in URIs to determine the average API endpoint depth by HTTP method, helping identify API structure complexity.
Count occurrences of specific terms in span names to analyze service operation patterns.
**Query**
```kusto
['otel-demo-traces']
| extend has_http = countof('frontend', ['service.name'])
| summarize services_with_frontend = sum(has_http), total_spans = count()
| extend percentage = round(100.0 * services_with_frontend / total_spans, 2)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20has_http%20%3D%20countof\(%27frontend%27%2C%20%5B%27service.name%27%5D\)%20%7C%20summarize%20services_with_frontend%20%3D%20sum\(has_http\)%2C%20total_spans%20%3D%20count\(\)%20%7C%20extend%20percentage%20%3D%20round\(100.0%20*%20services_with_frontend%20%2F%20total_spans%2C%202\)%22%7D)
**Output**
| services\_with\_frontend | total\_spans | percentage |
| ------------------------ | ------------ | ---------- |
| 1234 | 8765 | 14.08 |
This query counts how many spans contain 'frontend' in their service name to understand the proportion of frontend-related operations in your traces.
Count slashes in URIs to analyze URL structure and detect unusual patterns that might indicate security threats.
**Query**
```kusto
['sample-http-logs']
| extend slash_count = countof('/', uri)
| where slash_count > 5
| project _time, uri, slash_count, id, status, ['geo.country']
| sort by slash_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20slash_count%20%3D%20countof\(%27%2F%27%2C%20uri\)%20%7C%20where%20slash_count%20%3E%205%20%7C%20project%20_time%2C%20uri%2C%20slash_count%2C%20id%2C%20status%2C%20%5B%27geo.country%27%5D%20%7C%20sort%20by%20slash_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| \_time | uri | slash\_count | id | status | geo.country |
| -------------------- | ---------------------------------------- | ------------ | ------- | ------ | ----------- |
| 2024-11-06T10:00:00Z | /api/v1/users/12345/posts/67890/comments | 6 | user123 | 200 | US |
| 2024-11-06T10:01:00Z | /admin/config/settings/advanced/security | 5 | user456 | 200 | UK |
This query identifies URIs with unusually high slash counts, which can help detect complex or potentially suspicious URL patterns that might warrant further investigation.
## List of related functions [#list-of-related-functions]
* [countof\_regex](/apl/scalar-functions/string-functions/countof-regex): Counts substring occurrences using regular expressions. Use this when you need pattern matching instead of exact string matching.
* [strlen](/apl/scalar-functions/string-functions/strlen): Returns the length of a string. Use this when you need the total character count rather than occurrence counting.
* [indexof](/apl/scalar-functions/string-functions/indexof): Finds the position of the first occurrence of a substring. Use this when you need to know where a substring appears, not how many times.
* [extract](/apl/scalar-functions/string-functions/extract): Extracts substrings using regular expressions. Use this when you need to capture matched text rather than count occurrences.
## Other query languages [#other-query-languages]
In Splunk SPL, you might use a combination of `rex` and counting operations. APL's `countof` provides a simpler approach for counting plain string occurrences.
```sql Splunk example
| rex field=message max_match=0 "error"
| eval error_count=mvcount(error)
```
```kusto APL equivalent
['sample-http-logs']
| extend error_count = countof('GET', method)
```
In ANSI SQL, you typically calculate string occurrences using length differences. APL's `countof` provides a more direct approach.
```sql SQL example
SELECT (LENGTH(field) - LENGTH(REPLACE(field, 'search', ''))) / LENGTH('search') AS count FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend count = countof('search', field)
```
---
# extract_all
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/extract-all
The `extract_all` function retrieves all substrings that match a regular expression from a source string. Use this function when you need to capture multiple matches of a pattern, such as extracting all email addresses, URLs, or repeated patterns from log entries.
## Usage [#usage]
### Syntax [#syntax]
```kusto
extract_all(regex, captureGroups, text)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------------- | ------------- | -------- | ----------------------------------------------------------------------------------------------- |
| regex | string | Yes | A regular expression with one or more capture groups. |
| captureGroups | dynamic array | Yes | An array of capture group numbers to extract (for example, `dynamic([1])` or `dynamic([1,2])`). |
| text | string | Yes | The source string to search. |
### Returns [#returns]
Returns a dynamic array containing all matches. For single capture groups, returns a one-dimensional array. For multiple capture groups, returns a two-dimensional array.
## Use case examples [#use-case-examples]
Extract all numeric values from URIs to analyze parameter patterns in API requests.
**Query**
```kusto
['sample-http-logs']
| extend numbers = extract_all('([0-9]+)', dynamic([1]), uri)
| where array_length(numbers) > 0
| project _time, uri, numbers, method
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20numbers%20%3D%20extract_all\(%27\(%5B0-9%5D%2B\)%27%2C%20dynamic\(%5B1%5D\)%2C%20uri\)%20%7C%20where%20array_length\(numbers\)%20%3E%200%20%7C%20project%20_time%2C%20uri%2C%20numbers%2C%20method%20%7C%20limit%2010%22%7D)
**Output**
| \_time | uri | numbers | method |
| -------------------- | --------------------------------- | ---------------------- | ------ |
| 2024-11-06T10:00:00Z | /api/users/123/posts/456 | \["123", "456"] | GET |
| 2024-11-06T10:01:00Z | /products/789 | \["789"] | GET |
| 2024-11-06T10:02:00Z | /orders/111/items/222/details/333 | \["111", "222", "333"] | POST |
This query extracts all numeric values from URIs, helping analyze how many IDs are typically passed in API requests and their patterns.
Extract all service names mentioned in span attributes to understand service dependencies.
**Query**
```kusto
['otel-demo-traces']
| extend service_mentions = extract_all('(frontend|checkout|cart|product-catalog)', dynamic([1]), ['service.name'])
| where array_length(service_mentions) > 0
| summarize mention_count = count() by service_mention = tostring(service_mentions)
| sort by mention_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20service_mentions%20%3D%20extract_all\(%27\(frontend%7Ccheckout%7Ccart%7Cproduct-catalog\)%27%2C%20dynamic\(%5B1%5D\)%2C%20%5B%27service.name%27%5D\)%20%7C%20where%20array_length\(service_mentions\)%20%3E%200%20%7C%20summarize%20mention_count%20%3D%20count\(\)%20by%20service_mention%20%3D%20tostring\(service_mentions\)%20%7C%20sort%20by%20mention_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| service\_mention | mention\_count |
| -------------------- | -------------- |
| \["frontend"] | 4532 |
| \["checkout"] | 3421 |
| \["cart"] | 2987 |
| \["product-catalog"] | 2341 |
This query extracts all service name patterns from span data, helping understand which services are most frequently referenced in traces.
Extract all suspicious keywords from URIs to detect potential SQL injection or XSS attempts.
**Query**
```kusto
['sample-http-logs']
| extend threats = extract_all('(union|select|script|alert|drop|insert|delete)', dynamic([1]), uri)
| where array_length(threats) > 0
| project _time, uri, threats, id, status, ['geo.country']
| sort by array_length(threats) desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20threats%20%3D%20extract_all\(%27\(union%7Cselect%7Cscript%7Calert%7Cdrop%7Cinsert%7Cdelete\)%27%2C%20dynamic\(%5B1%5D\)%2C%20uri\)%20%7C%20where%20array_length\(threats\)%20%3E%200%20%7C%20project%20_time%2C%20uri%2C%20threats%2C%20id%2C%20status%2C%20%5B%27geo.country%27%5D%20%7C%20sort%20by%20array_length\(threats\)%20desc%20%7C%20limit%2010%22%7D)
**Output**
| \_time | uri | threats | id | status | geo.country |
| -------------------- | ----------------------------------------- | -------------------- | ------- | ------ | ----------- |
| 2024-11-06T10:00:00Z | /search?q=\ | \["script", "alert"] | user123 | 403 | Unknown |
| 2024-11-06T10:01:00Z | /api?id=1'union select \* | \["union", "select"] | user456 | 403 | Russia |
This query extracts all SQL and XSS-related keywords from URIs, helping identify potential injection attacks by counting how many threat indicators appear in each request.
## List of related functions [#list-of-related-functions]
* [extract](/apl/scalar-functions/string-functions/extract): Extracts only the first match of a regex pattern. Use this when you only need the first occurrence rather than all matches.
* [split](/apl/scalar-functions/string-functions/split): Splits strings by a delimiter into an array. Use this for simpler tokenization without regex complexity.
* [parse\_json](/apl/scalar-functions/string-functions/parse-json): Parses JSON strings into dynamic objects. Use this when working with structured JSON data rather than regex patterns.
* [countof\_regex](/apl/scalar-functions/string-functions/countof-regex): Counts regex pattern occurrences. Use this when you only need the count of matches, not the actual matched text.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `rex` with `max_match=0` to extract all matches. APL's `extract_all` provides a more direct approach.
```sql Splunk example
| rex field=message max_match=0 "error_(?\d+)"
| mvexpand code
```
```kusto APL equivalent
['sample-http-logs']
| extend codes = extract_all('error_(\\d+)', dynamic([1]), uri)
```
In ANSI SQL, extracting all regex matches typically requires recursive queries or database-specific functions. APL's `extract_all` simplifies this operation.
```sql SQL example
SELECT REGEXP_EXTRACT_ALL(field, 'pattern') AS matches FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend matches = extract_all('pattern', dynamic([1]), field)
```
---
# extract
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/extract
The `extract` function retrieves the first substring that matches a regular expression from a source string. Use this function when you need to pull out specific patterns from log messages, URLs, or any text field using regex capture groups.
## Usage [#usage]
### Syntax [#syntax]
```kusto
extract(regex, captureGroup, text)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------- |
| regex | string | Yes | A regular expression pattern with optional capture groups. |
| captureGroup | int | Yes | The capture group to extract. Use 0 for the entire match, 1 for the first group, 2 for the second, etc. |
| text | string | Yes | The source string to search. |
### Returns [#returns]
Returns the substring matched by the specified capture group, or null if no match is found.
## Use case examples [#use-case-examples]
Extract user IDs from HTTP request URIs to identify which users are accessing specific endpoints.
**Query**
```kusto
['sample-http-logs']
| extend user_id = extract('/users/([0-9]+)', 1, uri)
| where isnotempty(user_id)
| summarize request_count = count() by user_id, method
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20user_id%20%3D%20extract\(%27%2Fusers%2F\(%5B0-9%5D%2B\)%27%2C%201%2C%20uri\)%20%7C%20where%20isnotempty\(user_id\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20user_id%2C%20method%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| user\_id | method | request\_count |
| -------- | ------ | -------------- |
| 12345 | GET | 234 |
| 67890 | POST | 187 |
| 11111 | GET | 156 |
| 22222 | PUT | 98 |
This query extracts numeric user IDs from URIs like '/users/12345' using a regex capture group, helping analyze per-user API usage patterns.
Extract version numbers from service names to track which service versions are running.
**Query**
```kusto
['otel-demo-traces']
| extend version = extract('v([0-9]+[.][0-9]+)', 1, ['service.name'])
| where isnotempty(version)
| summarize span_count = count() by ['service.name'], version
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20version%20%3D%20extract\(%27v\(%5B0-9%5D%2B%5B.%5D%5B0-9%5D%2B\)%27%2C%201%2C%20%5B%27service.name%27%5D\)%20%7C%20where%20isnotempty\(version\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20%5B%27service.name%27%5D%2C%20version%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| service.name | version | span\_count |
| ------------- | ------- | ----------- |
| frontend-v2.1 | 2.1 | 3456 |
| checkout-v1.5 | 1.5 | 2341 |
| cart-v3.0 | 3.0 | 1987 |
This query extracts version numbers from service names, helping track which versions of services are generating traces.
Extract IP addresses from URIs or request headers to identify the source of suspicious requests.
**Query**
```kusto
['sample-http-logs']
| extend ip_address = extract('([0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3})', 1, uri)
| where status == '403' or status == '401'
| where isnotempty(ip_address)
| summarize failed_attempts = count() by ip_address, status
| sort by failed_attempts desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20ip_address%20%3D%20extract\(%27\(%5B0-9%5D%7B1%2C3%7D%5B.%5D%5B0-9%5D%7B1%2C3%7D%5B.%5D%5B0-9%5D%7B1%2C3%7D%5B.%5D%5B0-9%5D%7B1%2C3%7D\)%27%2C%201%2C%20uri\)%20%7C%20where%20status%20%3D%3D%20%27403%27%20or%20status%20%3D%3D%20%27401%27%20%7C%20where%20isnotempty\(ip_address\)%20%7C%20summarize%20failed_attempts%20%3D%20count\(\)%20by%20ip_address%2C%20status%20%7C%20sort%20by%20failed_attempts%20desc%20%7C%20limit%2010%22%7D)
**Output**
| ip\_address | status | failed\_attempts |
| ------------- | ------ | ---------------- |
| 192.168.1.100 | 401 | 45 |
| 10.0.0.25 | 403 | 32 |
| 172.16.0.50 | 401 | 28 |
This query extracts IP addresses embedded in URIs from failed authentication requests, helping identify potential attackers or misconfigured systems.
## List of related functions [#list-of-related-functions]
* [extract\_all](/apl/scalar-functions/string-functions/extract-all): Extracts all matches of a regex pattern. Use this when you need multiple matches instead of just the first one.
* [parse\_json](/apl/scalar-functions/string-functions/parse-json): Parses JSON strings into dynamic objects. Use this when working with structured JSON data rather than regex patterns.
* [split](/apl/scalar-functions/string-functions/split): Splits strings by a delimiter. Use this for simpler tokenization without regex complexity.
* [replace\_regex](/apl/scalar-functions/string-functions/replace-regex): Replaces regex matches with new text. Use this when you need to modify matched patterns rather than extract them.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `rex` with named or numbered groups. APL's `extract` is similar but uses a numbered capture group parameter.
```sql Splunk example
| rex field=message "user=(?\w+)"
```
```kusto APL equivalent
['sample-http-logs']
| extend username = extract('user=([A-Za-z0-9_]+)', 1, uri)
```
In ANSI SQL, regex extraction varies by database. APL's `extract` provides a consistent approach across all data.
```sql SQL example
SELECT REGEXP_SUBSTR(field, 'pattern', 1, 1, NULL, 1) AS extracted FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend extracted = extract('pattern', 1, field)
```
---
# format_bytes
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/format-bytes
The `format_bytes` function formats a numeric value as a human-readable string representing data size in bytes with appropriate units (KB, MB, GB, etc.). Use this function to make byte values more readable in reports, dashboards, and log analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
format_bytes(value, precision, units, base)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| value | number | Yes | The numeric value representing bytes to format. |
| precision | number | No | Number of decimal places (default: 0). |
| units | string | No | Target units. If omitted, units are auto-selected. Base 2 suffixes: `Bytes`, `KiB`, `KB`, `MiB`, `MB`, `GiB`, `GB`, `TiB`, `TB`, `PiB`, `EiB`, `ZiB`, `YiB`. Base 10 suffixes: `kB`, `MB`, `GB`, `TB`, `PB`, `EB`, `ZB`, `YB`. |
| base | number | No | Either 2 (default, 1024-based) or 10 (1000-based) for unit calculations. |
### Returns [#returns]
Returns a formatted string representing the byte value with appropriate units.
## Use case examples [#use-case-examples]
Format response header sizes as human-readable values for better analysis of payload patterns.
**Query**
```kusto
['sample-http-logs']
| extend formatted_size = format_bytes(resp_header_size_bytes, 2)
| summarize avg_size = avg(resp_header_size_bytes), formatted_avg = format_bytes(toint(avg(resp_header_size_bytes)), 2) by status
| sort by avg_size desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20formatted_size%20%3D%20format_bytes\(resp_header_size_bytes%2C%202\)%20%7C%20summarize%20avg_size%20%3D%20avg\(resp_header_size_bytes\)%2C%20formatted_avg%20%3D%20format_bytes\(toint\(avg\(resp_header_size_bytes\)\)%2C%202\)%20by%20status%20%7C%20sort%20by%20avg_size%20desc%20%7C%20limit%2010%22%7D)
**Output**
| status | avg\_size | formatted\_avg |
| ------ | --------- | -------------- |
| 500 | 8765432 | 8.36 MB |
| 200 | 3456789 | 3.30 MB |
| 404 | 1234567 | 1.18 MB |
| 301 | 456789 | 446.08 KB |
This query formats average response header sizes by HTTP status code, making it easier to identify which status codes are associated with larger data transfers.
Format response header sizes for failed authentication attempts to identify potential data exfiltration or unusual payload patterns.
**Query**
```kusto
['sample-http-logs']
| where status == '403' or status == '401'
| extend formatted_size = format_bytes(resp_header_size_bytes, 1)
| summarize failed_attempts = count(), avg_size = format_bytes(toint(avg(resp_header_size_bytes)), 1) by status
| sort by failed_attempts desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20where%20status%20%3D%3D%20%27403%27%20or%20status%20%3D%3D%20%27401%27%20%7C%20extend%20formatted_size%20%3D%20format_bytes\(resp_header_size_bytes%2C%201\)%20%7C%20summarize%20failed_attempts%20%3D%20count\(\)%2C%20avg_size%20%3D%20format_bytes\(toint\(avg\(resp_header_size_bytes\)\)%2C%201\)%20by%20status%20%7C%20sort%20by%20failed_attempts%20desc%22%7D)
**Output**
| status | failed\_attempts | avg\_size |
| ------ | ---------------- | --------- |
| 401 | 1234 | 850.0 KB |
| 403 | 987 | 720.0 KB |
This query formats average response header sizes, helping identify unusual payload patterns that might indicate security issues.
## List of related functions [#list-of-related-functions]
* [parse\_bytes](/apl/scalar-functions/string-functions/parse-bytes): Parses a formatted byte string back to a numeric value. Use this to reverse the formatting operation.
* [strlen](/apl/scalar-functions/string-functions/strlen): Returns the length of a string in characters. Use this when you need character count rather than byte formatting.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically need custom eval expressions or lookup tables to format bytes. APL's `format_bytes` provides this functionality natively.
```sql Splunk example
| eval size_str=if(resp_header_size_bytes<1024, resp_header_size_bytes." B", if(resp_header_size_bytes<1048576, round(resp_header_size_bytes/1024,2)." KB", round(resp_header_size_bytes/1048576,2)." MB"))
```
```kusto APL equivalent
['sample-http-logs']
| extend size_str = format_bytes(resp_header_size_bytes)
```
In ANSI SQL, formatting bytes requires complex CASE statements. APL's `format_bytes` simplifies this operation.
```sql SQL example
SELECT CASE
WHEN resp_header_size_bytes < 1024 THEN CONCAT(resp_header_size_bytes, ' B')
WHEN resp_header_size_bytes < 1048576 THEN CONCAT(ROUND(resp_header_size_bytes/1024, 2), ' KB')
ELSE CONCAT(ROUND(resp_header_size_bytes/1048576, 2), ' MB')
END AS size_str FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend size_str = format_bytes(resp_header_size_bytes)
```
---
# format_url
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/format-url
The `format_url` function constructs a properly formatted URL from a dynamic object containing URL components (scheme, host, path, port, etc.). Use this function when you need to build URLs programmatically from parsed components or when reconstructing URLs from log data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
format_url(url_parts)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---------- | ------- | -------- | ------------------------------------------------------------------------------------------------------ |
| url\_parts | dynamic | Yes | A dynamic object containing URL components: scheme, host, path, port, fragment, user, password, query. |
### Returns [#returns]
Returns a properly formatted URL string constructed from the provided components.
## Use case examples [#use-case-examples]
Reconstruct full URLs from parsed components to analyze complete request patterns.
**Query**
```kusto
['sample-http-logs']
| extend full_url = format_url(dynamic({'scheme': 'https', 'host': 'api.example.com', 'path': uri}))
| project _time, method, status, full_url
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20full_url%20%3D%20format_url\(dynamic\(%7B%27scheme%27%3A%20%27https%27%2C%20%27host%27%3A%20%27api.example.com%27%2C%20%27path%27%3A%20uri%7D\)\)%20%7C%20project%20_time%2C%20method%2C%20status%2C%20full_url%20%7C%20limit%2010%22%7D)
**Output**
| \_time | method | status | full\_url |
| -------------------- | ------ | ------ | -------------------------------------- |
| 2024-11-06T10:00:00Z | GET | 200 | `https://api.example.com/api/users` |
| 2024-11-06T10:01:00Z | POST | 201 | `https://api.example.com/api/orders` |
| 2024-11-06T10:02:00Z | GET | 200 | `https://api.example.com/api/products` |
This query reconstructs full URLs from URI paths by adding the scheme and host, useful for generating clickable links in reports.
Build URLs from trace attributes to identify the full endpoints being called.
**Query**
```kusto
['otel-demo-traces']
| extend service_url = format_url(dynamic({'scheme': 'http', 'host': 'localhost', 'port': 8080, 'path': strcat('/', ['service.name'])}))
| project _time, ['service.name'], service_url, trace_id
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20service_url%20%3D%20format_url\(dynamic\(%7B%27scheme%27%3A%20%27http%27%2C%20%27host%27%3A%20%27localhost%27%2C%20%27port%27%3A%208080%2C%20%27path%27%3A%20strcat\(%27%2F%27%2C%20%5B%27service.name%27%5D\)%7D\)\)%20%7C%20project%20_time%2C%20%5B%27service.name%27%5D%2C%20service_url%2C%20trace_id%20%7C%20limit%2010%22%7D)
**Output**
| \_time | service.name | service\_url | trace\_id |
| -------------------- | ------------ | -------------------------------- | --------- |
| 2024-11-06T10:00:00Z | frontend | `http://localhost:8080/frontend` | abc123 |
| 2024-11-06T10:01:00Z | checkout | `http://localhost:8080/checkout` | def456 |
| 2024-11-06T10:02:00Z | cart | `http://localhost:8080/cart` | ghi789 |
This query constructs service URLs from trace data, helping visualize the actual endpoints in a distributed system.
Construct URLs with authentication parameters to audit access attempts.
**Query**
```kusto
['sample-http-logs']
| extend access_url = format_url(dynamic({'scheme': 'https', 'host': 'secure.example.com', 'path': uri, 'user': id}))
| project _time, access_url, status, ['geo.country']
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20access_url%20%3D%20format_url\(dynamic\(%7B'scheme'%3A%20'https'%2C%20'host'%3A%20'secure.example.com'%2C%20'path'%3A%20uri%2C%20'user'%3A%20id%7D\)\)%20%7C%20project%20_time%2C%20access_url%2C%20status%2C%20%5B'geo.country'%5D%20%7C%20limit%2010%22%7D)
**Output**
| \_time | access\_url | status | geo.country |
| -------------------- | ---------------------------------------------------------------------------------------------- | ------ | ------------- |
| 2024-11-06T10:00:00Z | [https://user123@secure.example.com/admin](https://user123@secure.example.com/admin) | 403 | United States |
| 2024-11-06T10:01:00Z | [https://user456@secure.example.com/api/secret](https://user456@secure.example.com/api/secret) | 401 | Unknown |
This query constructs complete URLs including user information for failed authentication attempts, helping security teams understand the full context of access attempts.
## List of related functions [#list-of-related-functions]
* [parse\_url](/apl/scalar-functions/string-functions/parse-url): Parses a URL string into its components. Use this to reverse the formatting operation and extract URL parts.
* [parse\_urlquery](/apl/scalar-functions/string-functions/parse-urlquery): Parses URL query parameters. Use this when you need to work with query string parameters specifically.
* [url\_encode](/apl/scalar-functions/string-functions/url-encode): Encodes a string for safe use in URLs. Use this to encode individual URL components before formatting.
* [strcat](/apl/scalar-functions/string-functions/strcat): Concatenates strings. Use this for simple URL construction without the structure of format\_url.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically concatenate URL parts manually with `eval`. APL's `format_url` provides a structured approach.
```sql Splunk example
| eval full_url=scheme."://".host.":".port.path
```
```kusto APL equivalent
['sample-http-logs']
| extend full_url = format_url(dynamic({'scheme': 'https', 'host': host, 'port': 443, 'path': path}))
```
In ANSI SQL, URL formatting requires string concatenation with null handling. APL's `format_url` simplifies this operation.
```sql SQL example
SELECT CONCAT(scheme, '://', host, COALESCE(CONCAT(':', port), ''), path) AS url FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend url = format_url(dynamic({'scheme': scheme, 'host': host, 'port': port, 'path': path}))
```
---
# gettype
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/gettype
The `gettype` function returns the runtime type of its argument as a string. Use this function when you need to determine the data type of fields, validate data structures, or debug type-related issues in your queries.
## Usage [#usage]
### Syntax [#syntax]
```kusto
gettype(expression)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---------- | ---- | -------- | ------------------------------------------------ |
| expression | any | Yes | The expression whose type you want to determine. |
### Returns [#returns]
Returns a string representing the runtime type: `string`, `int`, `long`, `real`, `bool`, `datetime`, `timespan`, `dynamic`, `array`, `dictionary`, or `null`.
## Use case examples [#use-case-examples]
Identify the data types of fields to ensure proper query operations and data validation.
**Query**
```kusto
['sample-http-logs']
| extend status_type = gettype(status),
duration_type = gettype(req_duration_ms),
time_type = gettype(_time)
| project status, status_type, req_duration_ms, duration_type, _time, time_type
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20status_type%20%3D%20gettype\(status\)%2C%20duration_type%20%3D%20gettype\(req_duration_ms\)%2C%20time_type%20%3D%20gettype\(_time\)%20%7C%20project%20status%2C%20status_type%2C%20req_duration_ms%2C%20duration_type%2C%20_time%2C%20time_type%20%7C%20limit%2010%22%7D)
**Output**
| status | status\_type | req\_duration\_ms | duration\_type | \_time | time\_type |
| ------ | ------------ | ----------------- | -------------- | -------------------- | ---------- |
| 200 | string | 145 | long | 2024-11-06T10:00:00Z | datetime |
| 404 | string | 89 | long | 2024-11-06T10:01:00Z | datetime |
| 500 | string | 234 | long | 2024-11-06T10:02:00Z | datetime |
This query identifies the data types of key fields in HTTP logs, helping ensure that data is in the expected format for analysis and troubleshooting type-related query issues.
Validate trace field types to ensure proper data ingestion and processing.
**Query**
```kusto
['otel-demo-traces']
| extend service_type = gettype(['service.name']),
duration_type = gettype(duration),
kind_type = gettype(kind)
| summarize type_counts = count() by service_type, duration_type, kind_type
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20service_type%20%3D%20gettype\(%5B%27service.name%27%5D\)%2C%20duration_type%20%3D%20gettype\(duration\)%2C%20kind_type%20%3D%20gettype\(kind\)%20%7C%20summarize%20type_counts%20%3D%20count\(\)%20by%20service_type%2C%20duration_type%2C%20kind_type%22%7D)
**Output**
| service\_type | duration\_type | kind\_type | type\_counts |
| ------------- | -------------- | ---------- | ------------ |
| string | timespan | string | 8765 |
This query validates the types of trace fields, helping identify data quality issues where fields might have unexpected types due to ingestion problems.
Detect type inconsistencies in security logs that might indicate data manipulation or logging errors.
**Query**
```kusto
['sample-http-logs']
| extend id_type = gettype(id),
status_type = gettype(status),
uri_type = gettype(uri)
| summarize failed_attempts = count() by id_type, status_type, uri_type
| sort by failed_attempts desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20id_type%20%3D%20gettype\(id\)%2C%20status_type%20%3D%20gettype\(status\)%2C%20uri_type%20%3D%20gettype\(uri\)%20%7C%20summarize%20failed_attempts%20%3D%20count\(\)%20by%20id_type%2C%20status_type%2C%20uri_type%20%7C%20sort%20by%20failed_attempts%20desc%22%7D)
**Output**
| id\_type | status\_type | uri\_type | failed\_attempts |
| -------- | ------------ | --------- | ---------------- |
| string | string | string | 2341 |
This query validates field types in failed authentication logs, helping detect anomalies where expected string fields might have different types due to injection attempts or data corruption.
## List of related functions [#list-of-related-functions]
* [isnull](/apl/scalar-functions/string-functions/isnull): Checks if a value is null. Use this to specifically test for null values rather than getting the type.
* [isnotnull](/apl/scalar-functions/string-functions/isnotnull): Checks if a value isn't null. Use this in filters when you need to exclude null values.
* [parse\_json](/apl/scalar-functions/string-functions/parse-json): Parses JSON strings into dynamic types. Use this before gettype when working with JSON data.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `typeof` to check types. APL's `gettype` provides similar functionality with consistent type names.
```sql Splunk example
| eval field_type=typeof(field_name)
```
```kusto APL equivalent
['sample-http-logs']
| extend field_type = gettype(field_name)
```
In ANSI SQL, type checking varies by database. APL's `gettype` provides a standardized approach to runtime type detection.
```sql SQL example
SELECT TYPEOF(field_name) AS field_type FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend field_type = gettype(field_name)
```
---
# indexof_regex
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/indexof-regex
Use the `indexof_regex` function to find the position of the first match of a regular expression in a string. The function is helpful when you want to locate a pattern within a larger text field and take action based on its position. For example, you can use `indexof_regex` to extract fields from semi-structured logs, validate string formats, or trigger alerts when specific patterns appear in log data.
The function returns the zero-based index of the first match. If no match is found, it returns `-1`. Use `indexof_regex` when you need more flexibility than simple substring search (`indexof`), especially when working with dynamic or non-fixed patterns.
All regex functions of APL use the [RE2 regex syntax](https://github.com/google/re2/wiki/Syntax).
## Usage [#usage]
### Syntax [#syntax]
```kusto
indexof_regex(string, match [, start [, occurrence [, length]]])
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------- |
| string | string | Yes | The input text to inspect. |
| match | string | Yes | The regular expression pattern to search for. |
| start | int | | The index in the string where to begin the search. If negative, the function starts that many characters from the end. |
| occurrence | int | | Which instance of the pattern to match. Defaults to `1` if not specified. |
| length | int | | The number of characters to search through. Use `-1` to search to the end of the string. |
### Returns [#returns]
The function returns the position (starting at zero) where the pattern first matches within the string. If the pattern isn’t found, the result is `-1`.
The function returns `null` in the following cases:
* The `start` value is negative.
* The `occurrence` value is less than 1.
* The `length` is set to a value below `-1`.
## Use case examples [#use-case-examples]
Use `indexof_regex` to detect whether the URI in a log entry contains an encoded user ID by checking for patterns like `user-[0-9]+`.
**Query**
```kusto
['sample-http-logs']
| extend user_id_pos = indexof_regex(uri, 'user-[0-9]+')
| where user_id_pos != -1
| project _time, id, uri, user_id_pos
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20user_id_pos%20%3D%20indexof_regex\(uri%2C%20'user-%5B0-9%5D%2B'\)%20%7C%20where%20user_id_pos%20!%3D%20-1%20%7C%20project%20_time%2C%20id%2C%20uri%2C%20user_id_pos%22%7D)
**Output**
| \_time | id | uri | user\_id\_pos |
| -------------------- | ------ | ------------------------ | ------------- |
| 2025-06-10T12:34:56Z | user42 | /api/user-12345/settings | 5 |
| 2025-06-10T12:35:07Z | user91 | /v2/user-6789/dashboard | 4 |
The query finds log entries where the URI contains a user ID pattern and shows the position of the match in the URI string.
Use `indexof_regex` to detect trace IDs that include a specific structure, such as four groups of hex digits.
**Query**
```kusto
['otel-demo-traces']
| extend match_index = indexof_regex(trace_id, '^[0-9a-f]{8}-[0-9a-f]{4}')
| where match_index == 0
| project _time, trace_id, match_index
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20match_index%20%3D%20indexof_regex\(trace_id%2C%20'%5E%5B0-9a-f%5D%7B8%7D-%5B0-9a-f%5D%7B4%7D'\)%20%7C%20where%20match_index%20%3D%3D%200%20%7C%20project%20_time%2C%20trace_id%2C%20match_index%22%7D)
**Output**
| \_time | trace\_id | match\_index |
| -------------------- | ------------------------------------ | ------------ |
| 2025-06-10T08:23:12Z | ab12cd34-1234-5678-9abc-def123456789 | 0 |
| 2025-06-10T08:24:55Z | fe98ba76-4321-abcd-8765-fedcba987654 | 0 |
This query finds spans where the trace ID begins with a specific regex pattern, helping validate span ID formatting.
Use `indexof_regex` to locate suspicious request patterns such as attempts to access system files (`/etc/passwd`).
**Query**
```kusto
['sample-http-logs']
| extend passwd_index = indexof_regex(uri, '/etc/passwd')
| where passwd_index != -1
| project _time, id, uri, passwd_index
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20passwd_index%20%3D%20indexof_regex\(uri%2C%20'%2Fetc%2Fpasswd'\)%20%7C%20where%20passwd_index%20!%3D%20-1%20%7C%20project%20_time%2C%20id%2C%20uri%2C%20passwd_index%22%7D)
**Output**
| \_time | id | uri | passwd\_index |
| -------------------- | ------ | ------------------------------ | ------------- |
| 2025-06-10T10:15:45Z | user88 | /cgi-bin/view?path=/etc/passwd | 20 |
This query detects HTTP requests attempting to access sensitive file paths, a common indicator of intrusion attempts.
## Other query languages [#other-query-languages]
Use `match()` in Splunk SPL to perform regular expression matching. However, `match()` returns a Boolean, not the match position. APL’s `indexof_regex` is similar to combining `match()` with additional logic to extract position, which isn’t natively supported in SPL.
```sql Splunk example
... | eval match_index=if(match(field, "pattern"), 0, -1)
```
```kusto APL equivalent
['dataset']
| extend match_index = indexof_regex(field, 'pattern')
```
ANSI SQL doesn’t have a built-in function to return the index of a regex match. You typically use `REGEXP_LIKE` for Boolean evaluation. `indexof_regex` provides a more direct and powerful way to find the exact match position in APL.
```sql SQL example
SELECT CASE WHEN REGEXP_LIKE(field, 'pattern') THEN 0 ELSE -1 END FROM table;
```
```kusto APL equivalent
['dataset']
| extend match_index = indexof_regex(field, 'pattern')
```
---
# indexof
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/indexof
The `indexof` function reports the zero-based index of the first occurrence of a specified string within an input string. Use this function to find the position of substrings, validate string formats, or extract parts of strings based on delimiter positions.
## Usage [#usage]
### Syntax [#syntax]
```kusto
indexof(source, lookup, start_index, length, occurrence)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------------ | ------ | -------- | ----------------------------------------------------------------------------- |
| source | string | Yes | The input string to search within. |
| lookup | string | Yes | The string to search for. |
| start\_index | int | No | The position to start searching from (default: 0). |
| length | int | No | Number of character positions to examine. Use -1 for unlimited (default: -1). |
| occurrence | int | No | The occurrence number to find (default: 1 for first occurrence). |
### Returns [#returns]
Returns the zero-based index position of the first occurrence of the lookup string, or -1 if not found.
## Use case examples [#use-case-examples]
Find the position of API version indicators in URIs to categorize and analyze API usage patterns.
**Query**
```kusto
['sample-http-logs']
| extend api_pos = indexof(uri, '/api/')
| where api_pos >= 0
| extend has_version = indexof(uri, '/v', api_pos)
| project _time, uri, api_pos, has_version, method, status
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20api_pos%20%3D%20indexof\(uri%2C%20%27%2Fapi%2F%27\)%20%7C%20where%20api_pos%20%3E%3D%200%20%7C%20extend%20has_version%20%3D%20indexof\(uri%2C%20%27%2Fv%27%2C%20api_pos\)%20%7C%20project%20_time%2C%20uri%2C%20api_pos%2C%20has_version%2C%20method%2C%20status%20%7C%20limit%2010%22%7D)
**Output**
| \_time | uri | api\_pos | has\_version | method | status |
| -------------------- | -------------- | -------- | ------------ | ------ | ------ |
| 2024-11-06T10:00:00Z | /api/v2/users | 0 | 4 | GET | 200 |
| 2024-11-06T10:01:00Z | /api/products | 0 | -1 | GET | 200 |
| 2024-11-06T10:02:00Z | /api/v1/orders | 0 | 4 | POST | 201 |
This query finds the position of API indicators in URIs, helping identify versioned versus unversioned API endpoints.
Locate service name delimiters to extract service identifiers from composite names.
**Query**
```kusto
['otel-demo-traces']
| extend dash_pos = indexof(['service.name'], '-')
| where dash_pos >= 0
| extend service_prefix = substring(['service.name'], 0, dash_pos)
| summarize span_count = count() by service_prefix
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20dash_pos%20%3D%20indexof\(%5B%27service.name%27%5D%2C%20%27-%27\)%20%7C%20where%20dash_pos%20%3E%3D%200%20%7C%20extend%20service_prefix%20%3D%20substring\(%5B%27service.name%27%5D%2C%200%2C%20dash_pos\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20service_prefix%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| service\_prefix | span\_count |
| --------------- | ----------- |
| otel | 8765 |
| service | 4321 |
| app | 2345 |
This query uses `indexof` to find delimiter positions in service names, enabling extraction of service prefixes for grouping and analysis.
Detect SQL injection attempts by finding the position of SQL keywords in URIs.
**Query**
```kusto
['sample-http-logs']
| extend union_pos = indexof(tolower(uri), 'union'),
select_pos = indexof(tolower(uri), 'select'),
drop_pos = indexof(tolower(uri), 'drop')
| where union_pos >= 0 or select_pos >= 0 or drop_pos >= 0
| project _time, uri, union_pos, select_pos, drop_pos, id, status, ['geo.country']
| sort by _time desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20union_pos%20%3D%20indexof\(tolower\(uri\)%2C%20%27union%27\)%2C%20select_pos%20%3D%20indexof\(tolower\(uri\)%2C%20%27select%27\)%2C%20drop_pos%20%3D%20indexof\(tolower\(uri\)%2C%20%27drop%27\)%20%7C%20where%20union_pos%20%3E%3D%200%20or%20select_pos%20%3E%3D%200%20or%20drop_pos%20%3E%3D%200%20%7C%20project%20_time%2C%20uri%2C%20union_pos%2C%20select_pos%2C%20drop_pos%2C%20id%2C%20status%2C%20%5B%27geo.country%27%5D%20%7C%20sort%20by%20_time%20desc%20%7C%20limit%2010%22%7D)
**Output**
| \_time | uri | union\_pos | select\_pos | drop\_pos | id | status | geo.country |
| -------------------- | ---------------------- | ---------- | ----------- | --------- | ------- | ------ | ----------- |
| 2024-11-06T10:00:00Z | /api?id=1'union select | -1 | 11 | -1 | user123 | 403 | Unknown |
| 2024-11-06T10:01:00Z | /search?q=drop table | -1 | -1 | 10 | user456 | 403 | Russia |
This query identifies potential SQL injection attempts by finding the position of SQL keywords in URIs, helping security teams detect and respond to attacks.
## List of related functions [#list-of-related-functions]
* [substring](/apl/scalar-functions/string-functions/substring): Extracts a substring from a source string. Use this together with indexof to extract parts of strings based on found positions.
* [strlen](/apl/scalar-functions/string-functions/strlen): Returns the length of a string. Use this with indexof to calculate positions relative to string length.
* [extract](/apl/scalar-functions/string-functions/extract): Extracts substrings using regular expressions. Use this when you need pattern matching instead of simple substring positions.
* [split](/apl/scalar-functions/string-functions/split): Splits strings by delimiters. Use this when you want to tokenize rather than find positions.
## Other query languages [#other-query-languages]
In Splunk SPL, you might use `searchmatch` or string manipulation. APL's `indexof` provides a direct way to find substring positions.
```sql Splunk example
| eval pos=if(match(field, "search"), strpos(field, "search"), -1)
```
```kusto APL equivalent
['sample-http-logs']
| extend pos = indexof(field, 'search')
```
In ANSI SQL, you use `POSITION()` or `INSTR()` to find substring positions. APL's `indexof` provides similar functionality with additional parameters.
```sql SQL example
SELECT POSITION('search' IN field) - 1 AS pos FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend pos = indexof(field, 'search')
```
---
# isascii
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/isascii
Use the `isascii` function to check whether a string contains only ASCII characters. It returns `true` if every character in the input string belongs to the ASCII character set (for example, character codes 0–127) and `false` otherwise.
The function is useful in scenarios where you want to detect non-ASCII text in logs, validate inputs for encoding compliance, or identify potential anomalies introduced by copy-pasted foreign characters or malformed input in user-submitted data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
isascii(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | ------ | ------------------------------------------- |
| value | string | The input string to check for ASCII content |
### Returns [#returns]
A `bool` value:
* `true` if all characters in `value` are ASCII characters.
* `false` if any character is outside the ASCII range.
## Use case examples [#use-case-examples]
Identify non-ASCII characters in request URIs to detect unusual or malformed traffic.
**Query**
```kusto
['sample-http-logs']
| extend is_ascii_uri = isascii(uri)
| summarize count() by is_ascii_uri
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20is_ascii_uri%20%3D%20isascii\(uri\)%20%7C%20summarize%20count\(\)%20by%20is_ascii_uri%22%7D)
**Output**
| is\_ascii\_uri | count\_ |
| -------------- | ------- |
| true | 14250 |
| false | 130 |
This query flags requests with non-ASCII characters in the `uri` field. These entries can indicate abnormal requests or encoding issues in log data.
Detect non-ASCII span IDs, which could indicate instrumentation bugs or encoding anomalies.
**Query**
```kusto
['otel-demo-traces']
| extend is_ascii_span_id = isascii(span_id)
| summarize count() by is_ascii_span_id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20is_ascii_span_id%20%3D%20isascii\(span_id\)%20%7C%20summarize%20count\(\)%20by%20is_ascii_span_id%22%7D)
**Output**
| is\_ascii\_span\_id | count\_ |
| ------------------- | ------- |
| true | 28700 |
| false | 2 |
This query validates that span IDs in your telemetry traces contain only ASCII characters. Non-ASCII values may hint at bugs or corrupted trace headers.
Identify requests where user IDs contain non-ASCII characters, which can help detect suspicious or malformed entries.
**Query**
```kusto
['sample-http-logs']
| extend is_ascii_id = isascii(id)
| summarize count() by is_ascii_id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20is_ascii_id%20%3D%20isascii\(id\)%20%7C%20summarize%20count\(\)%20by%20is_ascii_id%22%7D)
**Output**
| is\_ascii\_id | count |
| ------------- | ----- |
| true | 11900 |
| false | 350 |
This query detects potentially malicious or malformed user IDs by filtering for non-ASCII values in the `id` field.
## Other query languages [#other-query-languages]
Splunk SPL doesn’t have a direct equivalent to `isascii`. To achieve similar functionality, you typically need to use `match()` or custom regular expressions. In contrast, APL provides `isascii` as a simple built-in function, making the check much more concise and performant.
```sql Splunk example
... | eval is_ascii=if(match(field, "^[\x00-\x7F]+$"), "true", "false")
```
```kusto APL equivalent
datatable(input:string)
[
'hello',
'こんにちは'
]
| extend is_ascii = isascii(input)
```
ANSI SQL doesn’t provide a built-in `isascii` function. You often need to simulate it using regular expressions or character code checks. APL simplifies this with the dedicated `isascii` function.
```sql SQL example
SELECT input,
CASE WHEN input ~ '^[\x00-\x7F]+$' THEN true ELSE false END AS is_ascii
FROM my_table;
```
```kusto APL equivalent
datatable(input:string)
[
'hello',
'こんにちは'
]
| extend is_ascii = isascii(input)
```
---
# isempty
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/isempty
The `isempty` function returns true if the argument is an empty string or null. Use this function to filter out records with missing or empty string values, validate data completeness, or identify fields that need default values.
## Usage [#usage]
### Syntax [#syntax]
```kusto
isempty(value)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------ | -------- | ----------------------------------------- |
| value | scalar | Yes | The value to check for emptiness or null. |
### Returns [#returns]
Returns `true` if the value is an empty string or null, otherwise returns `false`.
## Use case examples [#use-case-examples]
Identify HTTP requests with missing or empty geographic information for data quality monitoring.
**Query**
```kusto
['sample-http-logs']
| extend has_empty_city = isempty(['geo.city']),
has_empty_country = isempty(['geo.country'])
| where has_empty_city or has_empty_country
| summarize incomplete_records = count() by has_empty_city, has_empty_country, status
| sort by incomplete_records desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20has_empty_city%20%3D%20isempty\(%5B%27geo.city%27%5D\)%2C%20has_empty_country%20%3D%20isempty\(%5B%27geo.country%27%5D\)%20%7C%20where%20has_empty_city%20or%20has_empty_country%20%7C%20summarize%20incomplete_records%20%3D%20count\(\)%20by%20has_empty_city%2C%20has_empty_country%2C%20status%20%7C%20sort%20by%20incomplete_records%20desc%22%7D)
**Output**
| has\_empty\_city | has\_empty\_country | status | incomplete\_records |
| ---------------- | ------------------- | ------ | ------------------- |
| true | false | 200 | 1234 |
| true | true | 404 | 567 |
| false | true | 500 | 234 |
This query identifies requests with incomplete geographic data, helping assess data quality and identify potential issues with geo-IP lookups.
Find traces with missing service information to identify instrumentation gaps.
**Query**
```kusto
['otel-demo-traces']
| extend empty_service = isempty(['service.name']),
empty_kind = isempty(kind)
| where empty_service or empty_kind
| summarize problematic_spans = count() by empty_service, empty_kind
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20empty_service%20%3D%20isempty\(%5B%27service.name%27%5D\)%2C%20empty_kind%20%3D%20isempty\(kind\)%20%7C%20where%20empty_service%20or%20empty_kind%20%7C%20summarize%20problematic_spans%20%3D%20count\(\)%20by%20empty_service%2C%20empty_kind%22%7D)
**Output**
| empty\_service | empty\_kind | problematic\_spans |
| -------------- | ----------- | ------------------ |
| false | true | 234 |
| true | false | 89 |
This query identifies spans with missing required fields, helping improve observability instrumentation by highlighting gaps in trace data.
Detect authentication attempts with missing user identifiers that might indicate anonymized or suspicious activity.
**Query**
```kusto
['sample-http-logs']
| where status == '401' or status == '403'
| extend empty_id = isempty(id)
| summarize failed_attempts = count(), empty_id_attempts = countif(empty_id) by status
| extend anonymous_percentage = round(100.0 * empty_id_attempts / failed_attempts, 2)
| sort by failed_attempts desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20where%20status%20%3D%3D%20%27401%27%20or%20status%20%3D%3D%20%27403%27%20%7C%20extend%20empty_id%20%3D%20isempty\(id\)%20%7C%20summarize%20failed_attempts%20%3D%20count\(\)%2C%20empty_id_attempts%20%3D%20countif\(empty_id\)%20by%20status%20%7C%20extend%20anonymous_percentage%20%3D%20round\(100.0%20*%20empty_id_attempts%20%2F%20failed_attempts%2C%202\)%20%7C%20sort%20by%20failed_attempts%20desc%22%7D)
**Output**
| status | failed\_attempts | empty\_id\_attempts | anonymous\_percentage |
| ------ | ---------------- | ------------------- | --------------------- |
| 401 | 1234 | 345 | 27.96 |
| 403 | 987 | 123 | 12.46 |
This query analyzes the percentage of failed authentication attempts without user IDs, helping security teams identify potential anonymous attack patterns.
## List of related functions [#list-of-related-functions]
* [isnotempty](/apl/scalar-functions/string-functions/isnotempty): Returns true if a value isn't empty and not null. Use this for the inverse check of isempty.
* [isnull](/apl/scalar-functions/string-functions/isnull): Checks only if a value is null. Use this when you specifically need to test for null without checking for empty strings.
* [coalesce](/apl/scalar-functions/string-functions/coalesce): Returns the first non-null or non-empty value. Use this to provide default values for empty fields.
* [strlen](/apl/scalar-functions/string-functions/strlen): Returns the length of a string. Use this when you need to check if a string has content beyond just emptiness.
## Other query languages [#other-query-languages]
In Splunk SPL, you check for empty values using conditions like `field=""` or `isnull(field)`. APL's `isempty` combines both checks.
```sql Splunk example
| where field="" OR isnull(field)
```
```kusto APL equivalent
['sample-http-logs']
| where isempty(field)
```
In ANSI SQL, you check for empty or null values using separate conditions. APL's `isempty` provides a more concise approach.
```sql SQL example
SELECT * FROM logs WHERE field IS NULL OR field = '';
```
```kusto APL equivalent
['sample-http-logs']
| where isempty(field)
```
---
# isnotempty
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/isnotempty
The `isnotempty` function returns true if the argument isn’t an empty string and isn’t null. Use this function to filter for records with valid, non-empty values, ensure data quality, or validate that required fields contain actual content.
## Usage [#usage]
### Syntax [#syntax]
```kusto
isnotempty(value)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------ | -------- | -------------------------------------------------- |
| value | scalar | Yes | The value to check for non-emptiness and non-null. |
### Returns [#returns]
Returns `true` if the value isn't an empty string and not null, otherwise returns `false`.
## Use case examples [#use-case-examples]
Filter HTTP logs to only include requests with valid geographic information for accurate location-based analytics.
**Query**
```kusto
['sample-http-logs']
| where isnotempty(['geo.city']) and isnotempty(['geo.country'])
| summarize request_count = count() by ['geo.city'], ['geo.country']
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20where%20isnotempty\(%5B%27geo.city%27%5D\)%20and%20isnotempty\(%5B%27geo.country%27%5D\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20%5B%27geo.city%27%5D%2C%20%5B%27geo.country%27%5D%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| geo.city | geo.country | request\_count |
| -------- | -------------- | -------------- |
| New York | United States | 2341 |
| London | United Kingdom | 1987 |
| Tokyo | Japan | 1654 |
| Paris | France | 1432 |
This query filters requests to only include those with complete geographic information, ensuring accurate location-based analysis without null or empty values.
Analyze only traces with complete service information to ensure accurate service performance metrics.
**Query**
```kusto
['otel-demo-traces']
| where isnotempty(['service.name']) and isnotempty(kind)
| summarize avg_duration = avg(duration), span_count = count() by ['service.name'], kind
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20where%20isnotempty\(%5B%27service.name%27%5D\)%20and%20isnotempty\(kind\)%20%7C%20summarize%20avg_duration%20%3D%20avg\(duration\)%2C%20span_count%20%3D%20count\(\)%20by%20%5B%27service.name%27%5D%2C%20kind%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| service.name | kind | avg\_duration | span\_count |
| ------------ | -------- | ------------- | ----------- |
| frontend | server | 125ms | 4532 |
| checkout | client | 89ms | 3421 |
| cart | internal | 56ms | 2987 |
This query filters traces to only include spans with complete service and kind information, ensuring reliable performance analysis without incomplete data.
Identify authenticated users by filtering out requests without valid user identifiers.
**Query**
```kusto
['sample-http-logs']
| extend authenticated = isnotempty(id)
| summarize total_attempts = count(), authenticated_attempts = countif(authenticated) by status
| extend authenticated_percentage = round(100.0 * authenticated_attempts / total_attempts, 2)
| sort by total_attempts desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20authenticated%20%3D%20isnotempty\(id\)%20%7C%20summarize%20total_attempts%20%3D%20count\(\)%2C%20authenticated_attempts%20%3D%20countif\(authenticated\)%20by%20status%20%7C%20extend%20authenticated_percentage%20%3D%20round\(100.0%20*%20authenticated_attempts%20%2F%20total_attempts%2C%202\)%20%7C%20sort%20by%20total_attempts%20desc%22%7D)
**Output**
| status | total\_attempts | authenticated\_attempts | authenticated\_percentage |
| ------ | --------------- | ----------------------- | ------------------------- |
| 401 | 1234 | 889 | 72.04 |
| 403 | 987 | 864 | 87.53 |
This query distinguishes between authenticated and anonymous failed access attempts by checking if user IDs are present, helping security teams understand attack patterns.
## List of related functions [#list-of-related-functions]
* [isempty](/apl/scalar-functions/string-functions/isempty): Returns true if a value is empty or null. Use this for the inverse check of isnotempty.
* [isnotnull](/apl/scalar-functions/string-functions/isnotnull): Checks only if a value isn't null. Use this when you specifically need to test for null without checking for empty strings.
* [strlen](/apl/scalar-functions/string-functions/strlen): Returns the length of a string. Use this when you need to ensure strings have minimum content length beyond just being non-empty.
* [coalesce](/apl/scalar-functions/string-functions/coalesce): Returns the first non-null or non-empty value. Use this to select from multiple fields or provide defaults.
## Other query languages [#other-query-languages]
In Splunk SPL, you check for non-empty values using conditions like `field!=""` and `isnotnull(field)`. APL's `isnotempty` combines both checks.
```sql Splunk example
| where field!="" AND isnotnull(field)
```
```kusto APL equivalent
['sample-http-logs']
| where isnotempty(field)
```
In ANSI SQL, you check for non-empty and non-null values using separate conditions. APL's `isnotempty` provides a more concise approach.
```sql SQL example
SELECT * FROM logs WHERE field IS NOT NULL AND field <> '';
```
```kusto APL equivalent
['sample-http-logs']
| where isnotempty(field)
```
---
# isnotnull
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/isnotnull
The `isnotnull` function returns true if the argument isn’t null. Use this function to filter for records with defined values, validate data presence, or distinguish between null and other values including empty strings.
## Usage [#usage]
### Syntax [#syntax]
```kusto
isnotnull(value)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------ | -------- | -------------------------------- |
| value | scalar | Yes | The value to check for non-null. |
### Returns [#returns]
Returns `true` if the value isn't null, otherwise returns `false`. Note that empty strings return `true` because they're not null.
## Use case examples [#use-case-examples]
Filter HTTP logs to only include requests where duration information is available for performance analysis.
**Query**
```kusto
['sample-http-logs']
| where isnotnull(req_duration_ms)
| summarize avg_duration = avg(req_duration_ms),
max_duration = max(req_duration_ms),
request_count = count() by status
| sort by avg_duration desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20where%20isnotnull\(req_duration_ms\)%20%7C%20summarize%20avg_duration%20%3D%20avg\(req_duration_ms\)%2C%20max_duration%20%3D%20max\(req_duration_ms\)%2C%20request_count%20%3D%20count\(\)%20by%20status%20%7C%20sort%20by%20avg_duration%20desc%20%7C%20limit%2010%22%7D)
**Output**
| status | avg\_duration | max\_duration | request\_count |
| ------ | ------------- | ------------- | -------------- |
| 500 | 987.5 | 5432 | 234 |
| 200 | 145.3 | 3421 | 8765 |
| 404 | 89.7 | 987 | 1234 |
This query filters to only include requests with duration data, ensuring accurate performance metrics without skewing calculations with null values.
Analyze traces with recorded durations to calculate accurate service performance metrics.
**Query**
```kusto
['otel-demo-traces']
| where isnotnull(duration)
| summarize p50_duration = percentile(duration, 50),
p95_duration = percentile(duration, 95),
trace_count = count() by ['service.name']
| sort by p95_duration desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20where%20isnotnull\(duration\)%20%7C%20summarize%20p50_duration%20%3D%20percentile\(duration%2C%2050\)%2C%20p95_duration%20%3D%20percentile\(duration%2C%2095\)%2C%20trace_count%20%3D%20count\(\)%20by%20%5B%27service.name%27%5D%20%7C%20sort%20by%20p95_duration%20desc%20%7C%20limit%2010%22%7D)
**Output**
| service.name | p50\_duration | p95\_duration | trace\_count |
| ------------ | ------------- | ------------- | ------------ |
| checkout | 234ms | 987ms | 3421 |
| frontend | 145ms | 654ms | 4532 |
| cart | 89ms | 456ms | 2987 |
This query ensures duration calculations are based only on spans with recorded timing data, preventing null values from affecting percentile calculations.
Track requests with identified users to analyze authenticated access patterns versus anonymous attempts.
**Query**
```kusto
['sample-http-logs']
| extend has_user_id = isnotnull(id)
| summarize requests_by_type = count() by has_user_id, status, ['geo.country']
| sort by requests_by_type desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20has_user_id%20%3D%20isnotnull\(id\)%20%7C%20summarize%20requests_by_type%20%3D%20count\(\)%20by%20has_user_id%2C%20status%2C%20%5B'geo.country'%5D%20%7C%20sort%20by%20requests_by_type%20desc%20%7C%20limit%2010%22%7D)
**Output**
| has\_user\_id | status | geo.country | requests\_by\_type |
| ------------- | ------ | ------------- | ------------------ |
| true | 401 | United States | 456 |
| true | 403 | Unknown | 345 |
| false | 401 | Russia | 234 |
| false | 403 | China | 123 |
This query distinguishes between authenticated and truly anonymous access attempts by checking for user ID presence, helping identify different attack patterns.
## List of related functions [#list-of-related-functions]
* [isnull](/apl/scalar-functions/string-functions/isnull): Returns true if a value is null. Use this for the inverse check of isnotnull.
* [isnotempty](/apl/scalar-functions/string-functions/isnotempty): Checks if a value isn't empty and not null. Use this when you need to ensure both conditions.
* [coalesce](/apl/scalar-functions/string-functions/coalesce): Returns the first non-null value from a list. Use this to provide default values for null fields.
* [gettype](/apl/scalar-functions/string-functions/gettype): Returns the type of a value. Use this to distinguish between null and other types.
## Other query languages [#other-query-languages]
In Splunk SPL, you check for non-null values using `isnotnull()` function. APL's `isnotnull` works the same way.
```sql Splunk example
| where isnotnull(field)
```
```kusto APL equivalent
['sample-http-logs']
| where isnotnull(field)
```
In ANSI SQL, you check for non-null values using `IS NOT NULL`. APL's `isnotnull` provides the same functionality with function syntax.
```sql SQL example
SELECT * FROM logs WHERE field IS NOT NULL;
```
```kusto APL equivalent
['sample-http-logs']
| where isnotnull(field)
```
---
# isnull
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/isnull
The `isnull` function evaluates its argument and returns true if the argument is null. Use this function to identify missing data, filter out incomplete records, or validate that optional fields are absent.
## Usage [#usage]
### Syntax [#syntax]
```kusto
isnull(value)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------ | -------- | ---------------------------- |
| value | scalar | Yes | The value to check for null. |
### Returns [#returns]
Returns `true` if the value is null, otherwise returns `false`. Note that empty strings return `false` because they're not null.
## Use case examples [#use-case-examples]
Identify HTTP requests with missing duration information to assess data quality and completeness.
**Query**
```kusto
['sample-http-logs']
| extend missing_duration = isnull(req_duration_ms)
| summarize total_requests = count(),
missing_duration_count = countif(missing_duration),
missing_percentage = round(100.0 * countif(missing_duration) / count(), 2) by status
| sort by missing_duration_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20missing_duration%20%3D%20isnull\(req_duration_ms\)%20%7C%20summarize%20total_requests%20%3D%20count\(\)%2C%20missing_duration_count%20%3D%20countif\(missing_duration\)%2C%20missing_percentage%20%3D%20round\(100.0%20*%20countif\(missing_duration\)%20%2F%20count\(\)%2C%202\)%20by%20status%20%7C%20sort%20by%20missing_duration_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| status | total\_requests | missing\_duration\_count | missing\_percentage |
| ------ | --------------- | ------------------------ | ------------------- |
| 500 | 1234 | 123 | 9.97 |
| 200 | 8765 | 87 | 0.99 |
| 404 | 2341 | 23 | 0.98 |
This query identifies the percentage of requests missing duration data by status code, helping assess logging infrastructure reliability and identify potential issues.
Find traces with missing duration information to identify instrumentation problems.
**Query**
```kusto
['otel-demo-traces']
| extend null_duration = isnull(duration)
| where null_duration
| summarize incomplete_spans = count() by ['service.name'], kind
| sort by incomplete_spans desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20null_duration%20%3D%20isnull\(duration\)%20%7C%20where%20null_duration%20%7C%20summarize%20incomplete_spans%20%3D%20count\(\)%20by%20%5B%27service.name%27%5D%2C%20kind%20%7C%20sort%20by%20incomplete_spans%20desc%20%7C%20limit%2010%22%7D)
**Output**
| service.name | kind | incomplete\_spans |
| --------------- | -------- | ----------------- |
| product-catalog | server | 234 |
| cart | internal | 123 |
| checkout | client | 89 |
This query identifies services with incomplete trace data, helping pinpoint instrumentation issues where duration information is not being captured properly.
Identify anonymous access attempts by finding requests without user identification.
**Query**
```kusto
['sample-http-logs']
| extend anonymous = isnull(id)
| summarize total_failures = count(),
anonymous_failures = countif(anonymous) by status, ['geo.country']
| extend anonymous_rate = round(100.0 * anonymous_failures / total_failures, 2)
| where anonymous_failures > 10
| sort by anonymous_failures desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20anonymous%20%3D%20isnull\(id\)%20%7C%20summarize%20total_failures%20%3D%20count\(\)%2C%20anonymous_failures%20%3D%20countif\(anonymous\)%20by%20status%2C%20%5B'geo.country'%5D%20%7C%20extend%20anonymous_rate%20%3D%20round\(100.0%20*%20anonymous_failures%20%2F%20total_failures%2C%202\)%20%7C%20where%20anonymous_failures%20%3E%2010%20%7C%20sort%20by%20anonymous_failures%20desc%20%7C%20limit%2010%22%7D)
**Output**
| status | geo.country | total\_failures | anonymous\_failures | anonymous\_rate |
| ------ | ----------- | --------------- | ------------------- | --------------- |
| 401 | Unknown | 567 | 345 | 60.85 |
| 403 | Russia | 234 | 189 | 80.77 |
| 401 | China | 198 | 156 | 78.79 |
This query identifies patterns of anonymous failed access attempts by country, helping security teams detect automated attacks or scanning activity.
## List of related functions [#list-of-related-functions]
* [isnotnull](/apl/scalar-functions/string-functions/isnotnull): Returns true if a value isn't null. Use this for the inverse check of isnull.
* [isempty](/apl/scalar-functions/string-functions/isempty): Checks if a value is empty or null. Use this when you need to check for both null and empty strings.
* [coalesce](/apl/scalar-functions/string-functions/coalesce): Returns the first non-null value from a list. Use this to provide default values for null fields.
* [gettype](/apl/scalar-functions/string-functions/gettype): Returns the type of a value. Use this to distinguish between null and other types.
## Other query languages [#other-query-languages]
In Splunk SPL, you check for null values using `isnull()` function. APL's `isnull` works the same way.
```sql Splunk example
| where isnull(field)
```
```kusto APL equivalent
['sample-http-logs']
| where isnull(field)
```
In ANSI SQL, you check for null values using `IS NULL`. APL's `isnull` provides the same functionality with function syntax.
```sql SQL example
SELECT * FROM logs WHERE field IS NULL;
```
```kusto APL equivalent
['sample-http-logs']
| where isnull(field)
```
---
# parse_bytes
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/parse-bytes
The `parse_bytes` function parses a string representation of data size (like `1 KB`, `500 MB`) and returns the numeric value in bytes. Use this function to convert human-readable byte strings from logs or configuration files into numeric values for calculations and comparisons.
## Usage [#usage]
### Syntax [#syntax]
```kusto
parse_bytes(bytes_string, base)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------------- | ------ | -------- | ------------------------------------------------------------------------------------- |
| bytes\_string | string | Yes | A string representing a data size with units (for example, `1 KB`, `500 MB`, `2 GB`). |
| base | int | No | Either 2 (default, 1024-based) or 10 (1000-based) for unit calculations. |
### Returns [#returns]
Returns the numeric value in bytes, or 0 if the string can't be parsed.
## Use case examples [#use-case-examples]
Parse human-readable size strings to analyze and aggregate data transfer volumes.
**Query**
```kusto
['sample-http-logs']
| extend size_bytes = parse_bytes('512 KB')
| where req_duration_ms > 3
| summarize total_bytes = sum(size_bytes), request_count = count() by status
| sort by total_bytes desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20size_bytes%20%3D%20parse_bytes\(%27512%20KB%27\)%20%7C%20where%20req_duration_ms%20%3E%203%20%7C%20summarize%20total_bytes%20%3D%20sum\(size_bytes\)%2C%20request_count%20%3D%20count\(\)%20by%20status%20%7C%20sort%20by%20total_bytes%20desc%20%7C%20limit%2010%22%7D)
**Output**
| status | total\_bytes | request\_count |
| ------ | ------------ | -------------- |
| 200 | 4587520 | 8765 |
| 500 | 1048576 | 2341 |
| 404 | 524288 | 1234 |
This query parses size strings to calculate total data transfer by HTTP status code, enabling volume-based analysis of API usage.
Convert size strings from span attributes to numeric bytes for threshold-based analysis.
**Query**
```kusto
['otel-demo-traces']
| extend payload_size = parse_bytes('256 KB', 2)
| where duration > 100ms
| summarize avg_size = avg(payload_size), span_count = count() by ['service.name']
| sort by avg_size desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20payload_size%20%3D%20parse_bytes\(%27256%20KB%27%2C%202\)%20%7C%20where%20duration%20%3E%20100ms%20%7C%20summarize%20avg_size%20%3D%20avg\(payload_size\)%2C%20span_count%20%3D%20count\(\)%20by%20%5B%27service.name%27%5D%20%7C%20sort%20by%20avg_size%20desc%20%7C%20limit%2010%22%7D)
**Output**
| service.name | avg\_size | span\_count |
| ------------ | --------- | ----------- |
| checkout | 262144 | 3421 |
| frontend | 262144 | 4532 |
| cart | 262144 | 2987 |
This query converts size strings to bytes for numeric analysis of payload sizes across different services.
## List of related functions [#list-of-related-functions]
* [format\_bytes](/apl/scalar-functions/string-functions/format-bytes): Formats numeric bytes as human-readable strings. Use this to reverse the parsing operation.
* [strlen](/apl/scalar-functions/string-functions/strlen): Returns the length of a string. Use this when you need string length rather than byte parsing.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically need custom eval expressions to parse byte strings. APL's `parse_bytes` provides this functionality natively.
```sql Splunk example
| eval bytes=case(
match(size_str, "KB"), tonumber(replace(size_str, " KB", "")) * 1024,
match(size_str, "MB"), tonumber(replace(size_str, " MB", "")) * 1048576,
true(), 0)
```
```kusto APL equivalent
['sample-http-logs']
| extend bytes = parse_bytes(size_str)
```
In ANSI SQL, parsing byte strings requires complex CASE statements. APL's `parse_bytes` simplifies this operation.
```sql SQL example
SELECT CASE
WHEN size_str LIKE '%KB%' THEN CAST(REPLACE(size_str, ' KB', '') AS FLOAT) * 1024
WHEN size_str LIKE '%MB%' THEN CAST(REPLACE(size_str, ' MB', '') AS FLOAT) * 1048576
ELSE 0
END AS bytes FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend bytes = parse_bytes(size_str)
```
---
# parse_csv
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/parse-csv
The `parse_csv` function splits a comma-separated values (CSV) string into an array of strings. Use this function to parse CSV-formatted log entries, configuration values, or any comma-delimited data into individual values for analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
parse_csv(csv_text)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| --------- | ------ | -------- | ---------------------------------------------------- |
| csv\_text | string | Yes | A string containing comma-separated values to parse. |
### Returns [#returns]
Returns a string array containing the individual values from the CSV string. Properly handles quoted values and escaped characters.
## Use case examples [#use-case-examples]
Parse comma-separated status codes or error types from log messages.
**Query**
```kusto
['sample-http-logs']
| extend status_list = parse_csv('200,201,204,304')
| extend is_success = status in (status_list)
| summarize request_count = count() by is_success, status
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20status_list%20%3D%20parse_csv\(%27200%2C201%2C204%2C304%27\)%20%7C%20extend%20is_success%20%3D%20status%20in%20\(status_list\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20is_success%2C%20status%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| is\_success | status | request\_count |
| ----------- | ------ | -------------- |
| true | 200 | 8765 |
| false | 404 | 2341 |
| false | 500 | 1234 |
| true | 304 | 987 |
This query parses a CSV list of success status codes and categorizes requests accordingly.
Parse comma-separated service lists from trace attributes or configuration.
**Query**
```kusto
['otel-demo-traces']
| extend service_list = parse_csv('frontend,checkout,cart')
| extend is_monitored = ['service.name'] in (service_list)
| summarize span_count = count() by ['service.name'], is_monitored
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20service_list%20%3D%20parse_csv\(%27frontend%2Ccheckout%2Ccart%27\)%20%7C%20extend%20is_monitored%20%3D%20%5B%27service.name%27%5D%20in%20\(service_list\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20%5B%27service.name%27%5D%2C%20is_monitored%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| service.name | is\_monitored | span\_count |
| --------------- | ------------- | ----------- |
| frontend | true | 4532 |
| checkout | true | 3421 |
| cart | true | 2987 |
| product-catalog | false | 2341 |
This query parses a CSV list of monitored services and identifies which services are included in the monitoring scope.
Parse comma-separated allowlists or blocklists for security rule evaluation.
**Query**
```kusto
['sample-http-logs']
| extend blocked_ips = parse_csv('192.168.1.100,10.0.0.25,172.16.0.50')
| extend simulated_ip = '192.168.1.100'
| extend is_blocked = simulated_ip in (blocked_ips)
| where is_blocked
| summarize blocked_attempts = count() by status, ['geo.country']
| sort by blocked_attempts desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20blocked_ips%20%3D%20parse_csv\(%27192.168.1.100%2C10.0.0.25%2C172.16.0.50%27\)%20%7C%20extend%20simulated_ip%20%3D%20%27192.168.1.100%27%20%7C%20extend%20is_blocked%20%3D%20simulated_ip%20in%20\(blocked_ips\)%20%7C%20where%20is_blocked%20%7C%20summarize%20blocked_attempts%20%3D%20count\(\)%20by%20status%2C%20%5B%27geo.country%27%5D%20%7C%20sort%20by%20blocked_attempts%20desc%20%7C%20limit%2010%22%7D)
**Output**
| status | geo.country | blocked\_attempts |
| ------ | ----------- | ----------------- |
| 403 | Unknown | 234 |
| 401 | Russia | 123 |
This query parses a CSV blocklist and identifies requests from blocked IP addresses for security monitoring.
## List of related functions [#list-of-related-functions]
* [split](/apl/scalar-functions/string-functions/split): Splits strings by any delimiter. Use this when working with non-CSV delimiters or when quote handling isn't needed.
* [parse\_json](/apl/scalar-functions/string-functions/parse-json): Parses JSON strings into dynamic objects. Use this when working with JSON arrays rather than CSV.
* [strcat\_delim](/apl/scalar-functions/string-functions/strcat-delim): Concatenates strings with delimiters. Use this to create CSV strings from individual values.
* [extract\_all](/apl/scalar-functions/string-functions/extract-all): Extracts multiple regex matches. Use this for more complex parsing patterns beyond CSV.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `rex` or the `split` function to parse CSV. APL's `parse_csv` provides proper CSV parsing with quote handling.
```sql Splunk example
| makemv delim="," field_name
```
```kusto APL equivalent
['sample-http-logs']
| extend values = parse_csv(field_name)
```
In ANSI SQL, parsing CSV requires string splitting functions that vary by database. APL's `parse_csv` provides standardized CSV parsing.
```sql SQL example
SELECT STRING_TO_ARRAY(field_name, ',') AS values FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend values = parse_csv(field_name)
```
---
# parse_json
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/parse-json
The `parse_json` function interprets a string as JSON and returns the value as a dynamic object. Use this function to extract structured data from JSON-formatted log entries, API responses, or configuration values stored as JSON strings.
## Usage [#usage]
### Syntax [#syntax]
```kusto
parse_json(json_string)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------------ | ------ | -------- | ---------------------------------------- |
| json\_string | string | Yes | A string containing valid JSON to parse. |
### Returns [#returns]
Returns a dynamic object representing the parsed JSON. If the JSON is invalid, returns the original string.
## Use case examples [#use-case-examples]
Parse JSON-formatted log messages to extract specific fields for analysis.
**Query**
```kusto
['sample-http-logs']
| extend json_data = parse_json('{"response_time": 145, "cache_hit": true, "endpoint": "/api/users"}')
| extend response_time = toint(json_data.response_time)
| extend cache_hit = tobool(json_data.cache_hit)
| extend endpoint = tostring(json_data.endpoint)
| project _time, response_time, cache_hit, endpoint, status
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20json_data%20%3D%20parse_json\(%27%7B%5C%22response_time%5C%22%3A%20145%2C%20%5C%22cache_hit%5C%22%3A%20true%2C%20%5C%22endpoint%5C%22%3A%20%5C%22%2Fapi%2Fusers%5C%22%7D%27\)%20%7C%20extend%20response_time%20%3D%20toint\(json_data.response_time\)%20%7C%20extend%20cache_hit%20%3D%20tobool\(json_data.cache_hit\)%20%7C%20extend%20endpoint%20%3D%20tostring\(json_data.endpoint\)%20%7C%20project%20_time%2C%20response_time%2C%20cache_hit%2C%20endpoint%2C%20status%20%7C%20limit%2010%22%7D)
**Output**
| \_time | response\_time | cache\_hit | endpoint | status |
| -------------------- | -------------- | ---------- | ---------- | ------ |
| 2024-11-06T10:00:00Z | 145 | true | /api/users | 200 |
| 2024-11-06T10:01:00Z | 145 | true | /api/users | 200 |
This query parses JSON-formatted metadata from logs to extract performance metrics like response time and cache hit status.
Extract structured attributes from JSON-formatted span data.
**Query**
```kusto
['otel-demo-traces']
| extend attrs = parse_json('{"http.method": "GET", "http.status_code": 200, "user.id": "12345"}')
| extend http_method = tostring(attrs['http.method'])
| extend http_status = toint(attrs['http.status_code'])
| extend user_id = tostring(attrs['user.id'])
| summarize span_count = count() by http_method, http_status
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20attrs%20%3D%20parse_json\(%27%7B%5C%22http.method%5C%22%3A%20%5C%22GET%5C%22%2C%20%5C%22http.status_code%5C%22%3A%20200%2C%20%5C%22user.id%5C%22%3A%20%5C%2212345%5C%22%7D%27\)%20%7C%20extend%20http_method%20%3D%20tostring\(attrs%5B%27http.method%27%5D\)%20%7C%20extend%20http_status%20%3D%20toint\(attrs%5B%27http.status_code%27%5D\)%20%7C%20extend%20user_id%20%3D%20tostring\(attrs%5B%27user.id%27%5D\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20http_method%2C%20http_status%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| http\_method | http\_status | span\_count |
| ------------ | ------------ | ----------- |
| GET | 200 | 8765 |
This query parses JSON attributes from OpenTelemetry spans to analyze HTTP request patterns.
Parse JSON-formatted security events to extract threat indicators.
**Query**
```kusto
['sample-http-logs']
| extend security_data = parse_json('{"threat_level": "high", "attack_type": "sql_injection", "blocked": true}')
| extend threat_level = tostring(security_data.threat_level)
| extend attack_type = tostring(security_data.attack_type)
| extend blocked = tobool(security_data.blocked)
| project _time, uri, threat_level, attack_type, blocked, id, ['geo.country']
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20security_data%20%3D%20parse_json\(%27%7B%5C%22threat_level%5C%22%3A%20%5C%22high%5C%22%2C%20%5C%22attack_type%5C%22%3A%20%5C%22sql_injection%5C%22%2C%20%5C%22blocked%5C%22%3A%20true%7D%27\)%20%7C%20extend%20threat_level%20%3D%20tostring\(security_data.threat_level\)%20%7C%20extend%20attack_type%20%3D%20tostring\(security_data.attack_type\)%20%7C%20extend%20blocked%20%3D%20tobool\(security_data.blocked\)%20%7C%20project%20_time%2C%20uri%2C%20threat_level%2C%20attack_type%2C%20blocked%2C%20id%2C%20%5B%27geo.country%27%5D%20%7C%20limit%2010%22%7D)
**Output**
| \_time | uri | threat\_level | attack\_type | blocked | id | geo.country |
| -------------------- | ---------- | ------------- | -------------- | ------- | ------- | ----------- |
| 2024-11-06T10:00:00Z | /api/users | high | sql\_injection | true | user123 | Unknown |
| 2024-11-06T10:01:00Z | /admin | high | sql\_injection | true | user456 | Russia |
This query parses JSON-formatted security events to extract and analyze threat information from failed access attempts.
## Best practices [#best-practices]
When working with JSON data in Axiom, consider the following best practices:
* **Prefer structured ingestion over runtime parsing:** If possible, structure your JSON data as separate fields during ingestion rather than storing it as a stringified JSON object. This provides better query performance and enables indexing on nested fields.
* **Use map fields for nested data:** For nested or unpredictable JSON structures, consider using [map fields](/apl/data-types/map-fields) instead of stringified JSON. Map fields allow you to query nested properties directly without using `parse_json` at query time.
* **Avoid mixed types:** When logging JSON data, ensure consistent field types across events. Mixed types (for example, sometimes a string, sometimes a number) can cause query issues. Use type conversion functions like `toint` or `tostring` when necessary.
* **Performance considerations:** Using `parse_json` at query time adds CPU overhead. For frequently queried JSON data, consider parsing during ingestion or using map fields for better performance.
## List of related functions [#list-of-related-functions]
* [parse\_url](/apl/scalar-functions/string-functions/parse-url): Parses URLs into components. Use this specifically for URL parsing rather than general JSON.
* [parse\_csv](/apl/scalar-functions/string-functions/parse-csv): Parses CSV strings. Use this for comma-separated values rather than JSON.
* [todynamic](/apl/scalar-functions/conversion-functions/todynamic): Alias for parse\_json. Use either name based on your preference.
* [gettype](/apl/scalar-functions/string-functions/gettype): Returns the type of a value. Use this to check the types of parsed JSON fields.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `spath` to parse JSON. APL's `parse_json` provides similar functionality with dynamic object support.
```sql Splunk example
| spath input=json_field
| rename "field.name" as extracted_value
```
```kusto APL equivalent
['sample-http-logs']
| extend parsed = parse_json(json_field)
| extend extracted_value = parsed['field']['name']
```
In ANSI SQL, JSON parsing varies by database with different functions. APL's `parse_json` provides standardized JSON parsing.
```sql SQL example
SELECT JSON_EXTRACT(json_field, '$.field.name') AS extracted_value FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend parsed = parse_json(json_field)
| extend extracted_value = parsed.field.name
```
---
# parse_path
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/parse-path
Use the `parse_path` function to extract structured components from file paths, URIs, or URLs in your log and trace data. This function is useful when you want to decompose a full path into individual segments such as the directory, filename, extension, or query parameters for easier filtering, aggregation, or analysis.
You typically use `parse_path` in log analysis, OpenTelemetry traces, and security investigations to understand which resources are being accessed, identify routing patterns, or isolate endpoints with high error rates. It simplifies complex string parsing tasks and helps you normalize paths for comparisons and reporting.
## Usage [#usage]
### Syntax [#syntax]
```kusto
parse_path(source)
```
### Parameters [#parameters]
| Name | Type | Description |
| ------ | ------ | ---------------------------------------------------- |
| source | string | A string representing a path, file URI, or full URL. |
### Returns [#returns]
Returns a dynamic object with the following fields:
* Scheme
* RootPath
* DirectoryPath
* DirectoryName
* Filename
* Extension
* AlternateDataStreamName
## Use case example [#use-case-example]
Extract endpoint directories and file extensions from HTTP request URIs.
**Query**
```kusto
['sample-http-logs']
| extend path_parts = parse_path(uri)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20path_parts%20%3D%20parse_path\(uri\)%20%7C%20project%20_time%2C%20path_parts%22%7D)
**Output**
| \_time | path\_parts |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Jun 11, 10:39:16 | `{ "Filename": "users", "RootPath": "", "Scheme": "", "AlternateDataStream": "", "DirectoryName": "messages", "DirectoryPath": "/api/v1/messages", "Extension": "" }` |
| Jun 11, 10:39:16 | `{ "Scheme": "", "AlternateDataStream": "", "DirectoryName": "background", "DirectoryPath": "/api/v1/textdata/background", "Extension": "", "Filename": "change", "RootPath": "" }` |
| Jun 11, 10:39:16 | `{ "Filename": "users", "RootPath": "", "Scheme": "", "AlternateDataStream": "", "DirectoryName": "textdata", "DirectoryPath": "/api/v1/textdata", "Extension": "" }` |
This query helps you identify which directories and file types receive the most traffic.
## Other query languages [#other-query-languages]
In Splunk SPL, path or URL parsing often involves a combination of `spath`, `rex`, and `spath` field access logic. You typically write regular expressions or JSONPath selectors manually.
In APL, `parse_path` handles common URL and path structures for you automatically. It returns a dynamic object with fields like `directory`, `basename`, `extension`, `query`, and others.
```sql Splunk example
... | rex field=uri "(?\/api\/v1\/[^\?]+)"
```
```kusto APL equivalent
['sample-http-logs']
| extend path_parts = parse_path(uri)
| extend endpoint = path_parts.directory
```
ANSI SQL doesn’t have a built-in function for parsing structured paths. You often use a combination of `SUBSTRING`, `CHARINDEX`, or user-defined functions.
APL simplifies this task with `parse_path`, which returns a structured object from a URI or file path, removing the need for manual string manipulation.
```sql SQL example
SELECT SUBSTRING(uri, 1, CHARINDEX('/', uri)) AS directory FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend directory = parse_path(uri).directory
```
---
# parse_url
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/parse-url
The `parse_url` function parses an absolute URL string into a dynamic object containing all URL components (scheme, host, port, path, query parameters, etc.). Use this function to extract and analyze specific parts of URLs from logs, web traffic data, or API requests.
## Usage [#usage]
### Syntax [#syntax]
```kusto
parse_url(url)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ------ | -------- | -------------------------------- |
| url | string | Yes | An absolute URL string to parse. |
### Returns [#returns]
Returns a dynamic object containing URL components: `scheme`, `host`, `port`, `path`, `username`, `password`, `query`, `fragment`.
## Use case examples [#use-case-examples]
Parse URLs from HTTP logs to analyze traffic patterns by host and path.
**Query**
```kusto
['sample-http-logs']
| extend full_url = strcat('https://api.example.com', uri)
| extend parsed = parse_url(full_url)
| extend host = tostring(parsed.host)
| extend path = tostring(parsed.path)
| summarize request_count = count() by host, path
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20full_url%20%3D%20strcat\(%27https%3A%2F%2Fapi.example.com%27%2C%20uri\)%20%7C%20extend%20parsed%20%3D%20parse_url\(full_url\)%20%7C%20extend%20host%20%3D%20tostring\(parsed.host\)%20%7C%20extend%20path%20%3D%20tostring\(parsed.path\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20host%2C%20path%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| host | path | request\_count |
| --------------- | ------------- | -------------- |
| api.example.com | /api/users | 2341 |
| api.example.com | /api/orders | 1987 |
| api.example.com | /api/products | 1654 |
This query parses complete URLs to extract host and path information for traffic analysis and API endpoint usage patterns.
Extract URL components from span attributes to analyze service communication patterns.
**Query**
```kusto
['otel-demo-traces']
| extend url = strcat('http://', ['service.name'], ':8080/api/endpoint')
| extend parsed = parse_url(url)
| extend host = tostring(parsed.host)
| extend port = toint(parsed.port)
| summarize span_count = count() by host, port
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20url%20%3D%20strcat\(%27http%3A%2F%2F%27%2C%20%5B%27service.name%27%5D%2C%20%27%3A8080%2Fapi%2Fendpoint%27\)%20%7C%20extend%20parsed%20%3D%20parse_url\(url\)%20%7C%20extend%20host%20%3D%20tostring\(parsed.host\)%20%7C%20extend%20port%20%3D%20toint\(parsed.port\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20host%2C%20port%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| host | port | span\_count |
| -------- | ---- | ----------- |
| frontend | 8080 | 4532 |
| checkout | 8080 | 3421 |
| cart | 8080 | 2987 |
This query parses service URLs from traces to understand port usage and service endpoints in a distributed system.
Parse URLs from security logs to identify suspicious patterns in schemes, hosts, or paths.
**Query**
```kusto
['sample-http-logs']
| extend full_url = strcat('http://example.com', uri)
| extend parsed = parse_url(full_url)
| extend scheme = tostring(parsed.scheme)
| extend path = tostring(parsed.path)
| extend has_traversal = indexof(path, '..') >= 0
| project _time, full_url, scheme, path, has_traversal, id, status
| where has_traversal
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20full_url%20%3D%20strcat\(%27http%3A%2F%2Fexample.com%27%2C%20uri\)%20%7C%20extend%20parsed%20%3D%20parse_url\(full_url\)%20%7C%20extend%20scheme%20%3D%20tostring\(parsed.scheme\)%20%7C%20extend%20path%20%3D%20tostring\(parsed.path\)%20%7C%20extend%20has_traversal%20%3D%20indexof\(path%2C%20%27..%27\)%20%3E%3D%200%20%7C%20project%20_time%2C%20full_url%2C%20scheme%2C%20path%2C%20has_traversal%2C%20id%2C%20status%20%7C%20where%20has_traversal%20%7C%20limit%2010%22%7D)
**Output**
| \_time | full\_url | scheme | path | has\_traversal | id | status |
| -------------------- | ------------------------------------- | ------ | ------------------- | -------------- | ------- | ------ |
| 2024-11-06T10:00:00Z | `http://example.com/../../etc/passwd` | http | `/../../etc/passwd` | true | user123 | 403 |
This query parses URLs from failed access attempts and checks for path traversal patterns, helping identify potential security threats.
## List of related functions [#list-of-related-functions]
* [parse\_urlquery](/apl/scalar-functions/string-functions/parse-urlquery): Parses only URL query parameters. Use this when you only need query string parsing.
* [format\_url](/apl/scalar-functions/string-functions/format-url): Constructs URLs from components. Use this to reverse the parsing operation and build URLs.
* [url\_decode](/apl/scalar-functions/string-functions/url-decode): Decodes URL-encoded strings. Use this to decode individual URL components.
* [split](/apl/scalar-functions/string-functions/split): Splits strings by delimiters. Use this for simpler URL tokenization without full parsing.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `rex` or URL-specific extractions. APL's `parse_url` provides structured URL parsing in one function.
```sql Splunk example
| rex field=url "(?https?)://(?[^/]+)(?.*)"
```
```kusto APL equivalent
['sample-http-logs']
| extend parsed = parse_url(url)
| extend scheme = parsed.scheme, host = parsed.host, path = parsed.path
```
In ANSI SQL, URL parsing requires complex string manipulation. APL's `parse_url` provides structured parsing natively.
```sql SQL example
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(url, '://', -1), '/', 1) AS host FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend parsed = parse_url(url)
| extend host = parsed.host
```
---
# parse_urlquery
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/parse-urlquery
The `parse_urlquery` function parses a URL query string and returns a dynamic object containing the query parameters as key-value pairs. Use this function to extract and analyze query parameters from URLs in logs, API requests, or web traffic data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
parse_urlquery(query_string)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------------- | ------ | -------- | -------------------------------------------------------------- |
| query\_string | string | Yes | A URL query string (with or without the leading '?') to parse. |
### Returns [#returns]
Returns a dynamic object containing the query parameters as key-value pairs.
## Use case examples [#use-case-examples]
Extract and analyze query parameters from API requests to understand search patterns and filter usage.
**Query**
```kusto
['sample-http-logs']
| extend parameters = parse_urlquery('page=1&limitation=50&sort=date')
| extend page = toint(parameters.page)
| extend limitation = toint(parameters.limitation)
| extend sort = tostring(parameters.sort)
| summarize request_count = count() by page, limitation, sort
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20parameters%20%3D%20parse_urlquery\('page%3D1%26limitation%3D50%26sort%3Ddate'\)%20%7C%20extend%20page%20%3D%20toint\(parameters.page\)%20%7C%20extend%20limitation%20%3D%20toint\(parameters.limitation\)%20%7C%20extend%20sort%20%3D%20tostring\(parameters.sort\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20page%2C%20limitation%2C%20sort%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| page | limit | sort | request\_count |
| ---- | ----- | ---- | -------------- |
| 1 | 50 | date | 8765 |
This query parses query parameters from API requests to analyze pagination and sorting preferences.
Extract query parameters from HTTP spans to analyze API query patterns.
**Query**
```kusto
['otel-demo-traces']
| extend query_string = '?user_id=12345&action=checkout¤cy=USD'
| extend params = parse_urlquery(query_string)
| extend user_id = tostring(params.user_id)
| extend action = tostring(params.action)
| extend currency = tostring(params.currency)
| summarize span_count = count() by action, currency
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20query_string%20%3D%20%27%3Fuser_id%3D12345%26action%3Dcheckout%26currency%3DUSD%27%20%7C%20extend%20params%20%3D%20parse_urlquery\(query_string\)%20%7C%20extend%20user_id%20%3D%20tostring\(params.user_id\)%20%7C%20extend%20action%20%3D%20tostring\(params.action\)%20%7C%20extend%20currency%20%3D%20tostring\(params.currency\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20action%2C%20currency%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| action | currency | span\_count |
| -------- | -------- | ----------- |
| checkout | USD | 8765 |
This query extracts query parameters from span data to analyze user actions and currency usage patterns in a distributed system.
Detect potential SQL injection or XSS attacks by analyzing suspicious query parameters.
**Query**
```kusto
['sample-http-logs']
| extend query_params = parse_urlquery('search=<script>&id=1 OR 1=1')
| extend search_param = tostring(query_params.search)
| extend id_param = tostring(query_params.id)
| extend has_script = indexof(search_param, '<script>') >= 0
| extend has_sql = indexof(id_param, 'OR') >= 0
| where has_script or has_sql
| project _time, uri, search_param, id_param, has_script, has_sql, id, ['geo.country']
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20query_params%20%3D%20parse_urlquery\('search%3D%3Cscript%3E%26id%3D1%20OR%201%3D1'\)%20%7C%20extend%20search_param%20%3D%20tostring\(query_params.search\)%20%7C%20extend%20id_param%20%3D%20tostring\(query_params.id\)%20%7C%20extend%20has_script%20%3D%20indexof\(search_param%2C%20'%3Cscript%3E'\)%20%3E%3D%200%20%7C%20extend%20has_sql%20%3D%20indexof\(id_param%2C%20'OR'\)%20%3E%3D%200%20%7C%20where%20has_script%20or%20has_sql%20%7C%20project%20_time%2C%20uri%2C%20search_param%2C%20id_param%2C%20has_script%2C%20has_sql%2C%20id%2C%20%5B'geo.country'%5D%20%7C%20limit%2010%22%7D)
**Output**
| \_time | uri | search\_param | id\_param | has\_script | has\_sql | id | geo.country |
| -------------------- | ------- | ------------- | --------- | ----------- | -------- | ------- | ----------- |
| 2024-11-06T10:00:00Z | /search | \ | /search?q=\[HTML\_REMOVED] | user123 | 403 | Unknown |
| 2024-11-06T10:01:00Z | /api?id=1 union select \* | /api?id=1 \[SQL\_REMOVED] \* | user456 | 403 | Russia |
This query sanitizes malicious HTML and SQL patterns, making them safe to display and analyze without risk of execution.
## List of related functions [#list-of-related-functions]
* [replace\_regex](/apl/scalar-functions/string-functions/replace-regex): Alias for replace with regex support. Use either name based on preference.
* [replace\_string](/apl/scalar-functions/string-functions/replace-string): Replaces plain string matches without regex. Use this for simpler, faster replacements when regex isn't needed.
* [extract](/apl/scalar-functions/string-functions/extract): Extracts regex matches without replacement. Use this when you need to capture text rather than modify it.
* [split](/apl/scalar-functions/string-functions/split): Splits strings by delimiters. Use this when tokenizing rather than replacing.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `rex` with mode=sed for replacements. APL's `replace` provides regex replacement with capture group support.
```sql Splunk example
| rex field=message mode=sed "s/pattern/replacement/g"
```
```kusto APL equivalent
['sample-http-logs']
| extend cleaned = replace('pattern', 'replacement', uri)
```
In ANSI SQL, you use `REGEXP_REPLACE` with varying syntax by database. APL's `replace` provides standardized regex replacement.
```sql SQL example
SELECT REGEXP_REPLACE(field, 'pattern', 'replacement') AS cleaned FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend cleaned = replace('pattern', 'replacement', field)
```
---
# reverse
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/reverse
The `reverse` function reverses the order of characters in a string. Use this function to analyze strings from right to left, detect palindromes, or transform data for specific pattern matching requirements.
## Usage [#usage]
### Syntax [#syntax]
```kusto
reverse(value)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------ | -------- | ---------------------------- |
| value | string | Yes | The input string to reverse. |
### Returns [#returns]
Returns the input string with its characters in reverse order.
## Use case examples [#use-case-examples]
Detect palindromic patterns in URIs or identifiers for data validation.
**Query**
```kusto
['sample-http-logs']
| extend reversed_uri = reverse(uri)
| extend is_palindrome = uri == reversed_uri
| summarize palindrome_count = countif(is_palindrome), total_count = count() by method
| extend palindrome_percentage = round(100.0 * palindrome_count / total_count, 2)
| sort by palindrome_count desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20reversed_uri%20%3D%20reverse\(uri\)%20%7C%20extend%20is_palindrome%20%3D%20uri%20%3D%3D%20reversed_uri%20%7C%20summarize%20palindrome_count%20%3D%20countif\(is_palindrome\)%2C%20total_count%20%3D%20count\(\)%20by%20method%20%7C%20extend%20palindrome_percentage%20%3D%20round\(100.0%20*%20palindrome_count%20%2F%20total_count%2C%202\)%20%7C%20sort%20by%20palindrome_count%20desc%22%7D)
**Output**
| method | palindrome\_count | total\_count | palindrome\_percentage |
| ------ | ----------------- | ------------ | ---------------------- |
| GET | 12 | 8765 | 0.14 |
| POST | 5 | 2341 | 0.21 |
| PUT | 2 | 987 | 0.20 |
This query detects palindromic URIs by comparing them with their reversed versions, which can help identify unusual or test data patterns.
Analyze trace IDs by examining their reversed format for pattern detection.
**Query**
```kusto
['otel-demo-traces']
| extend reversed_trace = reverse(trace_id)
| extend first_char = substring(trace_id, 0, 1)
| extend last_char = substring(reversed_trace, 0, 1)
| extend matches = first_char == last_char
| summarize match_count = countif(matches), total = count() by ['service.name']
| extend match_percentage = round(100.0 * match_count / total, 2)
| sort by match_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20reversed_trace%20%3D%20reverse\(trace_id\)%20%7C%20extend%20first_char%20%3D%20substring\(trace_id%2C%200%2C%201\)%20%7C%20extend%20last_char%20%3D%20substring\(reversed_trace%2C%200%2C%201\)%20%7C%20extend%20matches%20%3D%20first_char%20%3D%3D%20last_char%20%7C%20summarize%20match_count%20%3D%20countif\(matches\)%2C%20total%20%3D%20count\(\)%20by%20%5B%27service.name%27%5D%20%7C%20extend%20match_percentage%20%3D%20round\(100.0%20*%20match_count%20%2F%20total%2C%202\)%20%7C%20sort%20by%20match_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| service.name | match\_count | total | match\_percentage |
| ------------ | ------------ | ----- | ----------------- |
| frontend | 287 | 4532 | 6.33 |
| checkout | 216 | 3421 | 6.31 |
| cart | 189 | 2987 | 6.33 |
This query analyzes trace ID patterns by checking if the first and last characters match, which can help validate ID generation algorithms.
Detect reverse proxy attacks or unusual URI patterns by analyzing reversed strings.
**Query**
```kusto
['sample-http-logs']
| extend reversed_uri = reverse(uri)
| extend has_reversed_exploit = indexof(reversed_uri, 'drowssap') >= 0 or indexof(reversed_uri, 'nigol') >= 0
| where has_reversed_exploit or status == '403' or status == '401'
| project _time, uri, reversed_uri, has_reversed_exploit, id, status
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20reversed_uri%20%3D%20reverse\(uri\)%20%7C%20extend%20has_reversed_exploit%20%3D%20indexof\(reversed_uri%2C%20%27drowssap%27\)%20%3E%3D%200%20or%20indexof\(reversed_uri%2C%20%27nigol%27\)%20%3E%3D%200%20%7C%20where%20has_reversed_exploit%20or%20status%20%3D%3D%20%27403%27%20or%20status%20%3D%3D%20%27401%27%20%7C%20project%20_time%2C%20uri%2C%20reversed_uri%2C%20has_reversed_exploit%2C%20id%2C%20status%20%7C%20limit%2010%22%7D)
**Output**
| \_time | uri | reversed\_uri | has\_reversed\_exploit | id | status |
| -------------------- | -------------- | -------------- | ---------------------- | ------- | ------ |
| 2024-11-06T10:00:00Z | /admin | nimda/ | false | user123 | 403 |
| 2024-11-06T10:01:00Z | /loginpassword | drowssapnigol/ | true | user456 | 401 |
This query detects potentially obfuscated attack patterns by examining reversed URIs for suspicious keywords like 'password' or 'login' spelled backwards.
## List of related functions [#list-of-related-functions]
* [substring](/apl/scalar-functions/string-functions/substring): Extracts parts of strings. Use this with reverse to extract from the end of strings.
* [strlen](/apl/scalar-functions/string-functions/strlen): Returns string length. Use this with reverse for position calculations from the right.
* [strcat](/apl/scalar-functions/string-functions/strcat): Concatenates strings. Use this to build strings with reversed components.
* [split](/apl/scalar-functions/string-functions/split): Splits strings into arrays. Use this with reverse to process tokens in reverse order.
## Other query languages [#other-query-languages]
In Splunk SPL, reversing strings typically requires custom functions or scripts. APL's `reverse` provides this functionality natively.
```sql Splunk example
| eval reversed=mvreverse(split(field, ""))| eval reversed=mvjoin(reversed, "")
```
```kusto APL equivalent
['sample-http-logs']
| extend reversed = reverse(field)
```
In ANSI SQL, string reversal varies by database with different functions. APL's `reverse` provides standardized string reversal.
```sql SQL example
SELECT REVERSE(field) AS reversed FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend reversed = reverse(field)
```
---
# split
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/split
The `split` function splits a string into an array of substrings based on a delimiter. Use this function to tokenize log messages, parse delimited data, or break down structured text into individual components for analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
split(source, delimiter)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| --------- | ------ | -------- | --------------------------------- |
| source | string | Yes | The source string to split. |
| delimiter | string | Yes | The delimiter string to split on. |
### Returns [#returns]
Returns a string array containing the substrings separated by the delimiter.
## Use case examples [#use-case-examples]
Split URI paths into segments for hierarchical analysis of API endpoint structure.
**Query**
```kusto
['sample-http-logs']
| extend path_segments = split(uri, '/')
| extend segment_count = array_length(path_segments)
| extend first_segment = tostring(path_segments[1])
| summarize request_count = count() by first_segment, segment_count
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20path_segments%20%3D%20split\(uri%2C%20%27%2F%27\)%20%7C%20extend%20segment_count%20%3D%20array_length\(path_segments\)%20%7C%20extend%20first_segment%20%3D%20tostring\(path_segments%5B1%5D\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20first_segment%2C%20segment_count%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| first\_segment | segment\_count | request\_count |
| -------------- | -------------- | -------------- |
| api | 4 | 5432 |
| users | 3 | 2341 |
| products | 3 | 1987 |
This query splits URIs by forward slashes to analyze API endpoint hierarchy and identify the most accessed top-level paths.
Parse dot-notation service names into components for hierarchical analysis.
**Query**
```kusto
['otel-demo-traces']
| extend service_parts = split(['service.name'], '-')
| extend service_type = tostring(service_parts[0])
| extend part_count = array_length(service_parts)
| summarize span_count = count() by service_type, part_count
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20service_parts%20%3D%20split\(%5B%27service.name%27%5D%2C%20%27-%27\)%20%7C%20extend%20service_type%20%3D%20tostring\(service_parts%5B0%5D\)%20%7C%20extend%20part_count%20%3D%20array_length\(service_parts\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20service_type%2C%20part_count%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| service\_type | part\_count | span\_count |
| ------------- | ----------- | ----------- |
| frontend | 1 | 4532 |
| checkout | 1 | 3421 |
| cart | 1 | 2987 |
This query splits service names by hyphens to extract service type prefixes and analyze service naming patterns.
Parse comma-separated attack indicators from security headers or URIs.
**Query**
```kusto
['sample-http-logs']
| extend simulated_threats = 'sql_injection,xss,path_traversal'
| extend threat_list = split(simulated_threats, ',')
| extend threat_count = array_length(threat_list)
| extend has_multiple_threats = threat_count > 1
| project _time, uri, threat_list, threat_count, has_multiple_threats, id, status
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20simulated_threats%20%3D%20'sql_injection%2Cxss%2Cpath_traversal'%20%7C%20extend%20threat_list%20%3D%20split\(simulated_threats%2C%20'%2C'\)%20%7C%20extend%20threat_count%20%3D%20array_length\(threat_list\)%20%7C%20extend%20has_multiple_threats%20%3D%20threat_count%20%3E%201%20%7C%20project%20_time%2C%20uri%2C%20threat_list%2C%20threat_count%2C%20has_multiple_threats%2C%20id%2C%20status%20%7C%20limit%2010%22%7D)
**Output**
| \_time | uri | threat\_list | threat\_count | has\_multiple\_threats | id | status |
| -------------------- | ------ | ------------------------------------------- | ------------- | ---------------------- | ------- | ------ |
| 2024-11-06T10:00:00Z | /admin | \["sql\_injection","xss","path\_traversal"] | 3 | true | user123 | 403 |
This query splits comma-separated threat indicators to analyze the types and combinations of security threats.
## List of related functions [#list-of-related-functions]
* [parse\_csv](/apl/scalar-functions/string-functions/parse-csv): Parses CSV strings with proper quote handling. Use this for CSV data instead of split.
* [extract\_all](/apl/scalar-functions/string-functions/extract-all): Extracts multiple regex matches. Use this when you need pattern-based tokenization.
* [strcat\_delim](/apl/scalar-functions/string-functions/strcat-delim): Concatenates strings with delimiters. Use this to reverse the split operation.
* [indexof](/apl/scalar-functions/string-functions/indexof): Finds delimiter positions. Use this when you need to know where splits would occur.
## Other query languages [#other-query-languages]
In Splunk SPL, you use the `split` function similarly. APL's `split` provides the same functionality.
```sql Splunk example
| eval parts=split(field, ",")
```
```kusto APL equivalent
['sample-http-logs']
| extend parts = split(field, ',')
```
In ANSI SQL, string splitting varies by database. APL's `split` provides standardized string splitting.
```sql SQL example
SELECT STRING_SPLIT(field, ',') AS parts FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend parts = split(field, ',')
```
---
# strcat_delim
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/strcat-delim
The `strcat_delim` function concatenates between 2 and 64 arguments with a specified delimiter between each argument. Use this function to build delimited strings like CSV rows, create formatted lists, or join fields with consistent separators.
## Usage [#usage]
### Syntax [#syntax]
```kusto
strcat_delim(delimiter, arg1, arg2, ..., argN)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----------------------- | ------ | -------- | ---------------------------------------------------------------------------------------- |
| delimiter | string | Yes | The separator string to insert between arguments. |
| `arg1, arg2, ..., argN` | any | Yes | Between 2 and 64 expressions to concatenate. Non-string values are converted to strings. |
### Returns [#returns]
Returns all arguments concatenated with the delimiter between each argument.
## Use case examples [#use-case-examples]
Create CSV-formatted log records for export or integration with external systems.
**Query**
```kusto
['sample-http-logs']
| extend csv_record = strcat_delim(',', method, status, uri, req_duration_ms, ['geo.country'])
| project _time, csv_record
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20csv_record%20%3D%20strcat_delim\(%27%2C%27%2C%20method%2C%20status%2C%20uri%2C%20req_duration_ms%2C%20%5B%27geo.country%27%5D\)%20%7C%20project%20_time%2C%20csv_record%20%7C%20limit%2010%22%7D)
**Output**
| \_time | csv\_record |
| -------------------- | --------------------------------------- |
| 2024-11-06T10:00:00Z | GET,200,/api/users,145,United States |
| 2024-11-06T10:01:00Z | POST,201,/api/orders,234,United Kingdom |
This query formats log fields as CSV records with comma delimiters, making them ready for export to spreadsheet applications or data warehouses.
Build pipe-delimited trace summaries for log aggregation systems.
**Query**
```kusto
['otel-demo-traces']
| extend trace_summary = strcat_delim(' | ', ['service.name'], kind, tostring(duration), trace_id)
| project _time, trace_summary
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20trace_summary%20%3D%20strcat_delim\(%27%20%7C%20%27%2C%20%5B%27service.name%27%5D%2C%20kind%2C%20tostring\(duration\)%2C%20trace_id\)%20%7C%20project%20_time%2C%20trace_summary%20%7C%20limit%2010%22%7D)
**Output**
| \_time | trace\_summary |
| -------------------- | ------------------------------------- |
| 2024-11-06T10:00:00Z | frontend \| server \| 125ms \| abc123 |
| 2024-11-06T10:01:00Z | checkout \| client \| 234ms \| def456 |
This query creates pipe-delimited trace summaries that are easy to read and parse, combining service, kind, duration, and trace ID.
Format security alerts with structured field separators for SIEM integration.
**Query**
```kusto
['sample-http-logs']
| extend alert = strcat_delim(' :: ', 'SECURITY_EVENT', status, method, uri, id, ['geo.country'])
| project _time, alert
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20alert%20%3D%20strcat_delim\('%20%3A%3A%20'%2C%20'SECURITY_EVENT'%2C%20status%2C%20method%2C%20uri%2C%20id%2C%20%5B'geo.country'%5D\)%20%7C%20project%20_time%2C%20alert%20%7C%20limit%2010%22%7D)
**Output**
| \_time | alert |
| -------------------- | ------------------------------------------------------------------- |
| 2024-11-06T10:00:00Z | SECURITY\_EVENT :: 403 :: GET :: /admin :: user123 :: United States |
| 2024-11-06T10:01:00Z | SECURITY\_EVENT :: 401 :: POST :: /api :: user456 :: Unknown |
This query creates structured security alerts with double-colon delimiters, making them easy to parse in SIEM systems while remaining human-readable.
## List of related functions [#list-of-related-functions]
* [strcat](/apl/scalar-functions/string-functions/strcat): Concatenates strings without delimiters. Use this when you want direct concatenation or need custom separators for each position.
* [split](/apl/scalar-functions/string-functions/split): Splits strings by delimiters. Use this to reverse strcat\_delim operations and extract individual fields.
* [parse\_csv](/apl/scalar-functions/string-functions/parse-csv): Parses CSV strings. Use this to parse the output of strcat\_delim with comma delimiters.
* [format\_url](/apl/scalar-functions/string-functions/format-url): Formats URLs from components. Use this specifically for URL construction rather than general delimited concatenation.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically concatenate with repeated delimiters. APL's `strcat_delim` provides a more concise approach.
```sql Splunk example
| eval combined=field1.",".field2.",".field3
```
```kusto APL equivalent
['sample-http-logs']
| extend combined = strcat_delim(',', field1, field2, field3)
```
In ANSI SQL, you use `CONCAT_WS` (concat with separator) for delimited concatenation. APL's `strcat_delim` provides similar functionality.
```sql SQL example
SELECT CONCAT_WS(',', field1, field2, field3) AS combined FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend combined = strcat_delim(',', field1, field2, field3)
```
---
# strcat
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/strcat
The `strcat` function concatenates between 1 and 64 string arguments into a single string. Use this function to combine multiple fields, build composite identifiers, or construct formatted messages from log data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
strcat(arg1, arg2, ..., argN)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----------------------- | ---- | -------- | ---------------------------------------------------------------------------------------- |
| `arg1, arg2, ..., argN` | any | Yes | Between 1 and 64 expressions to concatenate. Non-string values are converted to strings. |
### Returns [#returns]
Returns all arguments concatenated into a single string.
## Use case examples [#use-case-examples]
Build composite keys from multiple fields for unique request identification.
**Query**
```kusto
['sample-http-logs']
| extend request_key = strcat(method, '-', status, '-', ['geo.country'])
| summarize request_count = count() by request_key
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20request_key%20%3D%20strcat\(method%2C%20%27-%27%2C%20status%2C%20%27-%27%2C%20%5B%27geo.country%27%5D\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20request_key%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| request\_key | request\_count |
| ---------------------- | -------------- |
| GET-200-United States | 3456 |
| POST-201-United States | 2341 |
| GET-404-Unknown | 1987 |
This query concatenates HTTP method, status, and country to create composite keys for analyzing request patterns by multiple dimensions.
Build formatted trace identifiers combining service and span information.
**Query**
```kusto
['otel-demo-traces']
| extend trace_identifier = strcat(['service.name'], ':', kind, ':', trace_id)
| project _time, trace_identifier, duration
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20trace_identifier%20%3D%20strcat\(%5B%27service.name%27%5D%2C%20%27%3A%27%2C%20kind%2C%20%27%3A%27%2C%20trace_id\)%20%7C%20project%20_time%2C%20trace_identifier%2C%20duration%20%7C%20limit%2010%22%7D)
**Output**
| \_time | trace\_identifier | duration |
| -------------------- | ---------------------- | -------- |
| 2024-11-06T10:00:00Z | frontend:server:abc123 | 125ms |
| 2024-11-06T10:01:00Z | checkout:client:def456 | 234ms |
This query creates formatted trace identifiers by concatenating service name, span kind, and trace ID for comprehensive trace referencing.
Build security event descriptions combining multiple threat indicators.
**Query**
```kusto
['sample-http-logs']
| extend event_description = strcat('Failed ', method, ' request to ', uri, ' from ', id, ' (', ['geo.country'], ')')
| project _time, event_description, status
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20event_description%20%3D%20strcat\('Failed%20'%2C%20method%2C%20'%20request%20to%20'%2C%20uri%2C%20'%20from%20'%2C%20id%2C%20'%20\('%2C%20%5B'geo.country'%5D%2C%20'\)'\)%20%7C%20project%20_time%2C%20event_description%2C%20status%20%7C%20limit%2010%22%7D)
**Output**
| \_time | event\_description | status |
| -------------------- | --------------------------------------------------------- | ------ |
| 2024-11-06T10:00:00Z | Failed GET request to /admin from user123 (United States) | 403 |
| 2024-11-06T10:01:00Z | Failed POST request to /api from user456 (Unknown) | 401 |
This query builds human-readable security event descriptions by concatenating multiple fields into informative alert messages.
## List of related functions [#list-of-related-functions]
* [strcat\_delim](/apl/scalar-functions/string-functions/strcat-delim): Concatenates strings with a delimiter. Use this when you want consistent separators between all arguments.
* [split](/apl/scalar-functions/string-functions/split): Splits strings into arrays. Use this to reverse concatenation operations.
* [replace\_string](/apl/scalar-functions/string-functions/replace-string): Replaces parts of strings. Use this when you need to modify concatenated strings.
* [format\_url](/apl/scalar-functions/string-functions/format-url): Formats URL components. Use this specifically for URL construction rather than general concatenation.
## Other query languages [#other-query-languages]
In Splunk SPL, you concatenate strings using the `.` operator or the `concat` function. APL's `strcat` provides similar functionality.
```sql Splunk example
| eval combined=field1."-".field2."-".field3
```
```kusto APL equivalent
['sample-http-logs']
| extend combined = strcat(field1, '-', field2, '-', field3)
```
In ANSI SQL, you use `CONCAT` to join strings. APL's `strcat` provides the same functionality.
```sql SQL example
SELECT CONCAT(field1, '-', field2, '-', field3) AS combined FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend combined = strcat(field1, '-', field2, '-', field3)
```
---
# strcmp
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/strcmp
The `strcmp` function compares two strings lexicographically and returns an integer indicating their relationship. Use this function to sort strings, validate string ordering, or implement custom comparison logic in your queries.
## Usage [#usage]
### Syntax [#syntax]
```kusto
strcmp(string1, string2)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------- | ------ | -------- | ----------------------------- |
| string1 | string | Yes | The first string to compare. |
| string2 | string | Yes | The second string to compare. |
### Returns [#returns]
Returns an integer: -1 if string1 is less than string2, 0 if they're equal, 1 if string1 is greater than string2.
## Use case examples [#use-case-examples]
Compare HTTP methods to establish custom ordering for request type analysis.
**Query**
```kusto
['sample-http-logs']
| extend method_order = strcmp(method, 'GET')
| summarize get_requests = countif(method_order == 0),
before_get = countif(method_order < 0),
after_get = countif(method_order > 0) by status
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20method_order%20%3D%20strcmp\(method%2C%20%27GET%27\)%20%7C%20summarize%20get_requests%20%3D%20countif\(method_order%20%3D%3D%200\)%2C%20before_get%20%3D%20countif\(method_order%20%3C%200\)%2C%20after_get%20%3D%20countif\(method_order%20%3E%200\)%20by%20status%20%7C%20limit%2010%22%7D)
**Output**
| status | get\_requests | before\_get | after\_get |
| ------ | ------------- | ----------- | ---------- |
| 200 | 5432 | 1234 | 2109 |
| 404 | 1987 | 234 | 120 |
This query uses strcmp to categorize HTTP methods relative to 'GET', enabling analysis of request type distribution by status code.
Compare service names to establish ordering for service dependency analysis.
**Query**
```kusto
['otel-demo-traces']
| extend name_comparison = strcmp(['service.name'], 'frontend')
| extend is_frontend = name_comparison == 0
| extend before_frontend = name_comparison < 0
| extend after_frontend = name_comparison > 0
| summarize span_count = count() by is_frontend, before_frontend, after_frontend
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20name_comparison%20%3D%20strcmp\(%5B%27service.name%27%5D%2C%20%27frontend%27\)%20%7C%20extend%20is_frontend%20%3D%20name_comparison%20%3D%3D%200%20%7C%20extend%20before_frontend%20%3D%20name_comparison%20%3C%200%20%7C%20extend%20after_frontend%20%3D%20name_comparison%20%3E%200%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20is_frontend%2C%20before_frontend%2C%20after_frontend%22%7D)
**Output**
| is\_frontend | before\_frontend | after\_frontend | span\_count |
| ------------ | ---------------- | --------------- | ----------- |
| true | false | false | 4532 |
| false | true | false | 3421 |
| false | false | true | 6012 |
This query categorizes services based on their lexicographic position relative to 'frontend', helping organize service hierarchies.
## List of related functions [#list-of-related-functions]
* [tolower](/apl/scalar-functions/string-functions/tolower): Converts strings to lowercase. Use this before strcmp for case-insensitive comparison.
* [toupper](/apl/scalar-functions/string-functions/toupper): Converts strings to uppercase. Use this before strcmp for case-insensitive comparison.
* [strlen](/apl/scalar-functions/string-functions/strlen): Returns string length. Use this to compare strings by length rather than lexicographically.
* [indexof](/apl/scalar-functions/string-functions/indexof): Finds substring positions. Use this for substring comparison rather than full string comparison.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use comparison operators. APL's `strcmp` provides explicit lexicographic comparison with numeric return values.
```sql Splunk example
| eval result=case(str1str2, 1, true(), 0)
```
```kusto APL equivalent
['sample-http-logs']
| extend result = strcmp(str1, str2)
```
In ANSI SQL, string comparison varies. APL's `strcmp` provides C-style string comparison returning -1, 0, or 1.
```sql SQL example
SELECT CASE
WHEN str1 < str2 THEN -1
WHEN str1 > str2 THEN 1
ELSE 0
END AS result FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend result = strcmp(str1, str2)
```
---
# string_size
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/string-size
The `string_size` function returns the number of bytes in a string. You use it when you want to measure the length of text fields such as user IDs, URLs, or status codes. This function is useful for detecting anomalies, filtering out unusually long values, or analyzing patterns in textual data.
For example, you can use `string_size` to detect requests with excessively long URIs, identify outlier user IDs, or monitor payload lengths in traces.
## Usage [#usage]
### Syntax [#syntax]
```kusto
string_size(source)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | -------- | ---------------------------- |
| `source` | `string` | The input string expression. |
### Returns [#returns]
An integer representing the number of bytes in the string. If the string is empty, the function returns `0`.
## Use case examples [#use-case-examples]
You can use `string_size` to detect unusually long URIs that might indicate an attempted exploit or malformed request.
**Query**
```kusto
['sample-http-logs']
| extend uri_length = string_size(uri)
| where uri_length > 100
| project _time, method, uri, uri_length, status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20uri_length%20%3D%20string_size%28uri%29%20%7C%20where%20uri_length%20%3E%2010%20%7C%20project%20_time%2C%20method%2C%20uri%2C%20uri_length%2C%20status%22%7D)
**Output**
| \_time | method | uri | uri\_length | status |
| -------------------- | ------ | --------------------------------- | ----------- | ------ |
| 2025-09-11T10:01:45Z | GET | /search/products?q=... | 142 | 200 |
| 2025-09-11T10:02:13Z | POST | /checkout/submit/order/details... | 187 | 400 |
This query finds all HTTP requests with URIs longer than 10 characters and lists their details.
You can measure the length of trace IDs or span IDs to ensure data consistency and identify malformed identifiers.
**Query**
```kusto
['otel-demo-traces']
| extend trace_length = string_size(trace_id)
| summarize avg_length = avg(trace_length) by ['service.name']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20trace_length%20%3D%20string_size%28trace_id%29%20%7C%20summarize%20avg_length%20%3D%20avg%28trace_length%29%20by%20%5B'service.name'%5D%22%7D)
**Output**
| service.name | avg\_length |
| --------------- | ----------- |
| frontend | 32 |
| checkoutservice | 32 |
| loadgenerator | 31.8 |
This query calculates the average trace ID length per service to verify identifier consistency across the system.
You can check for anomalous user IDs by looking at the length of the `id` field. Very short or very long IDs may signal invalid or suspicious activity.
**Query**
```kusto
['sample-http-logs']
| extend id_length = string_size(id)
| where id_length < 5 or id_length > 20
| project _time, id, id_length, status, ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20id_length%20%3D%20string_size\(id\)%20%7C%20where%20id_length%20%3C%205%20or%20id_length%20%3E%2020%20%7C%20project%20_time%2C%20id%2C%20id_length%2C%20status%2C%20%5B'geo.country'%5D%22%7D)
**Output**
| \_time | id | id\_length | status | geo.country |
| -------------------- | ----------------------------- | ---------- | ------ | ----------- |
| 2025-09-11T09:55:01Z | a12 | 3 | 401 | US |
| 2025-09-11T09:58:42Z | user\_long\_id\_example\_test | 24 | 200 | DE |
This query detects requests with suspiciously short or long user IDs, which might indicate invalid credentials or malicious activity.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use the `len` function to calculate the number of characters in a string. In APL, you use `string_size` to calculate the number of bytes in a string.
```sql Splunk example
... | eval uri_length=len(uri)
```
```kusto APL equivalent
['sample-http-logs']
| extend uri_length = string_size(uri)
```
In ANSI SQL, you use the `LENGTH` or `CHAR_LENGTH` function to calculate string length. In APL, the equivalent is `string_size` to calculate the number of bytes in a string.
```sql SQL example
SELECT LENGTH(uri) AS uri_length
FROM sample_http_logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend uri_length = string_size(uri)
```
---
# strip_ansi_escapes
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/strip-ansi-escapes
Use the `strip_ansi_escapes` function in APL to remove ANSI escape sequences from strings. ANSI escape codes are special character sequences used in terminal output to control text formatting, colors, and cursor positioning. When analyzing logs or terminal output, these codes can interfere with text processing, search operations, and data analysis.
You use `strip_ansi_escapes` when you need to clean up terminal output, colorized logs, or any text that contains ANSI formatting codes. This is particularly useful when ingesting logs from command-line tools, CI/CD pipelines, container logs, or any system that outputs colored or formatted terminal text.
## Usage [#usage]
### Syntax [#syntax]
```kusto
strip_ansi_escapes(text)
```
### Parameters [#parameters]
| Name | Type | Description |
| ------ | -------- | -------------------------------------------------------------------- |
| `text` | `string` | The string containing ANSI escape sequences that you want to remove. |
### Returns [#returns]
A string with all ANSI escape sequences removed, leaving only the plain text content.
## List of related functions [#list-of-related-functions]
* [trim](/apl/scalar-functions/string-functions/trim): Use `trim` to remove leading and trailing characters. Use `strip_ansi_escapes` specifically for removing ANSI escape sequences anywhere in the string.
* [replace\_regex](/apl/scalar-functions/string-functions#replace-regex): Use `replace_regex` for custom pattern-based replacements. Use `strip_ansi_escapes` as a specialized, optimized solution for ANSI codes.
* [trim\_space](/apl/scalar-functions/string-functions/trim-space): Use `trim_space` to remove whitespace. Use `strip_ansi_escapes` to clean formatting codes.
* [extract](/apl/scalar-functions/string-functions#extract): Use `extract` to pull specific patterns from text. Use `strip_ansi_escapes` to clean the text before extraction for better pattern matching.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use regex-based replacements to remove ANSI escape sequences. APL's `strip_ansi_escapes` provides a dedicated function that handles all standard ANSI escape sequence patterns automatically.
```sql Splunk example
| rex mode=sed field=message 's/\x1b\[[0-9;]*m//g'
```
```kusto APL equivalent
['sample-http-logs']
| extend clean_message = strip_ansi_escapes(uri)
```
ANSI SQL doesn't have a built-in function for removing ANSI escape sequences. You need to use regex replacement functions specific to your SQL dialect. APL's `strip_ansi_escapes` provides a cross-platform solution that handles various ANSI escape patterns.
```sql SQL example
SELECT REGEXP_REPLACE(message, '\x1b\[[0-9;]*m', '', 'g')
FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend clean_message = strip_ansi_escapes(message)
```
---
# strlen
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/strlen
The `strlen` function returns the length of a string in characters. Use this function to validate field lengths, filter by size constraints, or analyze text content patterns in your logs.
## Usage [#usage]
### Syntax [#syntax]
```kusto
strlen(source)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------ | ------ | -------- | ---------------------- |
| source | string | Yes | The string to measure. |
### Returns [#returns]
Returns the length of the string in characters (not bytes).
## Use case examples [#use-case-examples]
Analyze URI lengths to identify potential long-URL attacks or data exfiltration attempts.
**Query**
```kusto
['sample-http-logs']
| extend uri_length = strlen(uri)
| summarize avg_length = avg(uri_length),
max_length = max(uri_length),
long_uri_count = countif(uri_length > 200) by method, status
| sort by max_length desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20uri_length%20%3D%20strlen\(uri\)%20%7C%20summarize%20avg_length%20%3D%20avg\(uri_length\)%2C%20max_length%20%3D%20max\(uri_length\)%2C%20long_uri_count%20%3D%20countif\(uri_length%20%3E%20200\)%20by%20method%2C%20status%20%7C%20sort%20by%20max_length%20desc%20%7C%20limit%2010%22%7D)
**Output**
| method | status | avg\_length | max\_length | long\_uri\_count |
| ------ | ------ | ----------- | ----------- | ---------------- |
| GET | 200 | 45.3 | 512 | 234 |
| POST | 404 | 38.7 | 387 | 89 |
This query analyzes URI length patterns to identify unusually long requests that might indicate attacks or data exfiltration attempts.
Monitor trace ID and span ID length consistency to validate instrumentation correctness.
**Query**
```kusto
['otel-demo-traces']
| extend trace_id_length = strlen(trace_id)
| extend span_id_length = strlen(span_id)
| summarize id_length_consistent = countif(trace_id_length == span_id_length),
trace_avg = avg(trace_id_length),
span_avg = avg(span_id_length) by ['service.name']
| sort by id_length_consistent desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20trace_id_length%20%3D%20strlen\(trace_id\)%20%7C%20extend%20span_id_length%20%3D%20strlen\(span_id\)%20%7C%20summarize%20id_length_consistent%20%3D%20countif\(trace_id_length%20%3D%3D%20span_id_length\)%2C%20trace_avg%20%3D%20avg\(trace_id_length\)%2C%20span_avg%20%3D%20avg\(span_id_length\)%20by%20%5B%27service.name%27%5D%20%7C%20sort%20by%20id_length_consistent%20desc%20%7C%20limit%2010%22%7D)
**Output**
| service.name | id\_length\_consistent | trace\_avg | span\_avg |
| ------------ | ---------------------- | ---------- | --------- |
| frontend | 4532 | 32.0 | 16.0 |
| checkout | 3421 | 32.0 | 16.0 |
This query validates that trace and span IDs have expected lengths, helping identify instrumentation issues where IDs might be malformed.
Detect potential buffer overflow attacks by identifying unusually long user identifiers or input fields.
**Query**
```kusto
['sample-http-logs']
| extend id_length = strlen(id)
| extend uri_length = strlen(uri)
| where id_length > 50 or uri_length > 500
| project _time, id, id_length, uri, uri_length, status, ['geo.country']
| sort by id_length desc, uri_length desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20id_length%20%3D%20strlen\(id\)%20%7C%20extend%20uri_length%20%3D%20strlen\(uri\)%20%7C%20where%20id_length%20%3E%2050%20or%20uri_length%20%3E%20500%20%7C%20project%20_time%2C%20id%2C%20id_length%2C%20uri%2C%20uri_length%2C%20status%2C%20%5B'geo.country'%5D%20%7C%20sort%20by%20id_length%20desc%2C%20uri_length%20desc%20%7C%20limit%2010%22%7D)
**Output**
| \_time | id | id\_length | uri | uri\_length | status | geo.country |
| -------------------- | ----------- | ---------- | -------- | ----------- | ------ | ----------- |
| 2024-11-06T10:00:00Z | verylong... | 87 | /api/... | 612 | 403 | Unknown |
This query identifies requests with abnormally long identifiers or URIs, which could indicate buffer overflow attempts or other injection attacks.
## List of related functions [#list-of-related-functions]
* [substring](/apl/scalar-functions/string-functions/substring): Extracts parts of strings. Use this with strlen to extract specific length substrings.
* [isempty](/apl/scalar-functions/string-functions/isempty): Checks if a string is empty. Use this to test for zero-length strings more explicitly.
* [countof](/apl/scalar-functions/string-functions/countof): Counts substring occurrences. Use this when you need occurrence counts rather than total length.
* [format\_bytes](/apl/scalar-functions/string-functions/format-bytes): Formats bytes as strings. Use this to format length values for display.
## Other query languages [#other-query-languages]
In Splunk SPL, you use the `len` function. APL's `strlen` provides the same functionality.
```sql Splunk example
| eval length=len(field)
```
```kusto APL equivalent
['sample-http-logs']
| extend length = strlen(field)
```
In ANSI SQL, you use `LENGTH` or `LEN` depending on the database. APL's `strlen` provides standardized string length measurement.
```sql SQL example
SELECT LENGTH(field) AS length FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend length = strlen(field)
```
---
# strrep
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/strrep
The `strrep` function repeats a string a specified number of times with an optional delimiter. Use this function to generate test data, create patterns for matching, or build formatted strings with repeated elements.
## Usage [#usage]
### Syntax [#syntax]
```kusto
strrep(value, multiplier, delimiter)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---------- | ------ | -------- | ----------------------------------------------------------------------- |
| value | string | Yes | The string to repeat. |
| multiplier | int | Yes | Number of repetitions (1 to 1024). Values over 1024 are capped at 1024. |
| delimiter | string | No | Optional delimiter between repetitions (default: empty string). |
### Returns [#returns]
Returns the string repeated the specified number of times with optional delimiters between repetitions.
## Use case examples [#use-case-examples]
Create visual separators or formatting patterns for log output visualization.
**Query**
```kusto
['sample-http-logs']
| extend separator = strrep('=', 50)
| extend formatted_log = strcat(separator, '\n', method, ' ', uri, ' ', status, '\n', separator)
| project _time, formatted_log
| limit 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20separator%20%3D%20strrep\(%27%3D%27%2C%2050\)%20%7C%20extend%20formatted_log%20%3D%20strcat\(separator%2C%20%27%5Cn%27%2C%20method%2C%20%27%20%27%2C%20uri%2C%20%27%20%27%2C%20status%2C%20%27%5Cn%27%2C%20separator\)%20%7C%20project%20_time%2C%20formatted_log%20%7C%20limit%205%22%7D)
**Output**
| \_time | formatted\_log |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| 2024-11-06T10:00:00Z | ================================================== GET /api/users 200 ================================================== |
This query creates visual separators using repeated equal signs, making log output more readable and organized.
Generate indentation patterns based on trace depth for hierarchical visualization.
**Query**
```kusto
['otel-demo-traces']
| extend simulated_depth = 3
| extend indentation = strrep(' ', simulated_depth)
| extend formatted_span = strcat(indentation, ['service.name'], ': ', kind)
| project _time, formatted_span, trace_id
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20simulated_depth%20%3D%203%20%7C%20extend%20indentation%20%3D%20strrep\(%27%20%20%27%2C%20simulated_depth\)%20%7C%20extend%20formatted_span%20%3D%20strcat\(indentation%2C%20%5B%27service.name%27%5D%2C%20%27%3A%20%27%2C%20kind\)%20%7C%20project%20_time%2C%20formatted_span%2C%20trace_id%20%7C%20limit%2010%22%7D)
**Output**
| \_time | formatted\_span | trace\_id |
| -------------------- | ---------------------- | --------- |
| 2024-11-06T10:00:00Z | frontend: server | abc123 |
| 2024-11-06T10:01:00Z | checkout: client | def456 |
This query creates hierarchical indentation for trace visualization by repeating spaces based on simulated span depth.
Generate test patterns for attack signature detection or create anonymization masks.
**Query**
```kusto
['sample-http-logs']
| extend id_length = strlen(id)
| extend anonymized_id = strcat(substring(id, 0, 3), strrep('*', id_length - 6), substring(id, id_length - 3, 3))
| project _time, id, anonymized_id, status, uri
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20id_length%20%3D%20strlen\(id\)%20%7C%20extend%20anonymized_id%20%3D%20strcat\(substring\(id%2C%200%2C%203\)%2C%20strrep\('*'%2C%20id_length%20-%206\)%2C%20substring\(id%2C%20id_length%20-%203%2C%203\)\)%20%7C%20project%20_time%2C%20id%2C%20anonymized_id%2C%20status%2C%20uri%20%7C%20limit%2010%22%7D)
**Output**
| \_time | id | anonymized\_id | status | uri |
| -------------------- | ---------- | -------------- | ------ | ------ |
| 2024-11-06T10:00:00Z | user123456 | use\*\*\*\*456 | 403 | /admin |
| 2024-11-06T10:01:00Z | admin12345 | adm\*\*\*345 | 401 | /api |
This query creates anonymized user IDs by replacing middle characters with repeated asterisks, maintaining partial visibility for analysis while protecting privacy.
## List of related functions [#list-of-related-functions]
* [strcat](/apl/scalar-functions/string-functions/strcat): Concatenates strings. Use this with strrep to build complex repeated patterns.
* [strcat\_delim](/apl/scalar-functions/string-functions/strcat-delim): Concatenates strings with delimiters. Use strrep's delimiter parameter as an alternative for repeated patterns.
* [strlen](/apl/scalar-functions/string-functions/strlen): Returns string length. Use this to calculate how many repetitions you need.
* [substring](/apl/scalar-functions/string-functions/substring): Extracts parts of strings. Use this with strrep for complex pattern building.
## Other query languages [#other-query-languages]
In Splunk SPL, repeating strings typically requires custom functions or loops. APL's `strrep` provides this functionality natively.
```sql Splunk example
| eval repeated=mvjoin(mvappend("text","text","text"), "")
```
```kusto APL equivalent
['sample-http-logs']
| extend repeated = strrep('text', 3)
```
In ANSI SQL, you use `REPEAT` or `REPLICATE` depending on the database. APL's `strrep` provides standardized string repetition.
```sql SQL example
SELECT REPEAT('text', 3) AS repeated FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend repeated = strrep('text', 3)
```
---
# substring
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/substring
The `substring` function extracts a substring from a source string starting at a specified position. Use this function to parse fixed-format logs, extract specific segments from structured strings, or truncate text fields.
## Usage [#usage]
### Syntax [#syntax]
```kusto
substring(source, startingIndex, length)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------------- | ------ | -------- | --------------------------------------------------------------------- |
| source | string | Yes | The source string to extract from. |
| startingIndex | int | Yes | The zero-based starting position. |
| length | int | No | The number of characters to extract. If omitted, extracts to the end. |
### Returns [#returns]
Returns the extracted substring. Returns empty string if startingIndex is beyond the string length.
## Use case examples [#use-case-examples]
Extract specific segments from fixed-format URIs or identifiers.
**Query**
```kusto
['sample-http-logs']
| extend api_version = substring(uri, 1, 4)
| where api_version == 'api/'
| extend endpoint = substring(uri, 5, 20)
| summarize request_count = count() by endpoint, method
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20api_version%20%3D%20substring\(uri%2C%201%2C%204\)%20%7C%20where%20api_version%20%3D%3D%20%27api%2F%27%20%7C%20extend%20endpoint%20%3D%20substring\(uri%2C%205%2C%2020\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20endpoint%2C%20method%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| endpoint | method | request\_count |
| -------- | ------ | -------------- |
| users | GET | 2341 |
| orders | POST | 1987 |
| products | GET | 1654 |
This query extracts API endpoints from URIs by taking specific character ranges, enabling analysis of API usage patterns.
Extract prefixes from trace IDs for partitioning or routing analysis.
**Query**
```kusto
['otel-demo-traces']
| extend trace_prefix = substring(trace_id, 0, 4)
| extend trace_suffix = substring(trace_id, strlen(trace_id) - 4, 4)
| summarize span_count = count() by trace_prefix
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20trace_prefix%20%3D%20substring\(trace_id%2C%200%2C%204\)%20%7C%20extend%20trace_suffix%20%3D%20substring\(trace_id%2C%20strlen\(trace_id\)%20-%204%2C%204\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20trace_prefix%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| trace\_prefix | span\_count |
| ------------- | ----------- |
| abcd | 1234 |
| ef12 | 1123 |
| 89ab | 987 |
This query extracts trace ID prefixes to analyze trace distribution patterns, which can help with load balancing and trace routing strategies.
Extract and analyze specific segments of suspicious URIs or user identifiers.
**Query**
```kusto
['sample-http-logs']
| extend uri_start = substring(uri, 0, 10)
| extend uri_has_exploit = indexof(uri_start, '..') >= 0 or indexof(uri_start, '<script') >= 0
| where uri_has_exploit
| project _time, uri, uri_start, uri_has_exploit, id, status, ['geo.country']
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20uri_start%20%3D%20substring\(uri%2C%200%2C%2010\)%20%7C%20extend%20uri_has_exploit%20%3D%20indexof\(uri_start%2C%20'..'\)%20%3E%3D%200%20or%20indexof\(uri_start%2C%20'%3Cscript'\)%20%3E%3D%200%20%7C%20where%20uri_has_exploit%20%7C%20project%20_time%2C%20uri%2C%20uri_start%2C%20uri_has_exploit%2C%20id%2C%20status%2C%20%5B'geo.country'%5D%20%7C%20limit%2010%22%7D)
**Output**
| \_time | uri | uri\_start | uri\_has\_exploit | id | status | geo.country |
| -------------------- | ------------------------ | ------------------ | ----------------- | ------- | ------ | ----------- |
| 2024-11-06T10:00:00Z | `../../etc/passwd` | `../../etc/` | true | user123 | 403 | Unknown |
| 2024-11-06T10:01:00Z | `<script>alert(1)` | `<script>al` | true | user456 | 403 | Russia |
This query extracts the beginning of URIs to quickly identify common attack patterns like path traversal or XSS attempts.
## List of related functions [#list-of-related-functions]
* [extract](/apl/scalar-functions/string-functions/extract): Extracts substrings using regex. Use this when you need pattern-based extraction rather than position-based.
* [split](/apl/scalar-functions/string-functions/split): Splits strings by delimiters. Use this when you need to tokenize rather than extract by position.
* [strlen](/apl/scalar-functions/string-functions/strlen): Returns string length. Use this to calculate positions relative to string length.
* [indexof](/apl/scalar-functions/string-functions/indexof): Finds substring positions. Use this to find dynamic starting positions for substring extraction.
## Other query languages [#other-query-languages]
In Splunk SPL, you use the `substr` function. APL's `substring` provides similar functionality with zero-based indexing.
```sql Splunk example
| eval extracted=substr(field, 5, 10)
```
```kusto APL equivalent
['sample-http-logs']
| extend extracted = substring(field, 4, 10)
```
Note: Splunk uses 1-based indexing while APL uses 0-based indexing.
In ANSI SQL, you use `SUBSTRING` with similar syntax. APL's `substring` provides the same functionality.
```sql SQL example
SELECT SUBSTRING(field, 5, 10) AS extracted FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend extracted = substring(field, 4, 10)
```
---
# tolower
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/tolower
The `tolower` function converts all characters in a string to lowercase. Use this function to normalize text for case-insensitive comparisons, standardize log data, or prepare strings for consistent analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
tolower(value)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------ | -------- | ----------------------------------------- |
| value | string | Yes | The input string to convert to lowercase. |
### Returns [#returns]
Returns the input string with all characters converted to lowercase.
## Use case examples [#use-case-examples]
Normalize HTTP methods for case-insensitive aggregation and analysis.
**Query**
```kusto
['sample-http-logs']
| extend normalized_method = tolower(method)
| summarize request_count = count() by normalized_method, status
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20normalized_method%20%3D%20tolower\(method\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20normalized_method%2C%20status%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| normalized\_method | status | request\_count |
| ------------------ | ------ | -------------- |
| get | 200 | 5432 |
| post | 201 | 2341 |
| get | 404 | 1987 |
This query normalizes HTTP methods to lowercase, ensuring that 'GET', 'Get', and 'get' are all counted together for accurate request analysis.
Standardize service names for consistent cross-service analysis.
**Query**
```kusto
['otel-demo-traces']
| extend normalized_service = tolower(['service.name'])
| summarize span_count = count(), avg_duration = avg(duration) by normalized_service
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20normalized_service%20%3D%20tolower\(%5B%27service.name%27%5D\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%2C%20avg_duration%20%3D%20avg\(duration\)%20by%20normalized_service%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| normalized\_service | span\_count | avg\_duration |
| ------------------- | ----------- | ------------- |
| frontend | 4532 | 125ms |
| checkout | 3421 | 234ms |
| cart | 2987 | 89ms |
This query normalizes service names to lowercase, ensuring consistent grouping regardless of naming convention variations.
## List of related functions [#list-of-related-functions]
* [toupper](/apl/scalar-functions/string-functions/toupper): Converts strings to uppercase. Use this for the opposite transformation.
* [totitle](/apl/scalar-functions/string-functions/totitle): Converts strings to title case. Use this for capitalized word formatting.
* [strcmp](/apl/scalar-functions/string-functions/strcmp): Compares strings. Use tolower before strcmp for case-insensitive comparisons.
* [replace\_string](/apl/scalar-functions/string-functions/replace-string): Replaces strings. Use tolower to normalize before replacements.
## Other query languages [#other-query-languages]
In Splunk SPL, you use the `lower` function. APL's `tolower` provides the same functionality.
```sql Splunk example
| eval lowercase=lower(field)
```
```kusto APL equivalent
['sample-http-logs']
| extend lowercase = tolower(field)
```
In ANSI SQL, you use `LOWER` for lowercase conversion. APL's `tolower` provides the same functionality.
```sql SQL example
SELECT LOWER(field) AS lowercase FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend lowercase = tolower(field)
```
---
# totitle
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/totitle
The `totitle` function converts a string to title case, capitalizing the first character. Use this function to format display strings, normalize names, or create human-readable output from log data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
totitle(value)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------ | -------- | ------------------------------------------ |
| value | string | Yes | The input string to convert to title case. |
### Returns [#returns]
Returns the input string with the first character capitalized.
## Use case examples [#use-case-examples]
Format HTTP methods and status codes for human-readable reports.
**Query**
```kusto
['sample-http-logs']
| extend formatted_method = totitle(tolower(method))
| summarize request_count = count() by formatted_method, status
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20formatted_method%20%3D%20totitle\(tolower\(method\)\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20formatted_method%2C%20status%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| formatted\_method | status | request\_count |
| ----------------- | ------ | -------------- |
| Get | 200 | 5432 |
| Post | 201 | 2341 |
| Get | 404 | 1987 |
This query formats HTTP methods in title case, making reports and dashboards more professional and easier to read.
Format service names for display in monitoring dashboards.
**Query**
```kusto
['otel-demo-traces']
| extend display_name = totitle(['service.name'])
| summarize span_count = count(), avg_duration = avg(duration) by display_name
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20display_name%20%3D%20totitle\(%5B%27service.name%27%5D\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%2C%20avg_duration%20%3D%20avg\(duration\)%20by%20display_name%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| display\_name | span\_count | avg\_duration |
| ------------- | ----------- | ------------- |
| Frontend | 4532 | 125ms |
| Checkout | 3421 | 234ms |
| Cart | 2987 | 89ms |
This query formats service names in title case for cleaner presentation in monitoring dashboards and reports.
## List of related functions [#list-of-related-functions]
* [tolower](/apl/scalar-functions/string-functions/tolower): Converts strings to lowercase. Use this before totitle for consistent formatting.
* [toupper](/apl/scalar-functions/string-functions/toupper): Converts strings to uppercase. Use this for fully capitalized output.
* [replace\_string](/apl/scalar-functions/string-functions/replace-string): Replaces strings. Use this with case functions for text transformation.
* [strcat](/apl/scalar-functions/string-functions/strcat): Concatenates strings. Use this with totitle to build formatted messages.
## Other query languages [#other-query-languages]
In Splunk SPL, title case conversion typically requires custom functions. APL's `totitle` provides this functionality natively.
```sql Splunk example
| eval titlecase=upper(substr(field,1,1)).lower(substr(field,2))
```
```kusto APL equivalent
['sample-http-logs']
| extend titlecase = totitle(field)
```
In ANSI SQL, title case conversion varies by database. APL's `totitle` provides standardized title case conversion.
```sql SQL example
SELECT INITCAP(field) AS titlecase FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend titlecase = totitle(field)
```
---
# toupper
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/toupper
The `toupper` function converts all characters in a string to uppercase. Use this function to normalize text for case-insensitive operations, standardize identifiers, or format strings for emphasis in output.
## Usage [#usage]
### Syntax [#syntax]
```kusto
toupper(value)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------ | -------- | ----------------------------------------- |
| value | string | Yes | The input string to convert to uppercase. |
### Returns [#returns]
Returns the input string with all characters converted to uppercase.
## Use case examples [#use-case-examples]
Standardize HTTP status codes and methods for consistent alerting and reporting.
**Query**
```kusto
['sample-http-logs']
| extend normalized_method = toupper(method)
| extend alert_status = iff(status startswith '5', toupper(strcat('ERROR_', status)), status)
| summarize request_count = count() by normalized_method, alert_status
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20normalized_method%20%3D%20toupper\(method\)%20%7C%20extend%20alert_status%20%3D%20iff\(status%20startswith%20%275%27%2C%20toupper\(strcat\(%27ERROR_%27%2C%20status\)\)%2C%20status\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20normalized_method%2C%20alert_status%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| normalized\_method | alert\_status | request\_count |
| ------------------ | ------------- | -------------- |
| GET | 200 | 5432 |
| POST | 201 | 2341 |
| GET | ERROR\_500 | 234 |
This query normalizes HTTP methods to uppercase and creates emphasized error status codes for critical alerts.
Create uppercase service identifiers for system monitoring and alerting.
**Query**
```kusto
['otel-demo-traces']
| extend service_code = toupper(substring(['service.name'], 0, 3))
| summarize span_count = count(), avg_duration = avg(duration) by service_code
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20service_code%20%3D%20toupper\(substring\(%5B%27service.name%27%5D%2C%200%2C%203\)\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%2C%20avg_duration%20%3D%20avg\(duration\)%20by%20service_code%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| service\_code | span\_count | avg\_duration |
| ------------- | ----------- | ------------- |
| FRO | 4532 | 125ms |
| CHE | 3421 | 234ms |
| CAR | 2987 | 89ms |
This query creates three-letter uppercase service codes for compact monitoring displays and alerts.
## List of related functions [#list-of-related-functions]
* [tolower](/apl/scalar-functions/string-functions/tolower): Converts strings to lowercase. Use this for the opposite transformation.
* [totitle](/apl/scalar-functions/string-functions/totitle): Converts strings to title case. Use this for capitalized formatting.
* [strcmp](/apl/scalar-functions/string-functions/strcmp): Compares strings. Use toupper before strcmp for case-insensitive comparisons.
* [strcat](/apl/scalar-functions/string-functions/strcat): Concatenates strings. Use this with toupper to build emphasized messages.
## Other query languages [#other-query-languages]
In Splunk SPL, you use the `upper` function. APL's `toupper` provides the same functionality.
```sql Splunk example
| eval uppercase=upper(field)
```
```kusto APL equivalent
['sample-http-logs']
| extend uppercase = toupper(field)
```
In ANSI SQL, you use `UPPER` for uppercase conversion. APL's `toupper` provides the same functionality.
```sql SQL example
SELECT UPPER(field) AS uppercase FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend uppercase = toupper(field)
```
---
# translate
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/translate
Use the `translate` function in APL (Axiom Processing Language) to substitute characters in a string, one by one, based on their position in two input lists. For every character in the input string that matches a character in the first list, `translate` replaces it with the character at the same position in the second list.
This function is useful when you want to:
* Replace specific characters without using complex regular expressions.
* Normalize text by mapping characters to a consistent format.
* Obfuscate or scrub data by transforming characters to placeholders.
## Usage [#usage]
### Syntax [#syntax]
```kusto
translate(searchList, replacementList, source)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----------------- | ------ | ------------------------------------------------- |
| `searchList` | string | Characters to search for in the input string. |
| `replacementList` | string | Characters to replace each match in `searchList`. |
| `source` | string | The input string to evaluate. |
### Returns [#returns]
A string with characters from `searchList` replaced by corresponding characters in `replacementList`. If `replacementList` is shorter than `searchList`, Axiom repeatedly uses the last character of `replacementList` to match the length of the search.
## Use case examples [#use-case-examples]
Use `translate` to mask user IDs by replacing all lowercase letters with asterisks.
**Query**
```kusto
['sample-http-logs']
| extend masked_id = translate('0123456789abcdefghijklmnopqrstuvwxyz', '##########*', id)
| project _time, id, masked_id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20masked_id%20%3D%20translate\('abcdefghijklmnopqrstuvwxyz'%2C%20'*'%2C%20id\)%20%7C%20project%20_time%2C%20id%2C%20masked_id%22%7D)
**Output**
| \_time | id | masked\_id |
| -------------------- | -------------------------------------- | -------------------------------------- |
| 2025-07-28T12:34:56Z | `bd6d8f17-2b8d-4b71-af20-f23dc8d20202` | `**#*#*##-#*#*-#*##-**##-*##**#*#####` |
| 2025-07-28T12:35:01Z | `1e317368-9ed4-4e8c-b535-b59a68ffda05` | `#*######-#**#-#*#*-*###-*##*##****##` |
This query masks characters in the `id` field by replacing numbers with hashes and letters with asterisks.
Use `translate` to remove vowels from service names for compact representation.
**Query**
```kusto
['otel-demo-traces']
| extend compact_service = translate('aeiou', '', ['service.name'])
| project _time, ['service.name'], compact_service
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20compact_service%20%3D%20translate\('aeiou'%2C%20''%2C%20%5B'service.name'%5D\)%20%7C%20project%20_time%2C%20%5B'service.name'%5D%2C%20compact_service%22%7D)
**Output**
| \_time | service.name | compact\_service |
| -------------------- | --------------- | ---------------- |
| 2025-07-28T10:00:00Z | product-catalog | prdct-ctlg |
| 2025-07-28T10:01:00Z | frontend | frntnd |
This example reduces the length of the `service.name` field by eliminating vowels.
Use `translate` to standardize HTTP status codes by masking digits with a symbol.
**Query**
```kusto
['sample-http-logs']
| extend normalized_status = translate('0123456789', '#', status)
| project _time, status, normalized_status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20normalized_status%20%3D%20translate\('0123456789'%2C%20'%23'%2C%20status\)%20%7C%20project%20_time%2C%20status%2C%20normalized_status%22%7D)
**Output**
| \_time | status | normalized\_status |
| -------------------- | ------ | ------------------ |
| 2025-07-28T13:45:00Z | 200 | ### |
| 2025-07-28T13:45:05Z | 404 | ### |
This use case replaces all digits in HTTP status codes with `#` characters, helping anonymize numeric values.
## Other query languages [#other-query-languages]
In Splunk SPL, you often use `replace` or `gsub` to transform string values. These functions support regular expressions and substring replacements, but they don’t directly support fixed-position character substitution.
In APL, `translate` lets you replace multiple characters in a string at once, based on positionally aligned character sets.
```sql Splunk example
... | eval masked_id=replace(id, "[abc]", "x")
```
```kusto APL equivalent
['sample-http-logs']
| extend masked_id = translate('abc', 'xxx', id)
```
If you’re familiar with ANSI SQL, APL’s `translate` function works like SQL’s `TRANSLATE`. Both functions take a character source set, a target set, and a string to process.
```sql SQL example
SELECT TRANSLATE(id, 'abc', 'xyz') FROM sample_http_logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend new_id = translate('abc', 'xyz', id)
```
---
# trim_end_regex
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/trim-end-regex
The `trim_end_regex` function removes all trailing matches of a regular expression pattern from a string. Use this function to remove complex patterns from string endings, clean structured log suffixes, or normalize data with pattern-based trimming.
## Usage [#usage]
### Syntax [#syntax]
```kusto
trim_end_regex(regex, text)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------ | -------- | ------------------------------------------------------ |
| regex | string | Yes | The regular expression pattern to remove from the end. |
| text | string | Yes | The source string to trim. |
### Returns [#returns]
Returns the source string with trailing regex matches removed.
## Use case examples [#use-case-examples]
Remove trailing numeric suffixes or version numbers from URIs.
**Query**
```kusto
['sample-http-logs']
| extend cleaned_uri = trim_end_regex('[0-9]+$', uri)
| summarize request_count = count() by cleaned_uri, method
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20cleaned_uri%20%3D%20trim_end_regex\(%27%5B0-9%5D%2B%24%27%2C%20uri\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20cleaned_uri%2C%20method%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| cleaned\_uri | method | request\_count |
| ------------ | ------ | -------------- |
| /api/users/ | GET | 2341 |
| /api/orders/ | POST | 1987 |
This query removes trailing numeric IDs from URIs, allowing aggregation by endpoint type rather than individual resources.
Remove version suffixes from service names using regex patterns.
**Query**
```kusto
['otel-demo-traces']
| extend cleaned_service = trim_end_regex('-v[0-9]+[.][0-9]+$', ['service.name'])
| summarize span_count = count() by cleaned_service
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20cleaned_service%20%3D%20trim_end_regex\(%27-v%5B0-9%5D%2B%5B.%5D%5B0-9%5D%2B%24%27%2C%20%5B%27service.name%27%5D\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20cleaned_service%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| cleaned\_service | span\_count |
| ---------------- | ----------- |
| frontend | 4532 |
| checkout | 3421 |
| cart | 2987 |
This query removes version number suffixes like '-v1.0' or '-v2.3' from service names, enabling cross-version analysis.
Remove trailing hexadecimal session tokens or identifiers from URIs.
**Query**
```kusto
['sample-http-logs']
| extend cleaned_uri = trim_end_regex('[a-f0-9]{8,}$', uri)
| summarize attempts = count() by cleaned_uri, status
| sort by attempts desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20cleaned_uri%20%3D%20trim_end_regex\('%5Ba-f0-9%5D%7B8%2C%7D%24'%2C%20uri\)%20%7C%20summarize%20failed_attempts%20%3D%20count\(\)%20by%20cleaned_uri%2C%20status%20%7C%20sort%20by%20failed_attempts%20desc%20%7C%20limit%2010%22%7D)
**Output**
| cleaned\_uri | status | failed\_attempts |
| ------------- | ------ | ---------------- |
| /api/session/ | 401 | 234 |
| /admin/token/ | 403 | 156 |
This query removes trailing hexadecimal tokens from URIs, helping identify which endpoints are targeted in authentication attempts without session-specific noise.
## List of related functions [#list-of-related-functions]
* [trim\_end](/apl/scalar-functions/string-functions/trim-end): Removes trailing characters. Use this for simple character-based trimming without regex.
* [trim\_regex](/apl/scalar-functions/string-functions/trim-regex): Removes both leading and trailing regex matches. Use this for bidirectional pattern trimming.
* [replace\_regex](/apl/scalar-functions/string-functions/replace-regex): Replaces regex matches. Use this when you need to replace patterns rather than just remove trailing ones.
* [trim\_start\_regex](/apl/scalar-functions/string-functions/trim-start-regex): Removes leading regex matches. Use this to trim patterns from the beginning.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `rex` with mode=sed for pattern-based trimming. APL's `trim_end_regex` provides a more direct approach.
```sql Splunk example
| rex field=field mode=sed "s/pattern$//g"
```
```kusto APL equivalent
['sample-http-logs']
| extend cleaned = trim_end_regex('pattern', field)
```
In ANSI SQL, regex-based trimming requires database-specific functions. APL's `trim_end_regex` provides standardized pattern-based trimming.
```sql SQL example
SELECT REGEXP_REPLACE(field, 'pattern$', '') AS cleaned FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend cleaned = trim_end_regex('pattern', field)
```
---
# trim_end
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/trim-end
The `trim_end` function removes all trailing occurrences of specified characters from a string. Use this function to clean log data, remove trailing whitespace or punctuation, or standardize string formats by removing unwanted suffixes.
## Usage [#usage]
### Syntax [#syntax]
```kusto
trim_end(cutset, text)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------ | ------ | -------- | ------------------------------------------------------ |
| cutset | string | Yes | A string containing characters to remove from the end. |
| text | string | Yes | The source string to trim. |
### Returns [#returns]
Returns the source string with all trailing characters in the cutset removed.
## Use case examples [#use-case-examples]
Remove trailing slashes and query parameters from URIs for endpoint grouping.
**Query**
```kusto
['sample-http-logs']
| extend cleaned_uri = trim_end('/?&', uri)
| summarize request_count = count() by cleaned_uri, method
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20cleaned_uri%20%3D%20trim_end\(%27%2F%3F%26%27%2C%20uri\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20cleaned_uri%2C%20method%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| cleaned\_uri | method | request\_count |
| ------------- | ------ | -------------- |
| /api/users | GET | 2341 |
| /api/orders | POST | 1987 |
| /api/products | GET | 1654 |
This query removes trailing slashes and query separator characters from URIs, enabling better endpoint aggregation.
Clean trailing characters from service names for consistent grouping.
**Query**
```kusto
['otel-demo-traces']
| extend cleaned_service = trim_end('-_', ['service.name'])
| summarize span_count = count() by cleaned_service
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20cleaned_service%20%3D%20trim_end\(%27-_%27%2C%20%5B%27service.name%27%5D\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20cleaned_service%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| cleaned\_service | span\_count |
| ---------------- | ----------- |
| frontend | 4532 |
| checkout | 3421 |
| cart | 2987 |
This query removes trailing hyphens and underscores from service names, standardizing naming conventions for analysis.
Clean trailing punctuation from user identifiers in security logs.
**Query**
```kusto
['sample-http-logs']
| extend cleaned_id = trim_end('.,;!?', id)
| summarize attempts = count() by cleaned_id, status
| sort by attempts desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20cleaned_id%20%3D%20trim_end\('.%2C%3B!%3F'%2C%20id\)%20%7C%20summarize%20attempts%20%3D%20count\(\)%20by%20cleaned_id%2C%20status%20%7C%20sort%20by%20attempts%20desc%20%7C%20limit%2010%22%7D)
**Output**
| cleaned\_id | status | failed\_attempts |
| ----------- | ------ | ---------------- |
| user123 | 401 | 45 |
| admin | 403 | 32 |
This query removes trailing punctuation from user IDs in authentication attempts, ensuring accurate counting when IDs have inconsistent formatting.
## List of related functions [#list-of-related-functions]
* [trim\_start](/apl/scalar-functions/string-functions/trim-start): Removes leading characters. Use this to trim from the beginning instead of the end.
* [trim](/apl/scalar-functions/string-functions/trim): Removes both leading and trailing characters. Use this when you need to clean both ends.
* [trim\_end\_regex](/apl/scalar-functions/string-functions/trim-end-regex): Removes trailing matches using regex. Use this for pattern-based trimming.
* [replace\_string](/apl/scalar-functions/string-functions/replace-string): Replaces strings. Use this when you need to remove characters from anywhere, not just the end.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `rtrim` for trailing whitespace. APL's `trim_end` provides more flexibility with custom character sets.
```sql Splunk example
| eval cleaned=rtrim(field)
```
```kusto APL equivalent
['sample-http-logs']
| extend cleaned = trim_end(' ', field)
```
In ANSI SQL, you use `RTRIM` for trailing characters. APL's `trim_end` provides similar functionality.
```sql SQL example
SELECT RTRIM(field) AS cleaned FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend cleaned = trim_end(' ', field)
```
---
# trim_regex
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/trim-regex
The `trim_regex` function removes all leading and trailing matches of a regular expression pattern from a string. Use this function to clean strings from both ends using pattern matching, normalize data with complex prefix/suffix patterns, or prepare strings for consistent analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
trim_regex(regex, text)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------ | -------- | -------------------------------------------------------- |
| regex | string | Yes | The regular expression pattern to remove from both ends. |
| text | string | Yes | The source string to trim. |
### Returns [#returns]
Returns the source string with leading and trailing regex matches removed.
## Use case examples [#use-case-examples]
Remove leading and trailing slashes or special characters from URIs.
**Query**
```kusto
['sample-http-logs']
| extend cleaned_uri = trim_regex('[/]+', uri)
| summarize request_count = count() by cleaned_uri, method
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20cleaned_uri%20%3D%20trim_regex\(%27%5B%2F%5D%2B%27%2C%20uri\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20cleaned_uri%2C%20method%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| cleaned\_uri | method | request\_count |
| ------------ | ------ | -------------- |
| api/users | GET | 2341 |
| api/orders | POST | 1987 |
This query removes leading and trailing slashes from URIs, normalizing paths for consistent endpoint analysis.
Clean service names by removing environment prefixes and version suffixes.
**Query**
```kusto
['otel-demo-traces']
| extend cleaned_service = trim_regex('(^(dev|prod|staging)-)|(-v[0-9.]+$)', ['service.name'])
| summarize span_count = count() by cleaned_service
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20cleaned_service%20%3D%20trim_regex\(%27\(%5E\(dev%7Cprod%7Cstaging\)-\)%7C\(-v%5B0-9.%5D%2B%24\)%27%2C%20%5B%27service.name%27%5D\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20cleaned_service%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| cleaned\_service | span\_count |
| ---------------- | ----------- |
| frontend | 4532 |
| checkout | 3421 |
| cart | 2987 |
This query removes both environment prefixes and version suffixes from service names, enabling aggregation across all environments and versions.
Remove leading/trailing whitespace and special characters from user identifiers.
**Query**
```kusto
['sample-http-logs']
| extend cleaned_id = trim_regex('[^a-zA-Z0-9_]+', id)
| summarize attempts = count() by cleaned_id, status
| sort by attempts desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20cleaned_id%20%3D%20trim_regex\('%5B%5Ea-zA-Z0-9_%5D%2B'%2C%20id\)%20%7C%20summarize%20attempts%20%3D%20count\(\)%20by%20cleaned_id%2C%20status%20%7C%20sort%20by%20attempts%20desc%20%7C%20limit%2010%22%7D)
**Output**
| cleaned\_id | status | attempts |
| ----------- | ------ | -------- |
| user123 | 401 | 45 |
| admin | 403 | 32 |
This query cleans user IDs by removing whitespace and special characters from both ends, ensuring accurate counting when identifiers have formatting inconsistencies.
## List of related functions [#list-of-related-functions]
* [trim](/apl/scalar-functions/string-functions/trim): Removes leading and trailing characters. Use this for simple character-based trimming without regex.
* [trim\_start\_regex](/apl/scalar-functions/string-functions/trim-start-regex): Removes leading regex matches. Use this for pattern trimming only from the start.
* [trim\_end\_regex](/apl/scalar-functions/string-functions/trim-end-regex): Removes trailing regex matches. Use this for pattern trimming only from the end.
* [replace\_regex](/apl/scalar-functions/string-functions/replace-regex): Replaces regex matches. Use this when you need to replace patterns anywhere, not just trim ends.
## Other query languages [#other-query-languages]
In Splunk SPL, you use multiple `rex` commands for bidirectional trimming. APL's `trim_regex` handles both ends in one operation.
```sql Splunk example
| rex field=field mode=sed "s/^pattern//g"
| rex field=field mode=sed "s/pattern$//g"
```
```kusto APL equivalent
['sample-http-logs']
| extend cleaned = trim_regex('pattern', field)
```
In ANSI SQL, bidirectional regex trimming requires nested functions. APL's `trim_regex` provides a single-function solution.
```sql SQL example
SELECT REGEXP_REPLACE(REGEXP_REPLACE(field, '^pattern', ''), 'pattern$', '') AS cleaned FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend cleaned = trim_regex('pattern', field)
```
---
# trim_space
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/trim-space
Use the `trim_space` function in APL to remove leading and trailing whitespace characters from a string. This function is especially useful when cleaning up input from logs, APIs, or user-generated content where strings may contain unintended spaces. You can apply `trim_space` to normalize data before comparisons, joins, or aggregations that depend on exact string matches.
Use `trim_space` when you need to ensure that extraneous spaces at the beginning or end of a string don’t interfere with your analysis or results.
## Usage [#usage]
### Syntax [#syntax]
```kusto
trim_space(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | ------ | ------------------------- |
| value | string | The input string to trim. |
### Returns [#returns]
A string with all leading and trailing whitespace removed. The function doesn't modify internal whitespace.
## Use case examples [#use-case-examples]
When analyzing request URIs from logs, trailing or leading spaces can lead to false negatives in equality comparisons. Use `trim_space` to normalize request paths.
**Query**
```kusto
['sample-http-logs']
| extend cleaned_uri = trim_space(uri)
| summarize count() by cleaned_uri
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20cleaned_uri%20%3D%20trim_space\(uri\)%20%7C%20summarize%20count\(\)%20by%20cleaned_uri%22%7D)
**Output**
| cleaned\_uri | count |
| ---------------- | ----- |
| /api/data | 120 |
| /api/data/submit | 88 |
| /login | 42 |
This query removes leading and trailing spaces from each `uri` and aggregates request counts by the cleaned path.
In OpenTelemetry traces, service names or span IDs can have unintended spaces when injected from external tools. Use `trim_space` to standardize span IDs before filtering.
**Query**
```kusto
['otel-demo-traces']
| extend clean_span_id = trim_space(span_id)
| summarize count() by clean_span_id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20clean_span_id%20%3D%20trim_space\(span_id\)%20%7C%20summarize%20count\(\)%20by%20clean_span_id%22%7D)
**Output**
| clean\_span\_id | count |
| ---------------- | ----- |
| 53c9e2f4e8794a6a | 17 |
| a3ff4f1e5b9d1c22 | 14 |
| 12c4b9f7da984dc7 | 21 |
This query trims each `span_id` and aggregates span counts, ensuring ID formatting does not affect grouping.
## Other query languages [#other-query-languages]
In Splunk, the `trim` function removes leading and trailing spaces. APL’s `trim_space` works similarly.
```sql Splunk example
| eval cleaned_field = trim(uri)
```
```kusto APL equivalent
['sample-http-logs']
| extend cleaned_uri = trim_space(uri)
```
In ANSI SQL, the `TRIM()` function removes leading and trailing whitespace by default. APL’s `trim_space` achieves the same behavior.
```sql SQL example
SELECT TRIM(uri) AS cleaned_uri FROM sample_http_logs
```
```kusto APL equivalent
['sample-http-logs']
| extend cleaned_uri = trim_space(uri)
```
---
# trim_start_regex
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/trim-start-regex
The `trim_start_regex` function removes all leading matches of a regular expression pattern from a string. Use this function to remove complex patterns from string beginnings, clean structured log prefixes, or normalize data with pattern-based trimming.
## Usage [#usage]
### Syntax [#syntax]
```kusto
trim_start_regex(regex, text)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ----- | ------ | -------- | ------------------------------------------------------------ |
| regex | string | Yes | The regular expression pattern to remove from the beginning. |
| text | string | Yes | The source string to trim. |
### Returns [#returns]
Returns the source string with leading regex matches removed.
## Use case examples [#use-case-examples]
Remove protocol and host prefixes from full URLs to extract paths.
**Query**
```kusto
['sample-http-logs']
| extend full_url = strcat('https://api.example.com', uri)
| extend path_only = trim_start_regex('https?://[^/]+', full_url)
| summarize request_count = count() by path_only, method
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20full_url%20%3D%20strcat\(%27https%3A%2F%2Fapi.example.com%27%2C%20uri\)%20%7C%20extend%20path_only%20%3D%20trim_start_regex\(%27https%3F%3A%2F%2F%5B%5E%2F%5D%2B%27%2C%20full_url\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20path_only%2C%20method%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| path\_only | method | request\_count |
| ----------- | ------ | -------------- |
| /api/users | GET | 2341 |
| /api/orders | POST | 1987 |
This query strips protocol and host information from URLs to focus on path-based analysis.
Remove environment and instance prefixes from service names using regex.
**Query**
```kusto
['otel-demo-traces']
| extend cleaned_service = trim_start_regex('^(prod|dev|staging)-[0-9]+-', ['service.name'])
| summarize span_count = count() by cleaned_service
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20cleaned_service%20%3D%20trim_start_regex\(%27%5E\(prod%7Cdev%7Cstaging\)-%5B0-9%5D%2B-%27%2C%20%5B%27service.name%27%5D\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20cleaned_service%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| cleaned\_service | span\_count |
| ---------------- | ----------- |
| frontend | 4532 |
| checkout | 3421 |
| cart | 2987 |
This query removes environment names and instance numbers from the beginning of service names, enabling service-level aggregation across all deployments.
Remove timestamp or log level prefixes from security event messages.
**Query**
```kusto
['sample-http-logs']
| extend simulated_message = strcat('ERROR: ', uri)
| extend cleaned_message = trim_start_regex('^(ERROR|WARN|INFO): ', simulated_message)
| project _time, simulated_message, cleaned_message, id, status
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20simulated_message%20%3D%20strcat\('ERROR%3A%20'%2C%20uri\)%20%7C%20extend%20cleaned_message%20%3D%20trim_start_regex\('%5E\(ERROR%7CWARN%7CINFO\)%3A%20'%2C%20simulated_message\)%20%7C%20project%20_time%2C%20simulated_message%2C%20cleaned_message%2C%20id%2C%20status%20%7C%20limit%2010%22%7D)
**Output**
| \_time | simulated\_message | cleaned\_message | id | status |
| -------------------- | ------------------ | ---------------- | ------- | ------ |
| 2024-11-06T10:00:00Z | ERROR: /admin | /admin | user123 | 403 |
This query removes log level prefixes from security messages, extracting the core message content for analysis.
## List of related functions [#list-of-related-functions]
* [trim\_start](/apl/scalar-functions/string-functions/trim-start): Removes leading characters. Use this for simple character-based trimming without regex.
* [trim\_regex](/apl/scalar-functions/string-functions/trim-regex): Removes both leading and trailing regex matches. Use this for bidirectional pattern trimming.
* [replace\_regex](/apl/scalar-functions/string-functions/replace-regex): Replaces regex matches. Use this when you need to replace patterns rather than just remove leading ones.
* [trim\_end\_regex](/apl/scalar-functions/string-functions/trim-end-regex): Removes trailing regex matches. Use this to trim patterns from the end.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `rex` with mode=sed for pattern-based trimming. APL's `trim_start_regex` provides a more direct approach.
```sql Splunk example
| rex field=field mode=sed "s/^pattern//g"
```
```kusto APL equivalent
['sample-http-logs']
| extend cleaned = trim_start_regex('pattern', field)
```
In ANSI SQL, regex-based trimming requires database-specific functions. APL's `trim_start_regex` provides standardized pattern-based trimming.
```sql SQL example
SELECT REGEXP_REPLACE(field, '^pattern', '') AS cleaned FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend cleaned = trim_start_regex('pattern', field)
```
---
# trim_start
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/trim-start
The `trim_start` function removes all leading occurrences of specified characters from a string. Use this function to clean log data, remove leading whitespace or special characters, or standardize string formats by removing unwanted prefixes.
## Usage [#usage]
### Syntax [#syntax]
```kusto
trim_start(cutset, text)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------ | ------ | -------- | ------------------------------------------------------------ |
| cutset | string | Yes | A string containing characters to remove from the beginning. |
| text | string | Yes | The source string to trim. |
### Returns [#returns]
Returns the source string with all leading characters in the cutset removed.
## Use case examples [#use-case-examples]
Remove leading slashes from URIs for consistent path analysis.
**Query**
```kusto
['sample-http-logs']
| extend cleaned_uri = trim_start('/', uri)
| summarize request_count = count() by cleaned_uri, method
| sort by request_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20cleaned_uri%20%3D%20trim_start\(%27%2F%27%2C%20uri\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20cleaned_uri%2C%20method%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| cleaned\_uri | method | request\_count |
| ------------ | ------ | -------------- |
| api/users | GET | 2341 |
| api/orders | POST | 1987 |
| api/products | GET | 1654 |
This query removes leading slashes from URIs, standardizing path formats for consistent grouping and analysis.
Remove environment prefixes from service names for cross-environment analysis.
**Query**
```kusto
['otel-demo-traces']
| extend cleaned_service = trim_start('prod-dev-staging-', ['service.name'])
| summarize span_count = count() by cleaned_service
| sort by span_count desc
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20cleaned_service%20%3D%20trim_start\(%27prod-dev-staging-%27%2C%20%5B%27service.name%27%5D\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20cleaned_service%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| cleaned\_service | span\_count |
| ---------------- | ----------- |
| frontend | 4532 |
| checkout | 3421 |
| cart | 2987 |
This query removes common environment prefix characters from service names, enabling aggregation across different deployment environments.
Remove leading special characters from URIs to detect obfuscated attack patterns.
**Query**
```kusto
['sample-http-logs']
| extend cleaned_uri = trim_start('./', uri)
| extend is_traversal = indexof(cleaned_uri, '..') >= 0
| where is_traversal
| project _time, uri, cleaned_uri, is_traversal, id, ['geo.country']
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20cleaned_uri%20%3D%20trim_start\('.%2F'%2C%20uri\)%20%7C%20extend%20is_traversal%20%3D%20indexof\(cleaned_uri%2C%20'..'\)%20%3E%3D%200%20%7C%20where%20is_traversal%20%7C%20project%20_time%2C%20uri%2C%20cleaned_uri%2C%20is_traversal%2C%20id%2C%20%5B'geo.country'%5D%20%7C%20limit%2010%22%7D)
**Output**
| \_time | uri | cleaned\_uri | is\_traversal | id | geo.country |
| -------------------- | -------------------- | ------------------ | ------------- | ------- | ----------- |
| 2024-11-06T10:00:00Z | `./../../etc/passwd` | `../../etc/passwd` | true | user123 | Unknown |
This query cleans leading dot and slash characters to reveal path traversal patterns that might be obfuscated with leading characters.
## List of related functions [#list-of-related-functions]
* [trim\_end](/apl/scalar-functions/string-functions/trim-end): Removes trailing characters. Use this to trim from the end instead of the beginning.
* [trim](/apl/scalar-functions/string-functions/trim): Removes both leading and trailing characters. Use this when you need to clean both ends.
* [trim\_start\_regex](/apl/scalar-functions/string-functions/trim-start-regex): Removes leading matches using regex. Use this for pattern-based trimming.
* [replace\_string](/apl/scalar-functions/string-functions/replace-string): Replaces strings. Use this when you need to remove characters from anywhere, not just the start.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `ltrim` for leading whitespace. APL's `trim_start` provides more flexibility with custom character sets.
```sql Splunk example
| eval cleaned=ltrim(field)
```
```kusto APL equivalent
['sample-http-logs']
| extend cleaned = trim_start(' ', field)
```
In ANSI SQL, you use `LTRIM` for leading characters. APL's `trim_start` provides similar functionality.
```sql SQL example
SELECT LTRIM(field) AS cleaned FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend cleaned = trim_start(' ', field)
```
---
# trim
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/trim
## Introduction [#introduction]
The `trim` function removes all leading and trailing characters from a string that are part of a specified cutset. A cutset is a set of characters, and `trim` removes any of them if they appear at the beginning or end of the string.
Use the `trim` function when you want to normalize or clean string values by stripping unwanted characters such as quotes, spaces, slashes, or punctuation. It’s useful in log analysis, standardizing OpenTelemetry attributes, or cleaning identifiers in security logs.
## Usage [#usage]
### Syntax [#syntax]
```kusto
trim(cutset, source)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------ | ------ | -------- | ---------------------------------------------------------------------------- |
| cutset | string | ✓ | The set of characters to remove from both the beginning and end of `source`. |
| source | string | ✓ | The source string to process. |
### Returns [#returns]
A string with all leading and trailing characters removed that match any character in the cutset.
## Use case examples [#use-case-examples]
You can use `trim` to normalize URLs by removing leading and trailing slashes before grouping.
**Query**
```kusto
['sample-http-logs']
| extend clean_uri = trim("/", uri)
| summarize count() by clean_uri
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20clean_uri%20%3D%20trim\('%2F'%2C%20uri\)%20%7C%20summarize%20count\(\)%20by%20clean_uri%22%7D)
**Output**
| clean\_uri | count |
| --------------- | ----- |
| api/login | 120 |
| product/details | 85 |
| cart/add | 62 |
This query removes leading and trailing slashes from the `uri` field so that identical paths group consistently.
In traces, you can use `trim` to standardize service names by removing surrounding underscores or dashes.
**Query**
```kusto
['otel-demo-traces']
| extend clean_service = trim("-_", ['service.name'])
| summarize avg(duration) by clean_service
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20clean_service%20%3D%20trim\('-_'%2C%20%5B'service.name'%5D\)%20%7C%20summarize%20avg\(duration\)%20by%20clean_service%22%7D)
**Output**
| clean\_service | avg\_duration |
| -------------- | ------------- |
| frontend | 120ms |
| cart | 210ms |
| checkout | 310ms |
This query ensures service names are consistent before calculating averages.
When analyzing user IDs, you can use `trim` to remove unwanted wrapping characters, such as hashes or quotes.
**Query**
```kusto
['sample-http-logs']
| extend clean_id = trim("#", id)
| summarize count() by clean_id
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20clean_id%20%3D%20trim\('%23'%2C%20id\)%20%7C%20summarize%20count\(\)%20by%20clean_id%22%7D)
**Output**
| clean\_id | count |
| --------- | ----- |
| user123 | 42 |
| user456 | 38 |
| user789 | 55 |
This query strips hashes around user IDs so they can be counted reliably.
## Other query languages [#other-query-languages]
In Splunk SPL, the `trim` function removes characters from both ends of a string, using a list of characters. APL’s `trim` works the same way: it uses a cutset of characters, not a regular expression.
```sql Splunk example
... | eval cleaned=trim(field, "-")
```
```kusto APL equivalent
print s='--https://axiom.co--'
| extend cleaned=trim("--", s)
```
In ANSI SQL, `TRIM` removes whitespace or specified characters from both ends of a string. APL’s `trim` works similarly, but instead of supporting keywords like `BOTH`, `LEADING`, or `TRAILING`, it uses separate functions: `trim` for both ends, `ltrim` for the start, and `rtrim` for the end. Like SQL, it operates on characters, not regular expressions.
```sql SQL example
SELECT TRIM(BOTH '-' FROM '--hello--');
```
```kusto APL equivalent
print s='--hello--'
| extend cleaned=trim("--", s)
```
---
# unicode_codepoints_from_string
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/unicode-codepoints-from-string
Use the `unicode_codepoints_from_string` function in APL to convert a UTF-8 string into an array of Unicode code points. This function is useful when you want to analyze or transform strings at the character encoding level, especially in multilingual datasets, log inspection, or byte-level debugging.
You can use this function to detect non-printable or non-ASCII characters, analyze internationalized content, or perform detailed comparisons between strings that look visually similar but differ in underlying code points.
## Usage [#usage]
### Syntax [#syntax]
```kusto
unicode_codepoints_from_string(source)
```
### Parameters [#parameters]
| Name | Type | Description |
| ------ | ------ | ---------------------------------- |
| source | string | The input UTF-8 string to convert. |
### Returns [#returns]
An array of integers, where each integer is the Unicode code point of the corresponding character in the input string.
## Use case examples [#use-case-examples]
Use this function to identify unusual characters in request URLs that might indicate obfuscated attacks or encoding issues.
**Query**
```kusto
['sample-http-logs']
| limit 100
| extend codepoints = unicode_codepoints_from_string(uri)
| mv-expand codepoints
| where codepoints < 32 or codepoints > 126
| project _time, uri, codepoints
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20limit%20100%20%7C%20extend%20codepoints%20%3D%20unicode_codepoints_from_string\(uri\)%20%7C%20mv-expand%20codepoints%20%7C%20where%20codepoints%20%3C%2032%20or%20codepoints%20%3E%20126%20%7C%20project%20_time%2C%20uri%2C%20codepoints%22%7D)
**Output**
| \_time | uri | codepoints |
| -------------------- | ----------------------------------- | ---------- |
| 2025-07-27T12:00:00Z | /api/v1/textdata/background/change£ | 163 |
This query flags URIs with non-standard characters, helping you identify suspicious or malformed requests.
Use this function to inspect `trace_id` values for structural anomalies or non-standard characters that can disrupt downstream systems.
**Query**
```kusto
['otel-demo-traces']
| limit 100
| extend codepoints = unicode_codepoints_from_string(trace_id)
| mv-expand codepoints
| where codepoints < 32 or codepoints > 126
| project _time, trace_id, codepoints
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20limit%20100%20%7C%20extend%20codepoints%20%3D%20unicode_codepoints_from_string\(trace_id\)%20%7C%20mv-expand%20codepoints%20%7C%20where%20codepoints%20%3C%2032%20or%20codepoints%20%3E%20126%20%7C%20project%20_time%2C%20trace_id%2C%20codepoints%22%7D)
**Output**
| \_time | trace\_id | codepoints |
| -------------------- | -------------------------------- | ---------- |
| 2025-07-27T13:30:00Z | aa3898b1c5bd7da25e6704b1bf59d6b§ | 167 |
This query detects trace IDs with non-standard characters, which might signal improper instrumentation or encoding errors.
Use this function to investigate potential obfuscation in user IDs by extracting and analyzing Unicode code points.
**Query**
```kusto
['sample-http-logs']
| limit 100
| extend codepoints = unicode_codepoints_from_string(id)
| mv-expand codepoints
| where codepoints < 32 or codepoints > 126
| project _time, id, codepoints
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20limit%20100%20%7C%20extend%20codepoints%20%3D%20unicode_codepoints_from_string\(id\)%20%7C%20mv-expand%20codepoints%20%7C%20where%20codepoints%20%3C%2032%20or%20codepoints%20%3E%20126%20%7C%20project%20_time%2C%20id%2C%20codepoints%22%7D)
**Output**
| \_time | id | codepoints |
| -------------------- | --------- | -------------------------------------- |
| 2025-07-27T15:15:00Z | user☠️999 | \[117,115,101,114,9760,65039,57,57,57] |
This query helps detect tampered user IDs that use emojis or hidden characters to evade filters.
## List of related functions [#list-of-related-functions]
* [array\_concat](/apl/scalar-functions/array-functions/array-concat): Combines multiple arrays. Useful when merging code point arrays from different strings.
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the number of elements in an array. Use it to check how many code points a string contains.
* [parse\_path](/apl/scalar-functions/string-functions/parse-path): Parses a path into components. Use it with `unicode_codepoints_from_string` when decoding or inspecting URL paths.
* [unicode\_codepoints\_to\_string](/apl/scalar-functions/string-functions/unicode-codepoints-to-string): Converts an array of Unicode code points into a UTF-8 encoded string.
## Other query languages [#other-query-languages]
In Splunk SPL, working with Unicode code points requires using `eval` expressions with `ord` or custom logic, which can be verbose. APL offers a built-in function for this, making it concise and efficient.
```sql Splunk example
| eval codepoints=split(mvjoin(map(split("abc", ""), ord('<>')), ","), ",")
```
```kusto APL equivalent
print codepoints = unicode_codepoints_from_string('abc')
```
ANSI SQL does not have a native function to extract Unicode code points. You typically need to use platform-specific functions or procedural logic. In APL, this is a single-function call.
```sql SQL example
-- Requires procedural logic or platform-specific functions like ASCII(), UNICODE(), etc.
```
```kusto APL equivalent
print codepoints = unicode_codepoints_from_string('abc')
```
---
# unicode_codepoints_to_string
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/unicode-codepoints-to-string
Use the `unicode_codepoints_to_string` function to convert an array of Unicode code points into a UTF-8 encoded string. This function is helpful when your data represents characters as numeric values—such as integer arrays from encodings, logs, or telemetry fields—and you want to decode them into readable text.
You can use `unicode_codepoints_to_string` to reconstruct log messages, parse encoded payloads, or normalize fragmented character sequences for visualization, comparison, or filtering.
## Usage [#usage]
### Syntax [#syntax]
```kusto
unicode_codepoints_to_string(array)
```
### Parameters [#parameters]
| Name | Type | Description |
| ------- | --------- | ------------------------------------------------------ |
| `array` | `dynamic` | An array of integers representing Unicode code points. |
### Returns [#returns]
A string constructed from the given Unicode code points using UTF-8 encoding.
## Use case examples [#use-case-examples]
Sometimes HTTP logs store user-agent strings or query parameters as numeric arrays for compact storage. You can use `unicode_codepoints_to_string` to decode those sequences.
**Query**
```kusto
['sample-http-logs']
| extend codepoints = dynamic([72, 84, 84, 80])
| extend decoded_method = unicode_codepoints_to_string(codepoints)
| project _time, decoded_method
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20codepoints%20%3D%20dynamic\(%5B72%2C%2084%2C%2084%2C%2080%5D\)%20%7C%20extend%20decoded_method%20%3D%20unicode_codepoints_to_string\(codepoints\)%20%7C%20project%20_time%2C%20decoded_method%22%7D)
**Output**
| \_time | decoded\_method |
| -------------------- | --------------- |
| 2025-07-29T14:12:00Z | HTTP |
This query decodes the static Unicode array `[72, 84, 84, 80]` into the string `'HTTP'`.
In tracing systems, service metadata might be emitted as numeric codes for efficiency. You can use `unicode_codepoints_to_string` to interpret those codes during post-processing.
**Query**
```kusto
['otel-demo-traces']
| where ['service.name'] == 'checkout'
| extend trace_label_codepoints = dynamic([67, 72, 69, 67, 75])
| extend decoded_label = unicode_codepoints_to_string(trace_label_codepoints)
| project _time, trace_id, ['service.name'], decoded_label
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20%5B'service.name'%5D%20%3D%3D%20'checkout'%20%7C%20extend%20trace_label_codepoints%20%3D%20dynamic\(%5B67%2C%2072%2C%2069%2C%2067%2C%2075%5D\)%20%7C%20extend%20decoded_label%20%3D%20unicode_codepoints_to_string\(trace_label_codepoints\)%20%7C%20project%20_time%2C%20trace_id%2C%20%5B'service.name'%5D%2C%20decoded_label%22%7D)
**Output**
| \_time | trace\_id | \['service.name'] | decoded\_label |
| -------------------- | ---------------- | ----------------- | -------------- |
| 2025-07-29T14:20:00Z | d4e9aefb8cc31b4d | checkout | CHECK |
The query decodes a fixed array into the label `'CHECK'` to tag traces visually in dashboards.
Sometimes security tools emit obfuscated payloads as numeric arrays. You can decode and inspect them using `unicode_codepoints_to_string`.
**Query**
```kusto
['sample-http-logs']
| extend obfuscated_uri = dynamic([47, 108, 111, 103, 105, 110])
| extend decoded_uri = unicode_codepoints_to_string(obfuscated_uri)
| project _time, uri, decoded_uri
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20obfuscated_uri%20%3D%20dynamic\(%5B47%2C%20108%2C%20111%2C%20103%2C%20105%2C%20110%5D\)%20%7C%20extend%20decoded_uri%20%3D%20unicode_codepoints_to_string\(obfuscated_uri\)%20%7C%20project%20_time%2C%20uri%2C%20decoded_uri%22%7D)
**Output**
| \_time | uri | decoded\_uri |
| -------------------- | -------- | ------------ |
| 2025-07-29T14:30:00Z | /blocked | /login |
This example shows how to reconstruct a denied URI (`/login`) from its obfuscated form using code points.
## List of related functions [#list-of-related-functions]
* [array\_concat](/apl/scalar-functions/array-functions/array-concat): Combines multiple arrays. Useful when merging code point arrays from different strings.
* [array\_length](/apl/scalar-functions/array-functions/array-length): Returns the number of elements in an array. Use it to check how many code points a string contains.
* [parse\_path](/apl/scalar-functions/string-functions/parse-path): Parses a path into components. Use it with `unicode_codepoints_from_string` when decoding or inspecting URL paths.
* [unicode\_codepoints\_from\_string](/apl/scalar-functions/string-functions/unicode-codepoints-from-string): Converts a UTF-8 string into an array of Unicode code points.
## Other query languages [#other-query-languages]
Splunk SPL doesn’t have a built-in function that directly converts an array of Unicode code points into a string. You typically need to use custom scripts, external commands, or workaround expressions to achieve similar functionality.
```sql Splunk example
| eval decoded=custom_decode_function(codepoints)
```
```kusto APL equivalent
print decoded = unicode_codepoints_to_string(dynamic([72, 101, 108, 108, 111]))
```
ANSI SQL doesn’t define a standard function for converting arrays of Unicode code points into strings. Implementing such functionality typically requires procedural code (e.g., in PL/pgSQL or T-SQL) or custom UDFs.
```sql SQL example
-- Pseudo-code in PL/pgSQL
SELECT array_to_string(array_agg(chr(code)), '') FROM codepoints;
```
```kusto APL equivalent
print decoded = unicode_codepoints_to_string(dynamic([72, 101, 108, 108, 111]))
```
---
# url_decode
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/url-decode
The `url_decode` function converts a URL-encoded string back to its original format. Use this function to decode query parameters, analyze encoded URIs, or extract readable text from URL-encoded log data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
url_decode(encoded_url)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ------------ | ------ | -------- | --------------------------------- |
| encoded\_url | string | Yes | The URL-encoded string to decode. |
### Returns [#returns]
Returns the decoded string in regular representation.
## Use case examples [#use-case-examples]
Decode URL-encoded query parameters to analyze user search terms and inputs.
**Query**
```kusto
['sample-http-logs']
| extend decoded_uri = url_decode(uri)
| where decoded_uri != uri
| project _time, uri, decoded_uri, method, status
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20decoded_uri%20%3D%20url_decode\(uri\)%20%7C%20where%20decoded_uri%20!%3D%20uri%20%7C%20project%20_time%2C%20uri%2C%20decoded_uri%2C%20method%2C%20status%20%7C%20limit%2010%22%7D)
**Output**
| \_time | uri | decoded\_uri | method | status |
| -------------------- | ----------------------- | --------------------- | ------ | ------ |
| 2024-11-06T10:00:00Z | /search?q=hello%20world | /search?q=hello world | GET | 200 |
| 2024-11-06T10:01:00Z | /api?name=John%20Doe | /api?name=John Doe | GET | 200 |
This query decodes URL-encoded URIs to reveal the actual search terms and parameters used by users.
Decode URL-encoded span attributes or metadata.
**Query**
```kusto
['otel-demo-traces']
| extend encoded_attr = 'service%3Dfrontend%26version%3D1.0'
| extend decoded_attr = url_decode(encoded_attr)
| project _time, ['service.name'], encoded_attr, decoded_attr
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20encoded_attr%20%3D%20%27service%253Dfrontend%2526version%253D1.0%27%20%7C%20extend%20decoded_attr%20%3D%20url_decode\(encoded_attr\)%20%7C%20project%20_time%2C%20%5B%27service.name%27%5D%2C%20encoded_attr%2C%20decoded_attr%20%7C%20limit%2010%22%7D)
**Output**
| \_time | service.name | encoded\_attr | decoded\_attr |
| -------------------- | ------------ | ---------------------------------- | ----------------------------- |
| 2024-11-06T10:00:00Z | frontend | service%3Dfrontend%26version%3D1.0 | service=frontend\&version=1.0 |
This query decodes URL-encoded attributes in traces, making them readable for analysis.
Decode potentially malicious URL-encoded payloads to identify attack patterns.
**Query**
```kusto
['sample-http-logs']
| extend decoded_uri = url_decode(uri)
| extend has_injection = indexof(decoded_uri, 'select') >= 0 or indexof(decoded_uri, '<script>') >= 0
| where has_injection
| project _time, uri, decoded_uri, has_injection, id, ['geo.country']
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20decoded_uri%20%3D%20url_decode\(uri\)%20%7C%20extend%20has_injection%20%3D%20indexof\(decoded_uri%2C%20'select'\)%20%3E%3D%200%20or%20indexof\(decoded_uri%2C%20'%3Cscript%3E'\)%20%3E%3D%200%20%7C%20where%20has_injection%20%7C%20project%20_time%2C%20uri%2C%20decoded_uri%2C%20has_injection%2C%20id%2C%20%5B'geo.country'%5D%20%7C%20limit%2010%22%7D)
**Output**
| \_time | uri | decoded\_uri | has\_injection | id | geo.country |
| -------------------- | -------------------------- | ---------------------- | -------------- | ------- | ----------- |
| 2024-11-06T10:00:00Z | /api?id=1%20union%20select | /api?id=1 union select | true | user123 | Unknown |
This query decodes URL-encoded injection attempts, revealing obfuscated SQL injection or XSS attacks for security analysis.
## List of related functions [#list-of-related-functions]
* [url\_encode](/apl/scalar-functions/string-functions/url-encode): Encodes strings for URL transmission. Use this to reverse the decoding operation.
* [parse\_url](/apl/scalar-functions/string-functions/parse-url): Parses URLs into components. Use this after url\_decode for full URL analysis.
* [parse\_urlquery](/apl/scalar-functions/string-functions/parse-urlquery): Parses URL query strings. Use this with url\_decode to extract query parameters.
* [base64\_decode\_tostring](/apl/scalar-functions/string-functions/base64-decode-tostring): Decodes Base64 strings. Use this for Base64 encoding rather than URL encoding.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `urldecode`. APL's `url_decode` provides the same functionality.
```sql Splunk example
| eval decoded=urldecode(field)
```
```kusto APL equivalent
['sample-http-logs']
| extend decoded = url_decode(field)
```
In ANSI SQL, URL decoding varies by database. APL's `url_decode` provides standardized URL decoding.
```sql SQL example
SELECT URL_DECODE(field) AS decoded FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend decoded = url_decode(field)
```
---
# url_encode
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/url-encode
The `url_encode` function converts a string into a format that can be safely transmitted over the Internet by encoding special characters. Use this function to prepare strings for URLs, build query parameters, or ensure data integrity in web requests.
## Usage [#usage]
### Syntax [#syntax]
```kusto
url_encode(url)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---- | ------ | -------- | ------------------------------------------------ |
| url | string | Yes | The input string to encode for URL transmission. |
### Returns [#returns]
Returns the string with special characters encoded for safe URL transmission.
## Use case examples [#use-case-examples]
Encode log field values for safe inclusion in generated URLs or API calls.
**Query**
```kusto
['sample-http-logs']
| extend search_term = 'hello world & special chars'
| extend encoded_search = url_encode(search_term)
| extend api_url = strcat('/api/search?q=', encoded_search)
| project _time, search_term, encoded_search, api_url
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27sample-http-logs%27%5D%20%7C%20extend%20search_term%20%3D%20%27hello%20world%20%26%20special%20chars%27%20%7C%20extend%20encoded_search%20%3D%20url_encode\(search_term\)%20%7C%20extend%20api_url%20%3D%20strcat\(%27%2Fapi%2Fsearch%3Fq%3D%27%2C%20encoded_search\)%20%7C%20project%20_time%2C%20search_term%2C%20encoded_search%2C%20api_url%20%7C%20limit%2010%22%7D)
**Output**
| \_time | search\_term | encoded\_search | api\_url |
| -------------------- | --------------------------- | ------------------------------------- | --------------------------------------------------- |
| 2024-11-06T10:00:00Z | hello world & special chars | hello%20world%20%26%20special%20chars | /api/search?q=hello%20world%20%26%20special%20chars |
This query encodes search terms for safe use in API URLs, ensuring special characters don't break the URL structure.
Encode service metadata for inclusion in trace URLs or external system integrations.
**Query**
```kusto
['otel-demo-traces']
| extend service_info = strcat(['service.name'], ': ', kind)
| extend encoded_info = url_encode(service_info)
| extend trace_url = strcat('https://tracing.example.com/trace/', trace_id, '?service=', encoded_info)
| project _time, service_info, encoded_info, trace_url
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B%27otel-demo-traces%27%5D%20%7C%20extend%20service_info%20%3D%20strcat\(%5B%27service.name%27%5D%2C%20%27%3A%20%27%2C%20kind\)%20%7C%20extend%20encoded_info%20%3D%20url_encode\(service_info\)%20%7C%20extend%20trace_url%20%3D%20strcat\(%27https%3A%2F%2Ftracing.example.com%2Ftrace%2F%27%2C%20trace_id%2C%20%27%3Fservice%3D%27%2C%20encoded_info\)%20%7C%20project%20_time%2C%20service_info%2C%20encoded_info%2C%20trace_url%20%7C%20limit%2010%22%7D)
**Output**
| \_time | service\_info | encoded\_info | trace\_url |
| -------------------- | ---------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| 2024-11-06T10:00:00Z | frontend: server | frontend%3A%20server | [https://tracing.example.com/trace/abc123?service=frontend%3A%20server](https://tracing.example.com/trace/abc123?service=frontend%3A%20server) |
This query encodes service information for safe inclusion in trace viewing URLs, enabling proper link generation in monitoring dashboards.
Encode alert details for safe transmission to webhook endpoints or SIEM systems.
**Query**
```kusto
['sample-http-logs']
| extend alert_message = strcat('Security Alert: ', method, ' to ', uri, ' from ', ['geo.country'])
| extend encoded_alert = url_encode(alert_message)
| extend webhook_url = strcat('https://alerts.example.com/webhook?message=', encoded_alert)
| project _time, alert_message, encoded_alert, webhook_url
| limit 10
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20alert_message%20%3D%20strcat\('Security%20Alert%3A%20'%2C%20method%2C%20'%20to%20'%2C%20uri%2C%20'%20from%20'%2C%20%5B'geo.country'%5D\)%20%7C%20extend%20encoded_alert%20%3D%20url_encode\(alert_message\)%20%7C%20extend%20webhook_url%20%3D%20strcat\('https%3A%2F%2Falerts.example.com%2Fwebhook%3Fmessage%3D'%2C%20encoded_alert\)%20%7C%20project%20_time%2C%20alert_message%2C%20encoded_alert%2C%20webhook_url%20%7C%20limit%2010%22%7D)
**Output**
| \_time | alert\_message | encoded\_alert | webhook\_url |
| -------------------- | ------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 2024-11-06T10:00:00Z | Security Alert: GET to /admin from United States | Security%20Alert%3A%20GET%20to%20%2Fadmin%20from%20United%20States | [https://alerts.example.com/webhook?message=Security%20Alert%3A%20GET%20to%20%2Fadmin%20from%20United%20States](https://alerts.example.com/webhook?message=Security%20Alert%3A%20GET%20to%20%2Fadmin%20from%20United%20States) |
This query encodes security alert messages for safe transmission via webhooks, ensuring special characters in URIs or messages don't break the webhook URL.
## List of related functions [#list-of-related-functions]
* [url\_decode](/apl/scalar-functions/string-functions/url-decode): Decodes URL-encoded strings. Use this to reverse the encoding operation.
* [format\_url](/apl/scalar-functions/string-functions/format-url): Formats URLs from components. Use this for building complete URLs from parts.
* [parse\_url](/apl/scalar-functions/string-functions/parse-url): Parses URLs into components. Use this to extract parts before encoding.
* [base64\_encode\_tostring](/apl/scalar-functions/string-functions/base64-encode-tostring): Encodes strings as Base64. Use this for Base64 encoding rather than URL encoding.
## Other query languages [#other-query-languages]
In Splunk SPL, you use `urlencode`. APL's `url_encode` provides the same functionality.
```sql Splunk example
| eval encoded=urlencode(field)
```
```kusto APL equivalent
['sample-http-logs']
| extend encoded = url_encode(field)
```
In ANSI SQL, URL encoding varies by database. APL's `url_encode` provides standardized URL encoding.
```sql SQL example
SELECT URL_ENCODE(field) AS encoded FROM logs;
```
```kusto APL equivalent
['sample-http-logs']
| extend encoded = url_encode(field)
```
---
# iscc
Source: https://axiom.co/docs/apl/scalar-functions/type-functions/iscc
Use the `iscc` function to determine whether a given string is a valid credit card number. This function checks the string against known credit card number patterns and applies a checksum verification (typically the Luhn algorithm) to validate the structure and integrity of the input.
You can use `iscc` when analyzing logs that may contain sensitive data to detect accidental leakage of credit card information. It’s also useful when filtering or sanitizing input data, monitoring suspicious behavior, or validating form submissions in telemetry data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
iscc(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | ------ | ------------------------------------ |
| value | string | The string to evaluate for validity. |
### Returns [#returns]
A `bool` value:
* `true` if the input string is a valid credit card number.
* `false` otherwise.
## Example [#example]
**Query**
```kusto
['sample-http-logs']
| extend has_credit_card = iscc('4111111111111111')
| project _time, has_credit_card
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20has_credit_card%20%3D%20iscc\('4111111111111111'\)%20%7C%20project%20_time%2C%20has_credit_card%22%7D)
**Output**
| \_time | has\_credit\_card |
| ------------------- | ----------------- |
| 2025-07-10T10:42:00 | true |
## List of related functions [#list-of-related-functions]
* [isimei](/apl/scalar-functions/type-functions/isimei): Checks whether a value is a valid International Mobile Equipment Identity (IMEI) number.
* [isreal](/apl/scalar-functions/type-functions/ismap): Checks whether a value is a real number.
* [isstring](/apl/scalar-functions/type-functions/isstring): Checks whether a value is a string. Use this for scalar string validation.
* [isutf8](/apl/scalar-functions/type-functions/isutf8): Checks whether a value is a valid UTF-8 encoded sequence.
## Other query languages [#other-query-languages]
Splunk SPL doesn’t provide a built-in function for validating credit card numbers. To perform similar validation, you typically rely on regular expressions and manual checksum implementations using `eval` or custom search commands.
```sql Splunk example
... | eval is_cc=if(match(field, "^[0-9]{13,19}$") AND luhn_check(field), "true", "false")
```
```kusto APL equivalent
datatable(card:string)
[
'4111111111111111',
'1234567890123456'
]
| extend is_cc = iscc(card)
```
ANSI SQL does not define a standard function for credit card validation. You must use a combination of pattern matching with `LIKE` or `REGEXP`, plus a user-defined function to implement checksum validation.
```sql SQL example
SELECT card,
CASE WHEN is_valid_card(card) THEN 'true' ELSE 'false' END AS is_cc
FROM transactions
```
```kusto APL equivalent
datatable(card:string)
[
'4111111111111111',
'1234567890123456'
]
| extend is_cc = iscc(card)
```
---
# isimei
Source: https://axiom.co/docs/apl/scalar-functions/type-functions/isimei
Use the `isimei` function to check whether a given string is a valid International Mobile Equipment Identity (IMEI) number. IMEIs are unique identifiers assigned to mobile devices, often used in mobile network logs, telecom datasets, and security investigations to distinguish devices.
You can use this function to:
* Validate whether a string field contains a proper IMEI format.
* Filter out malformed or suspicious entries in datasets containing device identifiers.
* Improve data quality when analyzing logs with user agent or device metadata.
`isimei` is especially useful when dealing with telemetry or audit data where IMEI values are passed through APIs, headers, or form fields, and you want to ensure they conform to a valid format.
## Usage [#usage]
### Syntax [#syntax]
```kusto
isimei(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | ------ | -------------------------------------------- |
| value | string | The value to test for valid IMEI formatting. |
### Returns [#returns]
A `bool` value:
* `true` if the input string is a valid IMEI number.
* `false` otherwise.
A valid IMEI is a 15-digit string that passes the [Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm) checksum.
## Example [#example]
**Query**
```kusto
['sample-http-logs']
| extend has_imei = isimei('356938035643809')
| project _time, has_imei
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20has_imei%20%3D%20isimei\('356938035643809'\)%20%7C%20project%20_time%2C%20has_imei%22%7D)
**Output**
| \_time | has\_imei |
| -------------------- | --------- |
| 2025-07-10T08:21:00Z | true |
## List of related functions [#list-of-related-functions]
* [isreal](/apl/scalar-functions/type-functions/ismap): Checks whether a value is a real number.
* [iscc](/apl/scalar-functions/type-functions/iscc): Checks whether a value is a valid credit card (CC) number.
* [isstring](/apl/scalar-functions/type-functions/isstring): Checks whether a value is a string. Use this for scalar string validation.
* [isutf8](/apl/scalar-functions/type-functions/isutf8): Checks whether a value is a valid UTF-8 encoded sequence.
## Other query languages [#other-query-languages]
Splunk SPL doesn’t include a built-in function for validating IMEI numbers. To replicate this logic, you typically use regular expressions and custom validation logic in `eval` or `where` clauses.
In APL, you can use `isimei` directly to check if a string is a valid IMEI number, simplifying your query.
```sql Splunk example
| eval is_imei=if(match(imei_field, "^[0-9]{15}$"), "true", "false") | where is_imei="true"
```
```kusto APL equivalent
| where isimei(imei_field)
```
ANSI SQL doesn’t include native IMEI validation functions. You typically rely on pattern matching with `LIKE` or regular expressions, if supported.
APL provides a dedicated `isimei` function to simplify this task.
```sql SQL example
SELECT *
FROM device_logs
WHERE imei_field LIKE '[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]'
```
```kusto APL equivalent
['sample-http-logs']
| where isimei(id)
```
---
# ismap
Source: https://axiom.co/docs/apl/scalar-functions/type-functions/ismap
Use the `ismap` function in APL to check whether a value is of the `dynamic` type and represents a mapping (also known as a dictionary, associative array, property bag, or object). A mapping consists of key-value pairs where keys are strings and values can be of any type. This function is especially useful when working with semi-structured data, such as logs or telemetry traces, where fields might dynamically contain arrays, objects, or scalar values.
Use `ismap` to:
* Filter records where a field is a map.
* Validate input types in heterogeneous data.
* Avoid runtime errors in downstream operations expecting map values.
## Usage [#usage]
### Syntax [#syntax]
```kusto
ismap(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ------- | ---- | ----------------------------------- |
| `value` | any | The value to check for being a map. |
### Returns [#returns]
Returns `true` if the value is a mapping (dictionary), otherwise returns `false`.
## Example [#example]
Use `ismap` to find log entries where a dynamic field contains structured key-value pairs, such as metadata attached to HTTP requests.
**Query**
```kusto
['sample-http-logs']
| extend is_structured = ismap(dynamic({"a":1, "b":2}))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20is_structured%20%3D%20ismap\(dynamic\(%7B'a'%3A1%2C%20'b'%3A2%7D\)\)%22%7D)
**Output**
| \_time | is\_structured |
| -------------------- | -------------- |
| 2025-06-06T08:00:00Z | true |
## List of related functions [#list-of-related-functions]
* [isimei](/apl/scalar-functions/type-functions/isimei): Checks whether a value is a valid International Mobile Equipment Identity (IMEI) number.
* [isreal](/apl/scalar-functions/type-functions/ismap): Checks whether a value is a real number.
* [iscc](/apl/scalar-functions/type-functions/iscc): Checks whether a value is a valid credit card (CC) number.
* [isstring](/apl/scalar-functions/type-functions/isstring): Checks whether a value is a string. Use this for scalar string validation.
* [isutf8](/apl/scalar-functions/type-functions/isutf8): Checks whether a value is a valid UTF-8 encoded sequence.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically work with field types implicitly and rarely check if a field is a dictionary. SPL lacks a direct equivalent to APL’s `ismap`, but you might perform similar validations using `typeof` checks or custom functions in `eval`.
```sql Splunk example
| eval is_map=if(typeof(field) == "object", true, false)
```
```kusto APL equivalent
['sample-http-logs']
| extend is_map = ismap(dynamic_field)
```
ANSI SQL doesn’t natively support map types. If you use a platform that supports JSON or semi-structured data (such as PostgreSQL with `jsonb`, BigQuery with `STRUCT`, or Snowflake), you can simulate map checks using type inspection or schema introspection.
```sql SQL example
SELECT
CASE
WHEN json_type(field) = 'object' THEN true
ELSE false
END AS is_map
FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend is_map = ismap(dynamic_field)
```
---
# isreal
Source: https://axiom.co/docs/apl/scalar-functions/type-functions/isreal
Use the `isreal` function to determine whether a value is a real number. This function is helpful when you need to validate data before performing numeric operations. For example, you can use `isreal` to filter out invalid values which could otherwise disrupt aggregations or calculations.
You often use `isreal` in data cleaning pipelines, conditional logic, and when inspecting metrics like durations, latencies, or numeric identifiers. It’s especially useful when working with telemetry or log data that includes optional or incomplete numeric fields.
## Usage [#usage]
### Syntax [#syntax]
```kusto
isreal(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | ---- | ---------------------------- |
| value | any | The input value to evaluate. |
### Returns [#returns]
Returns `true` if the input is a valid real number. Returns `false` for strings, nulls, or non-numeric types.
## Example [#example]
Use `isreal` to identify real number values.
**Query**
```kusto
['sample-http-logs']
| extend is_real = isreal(123.11)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20is_real%20%3D%20isreal\(123.11\)%22%7D)
**Output**
| \_time | is\_real |
| -------------------- | -------- |
| 2025-06-05T12:01:00Z | true |
## List of related functions [#list-of-related-functions]
* [isimei](/apl/scalar-functions/type-functions/isimei): Checks whether a value is a valid International Mobile Equipment Identity (IMEI) number.
* [ismap](/apl/scalar-functions/type-functions/ismap): Checks whether a value is of the `dynamic` type and represents a mapping.
* [iscc](/apl/scalar-functions/type-functions/iscc): Checks whether a value is a valid credit card (CC) number.
* [isstring](/apl/scalar-functions/type-functions/isstring): Checks whether a value is a string. Use this for scalar string validation.
* [isutf8](/apl/scalar-functions/type-functions/isutf8): Checks whether a value is a valid UTF-8 encoded sequence.
## Other query languages [#other-query-languages]
Splunk uses the `isnum` function to check whether a string represents a numeric value.
```sql Splunk example
... | eval is_valid = if(isnum(duration), "yes", "no")
```
```kusto APL equivalent
... | extend is_valid = iff(isreal(duration), 'yes', 'no')
```
ANSI SQL doesn’t have a direct equivalent to `isreal`. You typically check for numeric values using `IS NOT NULL` and avoid known invalid markers manually. APL’s `isreal` abstracts this by directly checking if a value is a real number.
```sql SQL example
SELECT *,
CASE WHEN duration IS NOT NULL THEN 'yes' ELSE 'no' END AS is_valid
FROM traces
```
```kusto APL equivalent
['otel-demo-traces']
| extend is_valid = iff(isreal(duration), 'yes', 'no')
```
---
# isstring
Source: https://axiom.co/docs/apl/scalar-functions/type-functions/isstring
Use the `isstring` function to determine whether a value is of type string. This function is especially helpful when working with heterogeneous datasets where field types aren’t guaranteed, or when ingesting data from sources with loosely structured or mixed schemas.
You can use `isstring` to:
* Filter rows based on whether a field is a string.
* Validate and clean data before applying string functions.
* Avoid runtime errors in queries that expect specific data types.
## Usage [#usage]
### Syntax [#syntax]
```kusto
isstring(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ------- | ---- | ---------------------------------- |
| `value` | any | The value to test for string type. |
### Returns [#returns]
A `bool` value that’s `true` if the input value is of type string, `false` otherwise.
## Use case example [#use-case-example]
Use `isstring` to filter rows where the HTTP status code is a valid string.
**Query**
```kusto
['sample-http-logs']
| extend is_string = isstring(status)
| where is_string
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20is_string%20%3D%20isstring\(status\)%20%7C%20where%20is_string%22%7D)
**Output**
| \_time | status | is\_string |
| -------------------- | ------ | ---------- |
| 2025-06-05T12:10:00Z | "404" | true |
This query filters out logs where the `status` field is stored as a string, which can help filter out ingestion issues or schema inconsistencies.
## List of related functions [#list-of-related-functions]
* [isimei](/apl/scalar-functions/type-functions/isimei): Checks whether a value is a valid International Mobile Equipment Identity (IMEI) number.
* [isreal](/apl/scalar-functions/type-functions/ismap): Checks whether a value is a real number.
* [iscc](/apl/scalar-functions/type-functions/iscc): Checks whether a value is a valid credit card (CC) number.
* [isutf8](/apl/scalar-functions/type-functions/isutf8): Checks whether a value is a valid UTF-8 encoded sequence.
## Other query languages [#other-query-languages]
In Splunk SPL, type checking is typically implicit and not exposed through a dedicated function like `isstring`. Instead, you often rely on function compatibility and casting behavior. In APL, `isstring` provides an explicit and reliable way to check if a value is a string before further processing.
```sql Splunk example
| eval type=if(isstr(field), "string", "not string")
```
```kusto APL equivalent
['sample-http-logs']
| extend type=iff(isstring(status), 'string', 'not string')
```
ANSI SQL doesn’t include a built-in `IS STRING` function. Instead, type checks usually rely on schema constraints, manual casting, or vendor-specific solutions. In contrast, APL offers `isstring` as a first-class function that returns a boolean indicating whether a value is of type string.
```sql SQL example
SELECT
CASE
WHEN typeof(status) = 'VARCHAR' THEN 'string'
ELSE 'not string'
END AS type
FROM logs
```
```kusto APL equivalent
['sample-http-logs']
| extend type=iff(isstring(status), 'string', 'not string')
```
---
# isutf8
Source: https://axiom.co/docs/apl/scalar-functions/type-functions/isutf8
Use the `isutf8` function to check whether a string is a valid UTF-8 encoded sequence. The function returns a boolean indicating whether the input conforms to UTF-8 encoding rules.
`isutf8` is useful when working with data from external sources such as logs, telemetry events, or data pipelines, where encoding issues can cause downstream processing to fail or produce incorrect results. By filtering out or isolating invalid UTF-8 strings, you can ensure better data quality and avoid unexpected behavior during parsing or transformation.
## Usage [#usage]
### Syntax [#syntax]
```kusto
isutf8(value)
```
### Parameters [#parameters]
| Name | Type | Description |
| ----- | ------ | ----------------------------- |
| value | string | The input string to validate. |
### Returns [#returns]
A `bool` value:
* `true` if the input string is valid UTF-8.
* `false` otherwise.
## Use case examples [#use-case-examples]
You can use `isutf8` to detect and exclude malformed UTF-8 entries in HTTP request logs that could indicate issues with upstream data encoding.
**Query**
```kusto
['sample-http-logs']
| where not(isutf8(uri)) or not(isutf8(method))
| project _time, id, method, uri, status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20not%28isutf8%28uri%29%29%20or%20not%28isutf8%28method%29%29%20%7C%20project%20_time%2C%20id%2C%20method%2C%20uri%2C%20status%22%7D)
**Output**
| \_time | id | method | uri | status |
| -------------------- | ------ | ------ | --------------- | ------ |
| 2025-07-09T13:32:05Z | user42 | GET | �/broken-path | 500 |
| 2025-07-09T14:10:17Z | user99 | POST | /submit-form%80 | 200 |
This query identifies records where the `uri` or `method` fields contain invalid UTF-8 characters, which may point to upstream client encoding issues or malformed requests.
In distributed traces, malformed span names or service identifiers can cause trace visualizations to fail. Use `isutf8` to validate such fields.
**Query**
```kusto
['otel-demo-traces']
| where not(isutf8(['service.name']))
| project _time, trace_id, span_id, ['service.name'], kind, status_code
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20not%28isutf8%28%5B'service.name'%5D%29%29%20%7C%20project%20_time%2C%20trace_id%2C%20span_id%2C%20%5B'service.name'%5D%2C%20kind%2C%20status_code%22%7D)
**Output**
| \_time | trace\_id | span\_id | \['service.name'] | kind | status\_code |
| -------------------- | --------- | -------- | ----------------- | ------ | ------------ |
| 2025-07-09T13:50:10Z | abc123 | xyz789 | fron��dproxy | server | 200 |
This query isolates spans where the service name contains invalid UTF-8 characters, helping you detect encoding issues in trace metadata.
Security analysts often inspect logs for anomalies. Use `isutf8` to detect tampered or improperly encoded request paths that could indicate obfuscation or injection attempts.
**Query**
```kusto
['sample-http-logs']
| where status == '403' and not(isutf8(uri))
| project _time, id, method, uri, ['geo.country']
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20%3D%3D%20'403'%20and%20not\(isutf8\(uri\)\)%20%7C%20project%20_time%2C%20id%2C%20method%2C%20uri%2C%20%5B'geo.country'%5D%22%7D)
**Output**
| \_time | id | method | uri | geo.country |
| -------------------- | ------ | ------ | ----------- | ----------- |
| 2025-07-09T12:45:02Z | user23 | GET | /bad%FFpath | Germany |
This query flags 403 responses with URIs containing invalid UTF-8, which could signal attempts to bypass filters or exploit encoding vulnerabilities.
## List of related functions [#list-of-related-functions]
* [isimei](/apl/scalar-functions/type-functions/isimei): Checks whether a value is a valid International Mobile Equipment Identity (IMEI) number.
* [isreal](/apl/scalar-functions/type-functions/ismap): Checks whether a value is a real number.
* [iscc](/apl/scalar-functions/type-functions/iscc): Checks whether a value is a valid credit card (CC) number.
* [isstring](/apl/scalar-functions/type-functions/isstring): Checks whether a value is a string. Use this for scalar string validation.
## Other query languages [#other-query-languages]
Splunk doesn’t provide a built-in function to directly check if a string is valid UTF-8. Users typically rely on workarounds using field transformations or regex, which can be error-prone or incomplete. APL provides `isutf8` as a simple and reliable alternative.
```sql Splunk example
| eval valid_utf8=if(match(field, "some-regex-pattern"), true, false)
```
```kusto APL equivalent
['sample-http-logs']
| where isutf8(uri)
```
ANSI SQL does not define a standard function to validate UTF-8 encoding in strings. Some platforms offer vendor-specific functions, but behavior varies. APL offers `isutf8` as a consistent, built-in way to validate string encoding.
```sql SQL example
SELECT * FROM logs WHERE IS_UTF8(uri) = true;
```
```kusto APL equivalent
['sample-http-logs']
| where isutf8(uri)
```
---
# Time series funtions
Source: https://axiom.co/docs/apl/scalar-functions/time-series/overview
The table summarizes the time series functions available in APL.
| Function | Description |
| -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| [series\_abs](/apl/scalar-functions/time-series/series-abs) | Returns the absolute value of a series. |
| [series\_acos](/apl/scalar-functions/time-series/series-acos) | Returns the inverse cosine (arccos) of a series. |
| [series\_add](/apl/scalar-functions/time-series/series-add) | Performs element-wise addition between two series. |
| [series\_asin](/apl/scalar-functions/time-series/series-asin) | Returns the inverse sine (arcsin) of a series. |
| [series\_atan](/apl/scalar-functions/time-series/series-atan) | Returns the inverse tangent (arctan) of a series. |
| [series\_ceiling](/apl/scalar-functions/time-series/series-ceiling) | Rounds each element up to the nearest integer. |
| [series\_cos](/apl/scalar-functions/time-series/series-cos) | Returns the cosine of a series. |
| [series\_cosine\_similarity](/apl/scalar-functions/time-series/series-cosine-similarity) | Calculates the cosine similarity between two series. |
| [series\_divide](/apl/scalar-functions/time-series/series-divide) | Performs element-wise division between two series. |
| [series\_dot\_product](/apl/scalar-functions/time-series/series-dot-product) | Calculates the dot product between two series. |
| [series\_equals](/apl/scalar-functions/time-series/series-equals) | Compares each element in a series to a specified value and returns a boolean array. |
| [series\_exp](/apl/scalar-functions/time-series/series-exp) | Calculates the exponential (e^x) of each element in a series. |
| [series\_fft](/apl/scalar-functions/time-series/series-fft) | Performs a Fast Fourier Transform on a series, converting time-domain data into frequency-domain representation. |
| [series\_fill\_backward](/apl/scalar-functions/time-series/series-fill-backward) | Fills missing values by propagating the last known value backward through the array. |
| [series\_fill\_const](/apl/scalar-functions/time-series/series-fill-const) | Fills missing values with a specified constant value. |
| [series\_fill\_forward](/apl/scalar-functions/time-series/series-fill-forward) | Fills missing values by propagating the first known value forward through the array. |
| [series\_fill\_linear](/apl/scalar-functions/time-series/series-fill-linear) | Fills missing values using linear interpolation between known values. |
| [series\_fir](/apl/scalar-functions/time-series/series-fir) | Applies a Finite Impulse Response filter to a series using a specified filter kernel. |
| [series\_floor](/apl/scalar-functions/time-series/series-floor) | Rounds down each element in a series to the nearest integer. |
| [series\_greater](/apl/scalar-functions/time-series/series-greater) | Returns the elements of a series that are greater than a specified value. |
| [series\_greater\_equals](/apl/scalar-functions/time-series/series-greater-equals) | Returns the elements of a series that are greater than or equal to a specified value. |
| [series\_ifft](/apl/scalar-functions/time-series/series-ifft) | Performs an Inverse Fast Fourier Transform on a series, converting frequency-domain data back into time-domain representation. |
| [series\_iir](/apl/scalar-functions/time-series/series-iir) | Applies an Infinite Impulse Response filter to a series. |
| [series\_less](/apl/scalar-functions/time-series/series-less) | Returns the elements of a series that are less than a specified value. |
| [series\_less\_equals](/apl/scalar-functions/time-series/series-less-equals) | Returns the elements of a series that are less than or equal to a specified value. |
| [series\_log](/apl/scalar-functions/time-series/series-log) | Returns the natural logarithm of each element in a series. |
| [series\_magnitude](/apl/scalar-functions/time-series/series-magnitude) | Calculates the Euclidean norm (magnitude) of a series. |
| [series\_max](/apl/scalar-functions/time-series/series-max) | Returns the maximum value from a series. |
| [series\_min](/apl/scalar-functions/time-series/series-min) | Returns the minimum value from a series. |
| [series\_multiply](/apl/scalar-functions/time-series/series-multiply) | Performs element-wise multiplication of two series. |
| [series\_not\_equals](/apl/scalar-functions/time-series/series-not-equals) | Returns the elements of a series that aren’t equal to a specified value. |
| [series\_pearson\_correlation](/apl/scalar-functions/time-series/series-pearson-correlation) | Calculates the Pearson correlation coefficient between two series. |
| [series\_pow](/apl/scalar-functions/time-series/series-pow) | Raises each element in a series to a specified power. |
| [series\_sign](/apl/scalar-functions/time-series/series-sign) | Returns the sign of each element in a series. |
| [series\_sin](/apl/scalar-functions/time-series/series-sin) | Returns the sine of a series. |
| [series\_stats](/apl/scalar-functions/time-series/series-stats) | Computes comprehensive statistical measures for a series. |
| [series\_stats\_dynamic](/apl/scalar-functions/time-series/series-stats-dynamic) | Computes statistical measures and returns them in a dynamic object format. |
| [series\_subtract](/apl/scalar-functions/time-series/series-subtract) | Performs element-wise subtraction between two series. |
| [series\_sum](/apl/scalar-functions/time-series/series-sum) | Returns the sum of a series. |
| [series\_tan](/apl/scalar-functions/time-series/series-tan) | Returns the tangent of a series. |
Time series functions operate on dynamic arrays where the values are real numbers.
---
# series_abs
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-abs
The `series_abs` function transforms all values in a numeric dynamic array (series) into their absolute values. This means that it converts negative values to their positive equivalents while leaving non-negative values unchanged.
You can use `series_abs` when you want to normalize data and remove the effect of directionality. For example, it’s useful in time-series scenarios where you want to analyze the magnitude of changes regardless of whether they’re positive or negative. Typical applications include error analysis, performance monitoring, and anomaly detection.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_abs(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------- |
| `array` | dynamic | A dynamic array of numeric values. |
### Returns [#returns]
A dynamic array where each element is the absolute value of the corresponding input element.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_abs` to analyze request durations by focusing on their magnitude, regardless of whether values are represented as positive or negative deviations.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend abs_durations = series_abs(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20abs_durations%20%3D%20series_abs\(durations\)%22%7D)
**Output**
| id | durations | abs\_durations |
| ---- | ------------------- | ----------------- |
| u123 | \[-50, 30, -10, 20] | \[50, 30, 10, 20] |
| u456 | \[5, -7, -3, 9] | \[5, 7, 3, 9] |
This query collects request durations for each user, then converts them into absolute values for magnitude-based analysis.
In OpenTelemetry traces, you can use `series_abs` to evaluate span durations as absolute values when analyzing deviations.
**Query**
```kusto
['otel-demo-traces']
| summarize durations = make_list(duration) by ['service.name']
| extend abs_durations = series_abs(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(duration\)%20by%20%5B'service.name'%5D%20%7C%20extend%20abs_durations%20%3D%20series_abs\(durations\)%22%7D)
**Output**
| service.name | durations | abs\_durations |
| --------------------- | ------------------------ | ---------------------- |
| frontend | \[-200ms, 300ms, -100ms] | \[200ms, 300ms, 100ms] |
| productcatalogservice | \[50ms, -80ms, 120ms] | \[50ms, 80ms, 120ms] |
This query aggregates span durations per service and applies `series_abs` to analyze absolute values of latencies.
In security logs, you can use `series_abs` to normalize anomalous request durations before analyzing request patterns.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by status
| extend abs_durations = series_abs(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20abs_durations%20%3D%20series_abs\(durations\)%22%7D)
**Output**
| status | durations | abs\_durations |
| ------ | ------------------- | ----------------- |
| 200 | \[-10, 15, -20, 5] | \[10, 15, 20, 5] |
| 500 | \[25, -40, -35, 30] | \[25, 40, 35, 30] |
This query groups request durations by status code and applies `series_abs` to focus on the magnitude of request times.
## List of related functions [#list-of-related-functions]
* [series\_acos](/apl/scalar-functions/time-series/series-acos): Returns the arc cosine of each element in an array. Use when you need to invert cosine transformations instead of sine.
* [series\_asin](/apl/scalar-functions/time-series/series-asin): Applies the arc sine function element-wise to array values. Use this when you need the inverse sine instead of the inverse cosine.
* [series\_atan](/apl/scalar-functions/time-series/series-atan): Returns the arc tangent of each element in an array. Useful for handling tangent-derived data.
## Other query languages [#other-query-languages]
In Splunk SPL, absolute values are usually calculated with the `eval` function and the `abs()` expression. In APL, you apply `series_abs` to an array column to calculate absolute values for all elements in one step.
```sql Splunk example
... | eval abs_duration=abs(duration)
```
```kusto APL equivalent
datatable(x: dynamic)
[
dynamic([-2, -1, 0, 1, 2])
]
| extend abs_values = series_abs(x)
```
In SQL, you calculate absolute values with the `ABS()` scalar function, but this only applies to single values, not arrays. In APL, `series_abs` applies the operation to every element in a dynamic array, which makes it convenient for series analysis.
```sql SQL example
SELECT ABS(duration) AS abs_duration
FROM requests;
```
```kusto APL equivalent
datatable(x: dynamic)
[
dynamic([-2, -1, 0, 1, 2])
]
| extend abs_values = series_abs(x)
```
---
# series_acos
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-acos
The `series_acos` function computes the arc cosine (inverse cosine) for each numeric element in a dynamic array. The output is another dynamic array where each value is transformed by the arc cosine function.
You use `series_acos` when you want to apply trigonometric analysis over time series or other numeric array data. This is useful in cases where your data is stored as arrays, such as time-binned metrics, periodic request patterns, or wave-like behaviors in telemetry data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_acos(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ------------------------------------------------------------------------- |
| `array` | dynamic | A dynamic array of numeric values where each element is between -1 and 1. |
### Returns [#returns]
A dynamic array of the same length as the input where each element is the arc cosine of the corresponding input element. The result values are in radians, in the range `[0, π]`.
## Use case examples [#use-case-examples]
You can analyze the periodicity of request durations. By applying `series_acos` to normalized values, you reveal inverse cosine transformations that are useful in signal-style analysis of request patterns.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms, 100) by id
| extend normalized = series_acos(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms%2C%20100\)%20by%20id%20%7C%20extend%20normalized%20%3D%20series_acos\(durations\)%22%7D)
**Output**
| id | durations | normalized |
| ---- | --------------------- | ------------------------- |
| U123 | \[100, 200, 300, 400] | \[1.47, 1.37, 1.27, 1.16] |
The query computes request duration arrays for each user, normalizes them, and applies the inverse cosine function element-wise.
You can transform span durations into the arc cosine space to analyze relationships between services in a trigonometric context, for example for anomaly detection.
**Query**
```kusto
['otel-demo-traces']
| summarize spans = make_list(duration, 50) by ['service.name']
| extend acos_spans = series_acos(spans)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20spans%20%3D%20make_list\(duration%2C%2050\)%20by%20%5B'service.name'%5D%20%7C%20extend%20acos_spans%20%3D%20series_acos\(spans\)%22%7D)
**Output**
| service.name | spans | acos\_spans |
| ------------ | ------------------- | ------------------- |
| frontend | \[20ms, 40ms, 60ms] | \[1.55, 1.52, 1.50] |
The query groups spans by service and applies the arc cosine transformation to each duration.
You can analyze request patterns by status code. By transforming request durations into arc cosine values, you can highlight cyclical or anomalous activity.
**Query**
```kusto
['sample-http-logs']
| where status == '200'
| summarize durations = make_list(req_duration_ms, 100) by ['geo.country']
| extend acos_durations = series_acos(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20%3D%3D%20'200'%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms%2C%20100\)%20by%20%5B'geo.country'%5D%20%7C%20extend%20acos_durations%20%3D%20series_acos\(durations\)%22%7D)
**Output**
| geo.country | durations | acos\_durations |
| ----------- | --------------------- | ------------------------- |
| US | \[120, 180, 240, 300] | \[1.47, 1.38, 1.29, 1.21] |
The query focuses on successful requests, aggregates their durations by country, and applies the arc cosine function to detect unusual duration distributions.
## List of related functions [#list-of-related-functions]
* [series\_asin](/apl/scalar-functions/time-series/series-asin): Applies the arc sine function element-wise to array values. Use this when you need the inverse sine instead of the inverse cosine.
* [series\_atan](/apl/scalar-functions/time-series/series-atan): Applies the arc tangent function element-wise to array values. Use this when analyzing angular relationships that use tangent ratios.
## Other query languages [#other-query-languages]
Splunk SPL doesn’t provide a direct `acos` function for array values. You typically need to expand multivalue fields into individual events, apply the `acos()` function to each event, and then optionally collect the results back into an array. In APL, `series_acos` applies the function element-wise to a dynamic array in one step.
```sql Splunk example
... | eval acos_value = mvmap(my_array, acos(x))
```
```kusto APL equivalent
datatable(arr: dynamic)
[
dynamic([0.1, 0.5, 1.0])
]
| extend acos_arr = series_acos(arr)
```
ANSI SQL provides the `ACOS()` scalar function for individual numeric values but does not support arrays as a native type. To work with multiple values, you usually normalize data into rows and apply `ACOS()` row by row. In APL, `series_acos` lets you apply the function directly to arrays without unnesting them.
```sql SQL example
SELECT ACOS(value)
FROM numbers;
```
```kusto APL equivalent
datatable(arr: dynamic)
[
dynamic([0.1, 0.5, 1.0])
]
| extend acos_arr = series_acos(arr)
```
---
# series_add
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-add
The `series_add` function performs element-wise addition between two dynamic arrays (series) of numeric values. It adds corresponding elements from both arrays and returns a new array containing the results. This function is useful when you need to combine or aggregate data from multiple time series or when performing mathematical operations across parallel datasets.
You can use `series_add` when you want to combine metrics from different sources, calculate cumulative values, or perform mathematical transformations on time-series data. Common applications include merging performance metrics, calculating total resource usage, and combining error rates from multiple services.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_add(array1, array2)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ------------------------------------------- |
| `array1` | dynamic | The first dynamic array of numeric values. |
| `array2` | dynamic | The second dynamic array of numeric values. |
### Returns [#returns]
A dynamic array where each element is the sum of the corresponding elements from `array1` and `array2`. If the arrays have different lengths, the result array has the length of the shorter array.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_add` to combine request durations from different processing stages to calculate total processing time.
**Query**
```kusto
['sample-http-logs']
| summarize stage1_durations = make_list(req_duration_ms), stage2_durations = make_list(req_duration_ms * 0.3) by id
| extend total_durations = series_add(stage1_durations, stage2_durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20stage1_durations%20%3D%20make_list\(req_duration_ms\)%2C%20stage2_durations%20%3D%20make_list\(req_duration_ms%20*%200.3\)%20by%20id%20%7C%20extend%20total_durations%20%3D%20series_add\(stage1_durations%2C%20stage2_durations\)%22%7D)
**Output**
| id | stage1\_durations | stage2\_durations | total\_durations |
| ---- | ----------------- | ----------------- | ---------------- |
| u123 | \[100, 200, 150] | \[30, 60, 45] | \[130, 260, 195] |
| u456 | \[80, 120] | \[24, 36] | \[104, 156] |
This query combines processing durations from two stages to calculate the total processing time for each user's requests.
In OpenTelemetry traces, you can use `series_add` to combine span durations from different services to analyze total request processing time.
**Query**
```kusto
['otel-demo-traces']
| summarize frontend_durations = make_list(iff(['service.name'] == 'frontend', duration, 0ms)), backend_durations = make_list(iff(['service.name'] == 'cart', duration, 0ms)) by trace_id
| extend total_durations = series_add(frontend_durations, backend_durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20frontend_durations%20%3D%20make_list\(iff\(%5B'service.name'%5D%20%3D%3D%20'frontend'%2C%20duration%2C%200ms\)\)%2C%20backend_durations%20%3D%20make_list\(iff\(%5B'service.name'%5D%20%3D%3D%20'cart'%2C%20duration%2C%200ms\)\)%20by%20trace_id%20%7C%20extend%20total_durations%20%3D%20series_add\(frontend_durations%2C%20backend_durations\)%22%7D)
**Output**
| trace\_id | frontend\_durations | backend\_durations | total\_durations |
| --------- | ----------------------- | ------------------------- | ------------------------- |
| t123 | \[00:00:01, 00:00:00.5] | \[00:00:00.2, 00:00:00.3] | \[00:00:01.2, 00:00:00.8] |
| t456 | \[00:00:00.8] | \[00:00:00.4] | \[00:00:01.2] |
This query adds frontend and backend service durations to calculate the combined processing time per trace.
In security logs, you can use `series_add` to combine request durations from different security checks to analyze total security processing overhead.
**Query**
```kusto
['sample-http-logs']
| summarize auth_durations = make_list(req_duration_ms * 0.1), validation_durations = make_list(req_duration_ms * 0.05) by status
| extend total_security_durations = series_add(auth_durations, validation_durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20auth_durations%20%3D%20make_list\(req_duration_ms%20*%200.1\)%2C%20validation_durations%20%3D%20make_list\(req_duration_ms%20*%200.05\)%20by%20status%20%7C%20extend%20total_security_durations%20%3D%20series_add\(auth_durations%2C%20validation_durations\)%22%7D)
**Output**
| status | auth\_durations | validation\_durations | total\_security\_durations |
| ------ | --------------- | --------------------- | -------------------------- |
| 200 | \[10, 20, 15] | \[5, 10, 7.5] | \[15, 30, 22.5] |
| 401 | \[25, 30] | \[12.5, 15] | \[37.5, 45] |
This query combines authentication and validation processing times to calculate total security overhead by HTTP status code.
## List of related functions [#list-of-related-functions]
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns the absolute value of each element in an array. Use when you need to remove negative signs without rounding.
* [series\_cosine\_similarity](/apl/scalar-functions/time-series/series-cosine-similarity): Calculates cosine similarity between two arrays. Use when you need normalized similarity measures rather than raw dot products.
* [series\_divide](/apl/scalar-functions/time-series/series-divide): Performs element-wise division between two arrays. Use when you need to calculate ratios or normalize values.
* [series\_dot\_product](/apl/scalar-functions/time-series/series-dot-product): Calculates the dot product between two arrays. Use when you need the raw dot product value rather than normalized similarity.
* [series\_sum](/apl/scalar-functions/time-series/series-sum): Calculates the sum of all elements in a single array. Use when you need to sum elements within one array rather than computing dot products.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use the `eval` command with mathematical operators to add values. However, adding arrays element-wise requires more complex operations. In APL, `series_add` directly performs element-wise addition on dynamic arrays.
```sql Splunk example
... | eval combined_value = field1 + field2
```
```kusto APL equivalent
datatable(x: dynamic, y: dynamic)
[
dynamic([1, 2, 3]), dynamic([4, 5, 6])
]
| extend combined_series = series_add(x, y)
```
In SQL, you add individual values using the `+` operator, but there's no built-in function for element-wise array addition. You would need to unnest arrays and perform complex joins. In APL, `series_add` handles this operation directly on dynamic arrays.
```sql SQL example
SELECT value1 + value2 AS sum_value
FROM my_table;
```
```kusto APL equivalent
datatable(x: dynamic, y: dynamic)
[
dynamic([1, 2, 3]), dynamic([4, 5, 6])
]
| extend sum_series = series_add(x, y)
```
---
# series_asin
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-asin
The `series_asin` function computes the arc sine (inverse sine) of each numeric element in a dynamic array. It returns a new array of the same length, where each element is the arc sine of the corresponding input element. The function is useful when you want to transform time series data or arrays of numeric values into angular measurements. This can help in advanced mathematical modeling, anomaly detection, and when working with normalized data that represents sine values.
You use `series_asin` when you need to invert sine transformations stored in array form, for example, to reconstruct angular information from periodic signals or normalize log and trace metrics for statistical or geometric analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_asin(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | -------------------------------------------------------------------------------------------------------------------------- |
| `array` | dynamic | A dynamic array of numeric values. Each element should be between `-1` and `1`, the valid domain of the arc sine function. |
### Returns [#returns]
A dynamic array of the same length as the input, where each element is the arc sine (in radians) of the corresponding input element.
## Use case examples [#use-case-examples]
When analyzing HTTP logs, you can normalize request durations to the range \[-1, 1] and then apply `series_asin` to transform them into angular values for further statistical analysis.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms, 5) by id
| extend normalized = series_divide(durations, 1000.0)
| extend angles = series_asin(normalized)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms%2C%205\)%20by%20id%20%7C%20extend%20normalized%20%3D%20series_divide\(durations%2C%201000.0\)%20%7C%20extend%20angles%20%3D%20series_asin\(normalized\)%22%7D)
**Output**
| id | durations | normalized | angles |
| --- | -------------------------- | -------------------------- | ------------------------------------ |
| A12 | \[100, 200, 300, 400, 500] | \[0.1, 0.2, 0.3, 0.4, 0.5] | \[0.100, 0.201, 0.305, 0.412, 0.524] |
The query collects request durations per user ID, normalizes them, and applies `series_asin` to transform values into angles.
For traces, you can normalize span durations and use `series_asin` to derive angular representations, which can be helpful in detecting periodic workload patterns.
**Query**
```kusto
['otel-demo-traces']
| summarize spans = make_list(duration, 5) by ['service.name']
| extend normalized = series_divide(spans, 10000000.0)
| extend angles = series_asin(normalized)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20spans%20%3D%20make_list\(duration%2C%205\)%20by%20%5B'service.name'%5D%20%7C%20extend%20normalized%20%3D%20series_divide\(spans%2C%2010000000.0\)%20%7C%20extend%20angles%20%3D%20series_asin\(normalized\)%22%7D)
**Output**
| service.name | spans | normalized | angles |
| ------------ | ------------------------------- | ---------------- | ---------------------- |
| frontend | \[12000000, 15000000, 20000000] | \[1.2, 1.5, 2.0] | \[null, null, null] |
| cartservice | \[5000000, 8000000, 10000000] | \[0.5, 0.8, 1.0] | \[0.524, 0.927, 1.571] |
This query collects spans per service, normalizes their durations, and computes arc sine values. Values outside \[-1, 1] result in `null`.
When examining security logs, you can normalize request durations for suspicious requests and use `series_asin` to highlight anomalous access patterns.
**Query**
```kusto
['sample-http-logs']
| summarize requests = make_list(req_duration_ms, 5) by ['geo.country']
| extend normalized = series_divide(requests, 1000.0)
| extend angles = series_asin(normalized)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20requests%20%3D%20make_list\(req_duration_ms%2C%205\)%20by%20%5B'geo.country'%5D%20%7C%20extend%20normalized%20%3D%20series_divide\(requests%2C%201000.0\)%20%7C%20extend%20angles%20%3D%20series_asin\(normalized\)%22%7D)
**Output**
| geo.country | requests | normalized | angles |
| ----------- | -------------------- | ---------------------- | ----------------------------- |
| US | \[50, 200, 400, 600] | \[0.05, 0.2, 0.4, 0.6] | \[0.050, 0.201, 0.412, 0.644] |
The query groups requests by country and converts normalized durations into angular values for anomaly detection.
## List of related functions [#list-of-related-functions]
* [series\_acos](/apl/scalar-functions/time-series/series-acos): Returns the arc cosine of each element in an array. Use when you need to invert cosine transformations instead of sine.
* [series\_atan](/apl/scalar-functions/time-series/series-atan): Returns the arc tangent of each element in an array. Useful for handling tangent-derived data.
## Other query languages [#other-query-languages]
Splunk SPL doesn’t provide a direct equivalent of `series_asin` that operates over arrays. Instead, SPL typically requires you to apply `asin()` to individual fields or use `mvmap` to apply the function to multivalue fields. In APL, `series_asin` simplifies this by applying the operation to each element of a dynamic array in one step.
```sql Splunk example
... | eval angle=mvmap(values, asin(x))
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([0.0, 0.5, 1.0])
]
| extend angle = series_asin(values)
```
ANSI SQL databases generally provide `ASIN()` for scalar values but do not include native array-processing functions. You would need to unnest an array into rows, apply `ASIN()`, and then aggregate the results back into an array. APL’s `series_asin` eliminates this boilerplate by letting you compute the arc sine across the entire array at once.
```sql SQL example
SELECT array_agg(ASIN(x))
FROM UNNEST(ARRAY[0.0, 0.5, 1.0]) AS t(x);
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([0.0, 0.5, 1.0])
]
| extend angle = series_asin(values)
```
---
# series_atan
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-atan
The `series_atan` function computes the arc tangent (inverse tangent) for each element in a numeric array (also known as a series). You use it to transform a dynamic array of numbers into their corresponding arc tangent values, expressed in radians. This is useful when you work with time series or array data and want to normalize, analyze, or transform it using trigonometric operations.
You often use `series_atan` in scenarios where you want to transform numeric measurements into angular values for further statistical analysis, pattern recognition, or anomaly detection.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_atan(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------- |
| `array` | dynamic | A dynamic array of numeric values. |
### Returns [#returns]
A dynamic array with the arc tangent of each input element. The results are in radians.
## Use case examples [#use-case-examples]
You want to analyze request durations by converting them into angular values for specialized statistical transformations.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms)
| extend atan_durations = series_atan(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20%7C%20extend%20atan_durations%20%3D%20series_atan\(durations\)%22%7D)
**Output**
| durations | atan\_durations |
| ---------------- | ------------------------- |
| \[10, 200, 5000] | \[1.4711, 1.5658, 1.5706] |
The query aggregates request durations, then transforms them into their arc tangent equivalents for normalized comparison.
You want to transform span durations into angular values for advanced time series modeling.
**Query**
```kusto
['otel-demo-traces']
| summarize spans = make_list(duration) by ['service.name']
| extend atan_spans = series_atan(spans)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20spans%20%3D%20make_list\(duration\)%20by%20%5B'service.name'%5D%20%7C%20extend%20atan_spans%20%3D%20series_atan\(spans\)%22%7D)
**Output**
| service.name | spans | atan\_spans |
| --------------- | --------------------- | ----------------- |
| frontend | \[00:00:01, 00:00:03] | \[0.7854, 1.2490] |
| checkoutservice | \[00:00:05, 00:00:07] | \[1.3734, 1.4289] |
The query collects span durations by service and transforms them into arc tangent values.
You want to analyze failed login attempts by transforming their request durations into angular values.
**Query**
```kusto
['sample-http-logs']
| summarize failed_durations = make_list(req_duration_ms) by id
| extend atan_failed = series_atan(failed_durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20failed_durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20atan_failed%20%3D%20series_atan\(failed_durations\)%22%7D)
**Output**
| id | failed\_durations | atan\_failed |
| ------ | ----------------- | ------------------------- |
| user42 | \[20, 200, 800] | \[1.5208, 1.5658, 1.5696] |
The query collects durations and applies the arc tangent function element-wise.
## List of related functions [#list-of-related-functions]
* [series\_asin](/apl/scalar-functions/time-series/series-asin): Applies the arc sine function element-wise to array values. Use this when you need the inverse sine instead of the inverse cosine.
* [series\_acos](/apl/scalar-functions/time-series/series-acos): Returns the arc cosine of each element in an array. Use when you need to invert cosine transformations instead of sine.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use `eval atan(x)` to calculate the arc tangent of a scalar value, but SPL doesn’t provide a built-in function for applying `atan` across arrays or time series directly. In APL, `series_atan` applies the operation element-wise to arrays, making it easier to work with dynamic and time series data.
```sql Splunk example
... | eval angle=atan(value)
```
```kusto APL equivalent
datatable(arr: dynamic)
[
dynamic([0, 1, -1])
]
| extend atan_arr = series_atan(arr)
```
In ANSI SQL, you use `ATAN(x)` for scalar values. To work with arrays, you typically need to unnest the array, apply `ATAN` to each element, and then aggregate the results back. APL simplifies this with `series_atan`, which applies the arc tangent function element-wise to the entire array.
```sql SQL example
SELECT ATAN(value)
FROM numbers;
```
```kusto APL equivalent
datatable(arr: dynamic)
[
dynamic([0, 1, -1])
]
| extend atan_arr = series_atan(arr)
```
---
# series_ceiling
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-ceiling
The `series_ceiling` function applies the ceiling operation to each element in a dynamic array (series) of numeric values. It rounds each number up to the nearest integer, returning the smallest integer that’s greater than or equal to the input value. This function is useful when you need to normalize fractional values upward or ensure minimum thresholds in your data analysis.
You can use `series_ceiling` when working with metrics that need to be rounded up for capacity planning, resource allocation, or when dealing with partial counts that should be treated as whole units. Common applications include calculating minimum required resources, rounding up processing times for SLA calculations, and normalizing fractional measurements.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_ceiling(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------- |
| `array` | dynamic | A dynamic array of numeric values. |
### Returns [#returns]
A dynamic array where each element is the ceiling (rounded up to the nearest integer) of the corresponding input element.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_ceiling` to round up request durations for capacity planning, ensuring you allocate sufficient resources based on worst-case scenarios.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms / 1000.0) by id
| extend ceiling_durations = series_ceiling(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms%20%2F%201000.0\)%20by%20id%20%7C%20extend%20ceiling_durations%20%3D%20series_ceiling\(durations\)%22%7D)
**Output**
| id | durations | ceiling\_durations |
| ---- | ------------------- | ------------------ |
| u123 | \[0.12, 0.87, 1.23] | \[1, 1, 2] |
| u456 | \[2.45, 0.56] | \[3, 1] |
This query converts request durations to seconds and rounds them up to ensure adequate resource allocation for each user.
In OpenTelemetry traces, you can use `series_ceiling` to round up span durations for SLA calculations, ensuring you meet minimum performance guarantees.
**Query**
```kusto
['otel-demo-traces']
| summarize durations_seconds = make_list(duration / 1s) by ['service.name']
| extend ceiling_durations = series_ceiling(durations_seconds)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20durations_seconds%20%3D%20make_list\(duration%20%2F%201s\)%20by%20%5B'service.name'%5D%20%7C%20extend%20ceiling_durations%20%3D%20series_ceiling\(durations_seconds\)%22%7D)
**Output**
| service.name | durations\_seconds | ceiling\_durations |
| --------------------- | ------------------ | ------------------ |
| frontend | \[0.2, 1.3, 0.8] | \[1, 2, 1] |
| productcatalogservice | \[0.05, 0.12, 2.1] | \[1, 1, 3] |
This query converts span durations to seconds and rounds them up for conservative SLA planning per service.
In security logs, you can use `series_ceiling` to round up processing times for security checks, ensuring adequate time allocation for threat detection processes.
**Query**
```kusto
['sample-http-logs']
| summarize processing_times = make_list(req_duration_ms / 100.0) by status
| extend ceiling_processing_times = series_ceiling(processing_times)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20processing_times%20%3D%20make_list\(req_duration_ms%20%2F%20100.0\)%20by%20status%20%7C%20extend%20ceiling_processing_times%20%3D%20series_ceiling\(processing_times\)%22%7D)
**Output**
| status | processing\_times | ceiling\_processing\_times |
| ------ | ----------------- | -------------------------- |
| 200 | \[1.2, 3.4, 0.8] | \[2, 4, 1] |
| 401 | \[5.7, 2.1] | \[6, 3] |
This query scales processing times and rounds them up to ensure sufficient time allocation for security processing by HTTP status code.
## List of related functions [#list-of-related-functions]
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns the absolute value of each element in an array. Use when you need to remove negative signs without rounding.
* [series\_add](/apl/scalar-functions/time-series/series-add): Performs element-wise addition between two arrays. Use when you need to combine values instead of calculating ratios.
* [series\_cosine\_similarity](/apl/scalar-functions/time-series/series-cosine-similarity): Calculates cosine similarity between two arrays. Use when you need normalized similarity measures rather than raw dot products.
* [series\_divide](/apl/scalar-functions/time-series/series-divide): Performs element-wise division between two arrays. Use when you need to calculate ratios or normalize values.
* [series\_dot\_product](/apl/scalar-functions/time-series/series-dot-product): Calculates the dot product between two arrays. Use when you need the raw dot product value rather than normalized similarity.
* [series\_sum](/apl/scalar-functions/time-series/series-sum): Calculates the sum of all elements in a single array. Use when you need to sum elements within one array rather than computing dot products.
## Other query languages [#other-query-languages]
In Splunk SPL, you use the `eval` command with the `ceil()` function to round values up. In APL, `series_ceiling` applies the ceiling operation to all elements in a dynamic array at once.
```sql Splunk example
... | eval rounded_duration = ceil(duration)
```
```kusto APL equivalent
datatable(x: dynamic)
[
dynamic([1.2, 2.7, 3.1, 4.9])
]
| extend ceiling_values = series_ceiling(x)
```
In SQL, you use the `CEILING()` or `CEIL()` function to round individual values up. However, this only works on scalar values, not arrays. In APL, `series_ceiling` operates on entire dynamic arrays, making it convenient for series analysis.
```sql SQL example
SELECT CEILING(duration) AS rounded_duration
FROM requests;
```
```kusto APL equivalent
datatable(x: dynamic)
[
dynamic([1.2, 2.7, 3.1, 4.9])
]
| extend ceiling_values = series_ceiling(x)
```
---
# series_cos
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-cos
The `series_cos` function returns the cosine of each element in a numeric array. You can use it to apply trigonometric transformations across entire time series or vectorized data in one step. This function is useful when you want to analyze periodic patterns, normalize angles, or apply mathematical transformations to series data such as request durations, response times, or trace latencies.
You often use `series_cos` together with other series functions like `series_sin` and `series_tan` to perform mathematical modeling, anomaly detection, or seasonality analysis in logs and telemetry data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_cos(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | -------------------------- | --------------------------- |
| `array` | dynamic (array of numbers) | An array of numeric values. |
### Returns [#returns]
A dynamic array where each element is the cosine of the corresponding input element.
## Use case examples [#use-case-examples]
You want to model periodic patterns in request durations by applying the cosine function to the values. This is useful if you want to normalize cyclical metrics for further analysis.
**Query**
```kusto
['sample-http-logs']
| summarize durations=make_list(req_duration_ms) by id
| extend cos_durations=series_cos(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%3Dmake_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20cos_durations%3Dseries_cos\(durations\)%22%7D)
**Output**
| id | durations | cos\_durations |
| -- | ---------------- | ------------------------ |
| u1 | \[120, 300, 450] | \[0.814, -0.990, -0.737] |
| u2 | \[50, 250, 400] | \[0.965, -0.801, -0.966] |
This query collects request durations per user ID and applies the cosine transformation to the entire array.
You want to apply trigonometric transformations to span durations to explore cyclical behavior in distributed traces.
**Query**
```kusto
['otel-demo-traces']
| summarize spans=make_list(duration) by ['service.name']
| extend cos_spans=series_cos(spans)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20spans%3Dmake_list\(duration\)%20by%20%5B'service.name'%5D%20%7C%20extend%20cos_spans%3Dseries_cos\(spans\)%22%7D)
**Output**
| service.name | spans | cos\_spans |
| --------------- | --------------------- | ----------------- |
| frontend | \[00:00:01, 00:00:03] | \[0.540, -0.990] |
| checkoutservice | \[00:00:02, 00:00:04] | \[-0.416, -0.653] |
This query groups spans by service and computes the cosine for each span duration, which can be used in advanced mathematical modeling of latency patterns.
You want to explore whether cosine transformations reveal patterns in request durations for suspicious traffic sources.
**Query**
```kusto
['sample-http-logs']
| summarize durations=make_list(req_duration_ms) by ['geo.country']
| extend cos_blocked=series_cos(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%3Dmake_list\(req_duration_ms\)%20by%20%5B'geo.country'%5D%20%7C%20extend%20cos_blocked%3Dseries_cos\(durations\)%22%7D)
**Output**
| geo.country | blocked\_durations | cos\_blocked |
| ----------- | ------------------ | ------------------------ |
| US | \[200, 400, 600] | \[-0.416, -0.653, 0.960] |
| DE | \[100, 250, 500] | \[0.540, -0.801, 0.284] |
This query applies the cosine function to blocked request durations grouped by country, which can help highlight periodic access attempts from malicious sources.
## List of related functions [#list-of-related-functions]
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns the absolute value of each element in an array. Use it to normalize negative values in arrays.
* [series\_acos](/apl/scalar-functions/time-series/series-acos): Computes the arccosine of each element in an array. Use when you want the inverse cosine.
* [series\_atan](/apl/scalar-functions/time-series/series-atan): Computes the arctangent of each element in an array. Use when you want the inverse tangent.
* [series\_sin](/apl/scalar-functions/time-series/series-sin): Returns the sine of each element in an array. Use it when analyzing cyclical data with a phase shift.
* [series\_tan](/apl/scalar-functions/time-series/series-tan): Returns the tangent of each element in an array. Use it when you want to transform arrays with tangent-based periodicity.
## Other query languages [#other-query-languages]
In Splunk SPL, trigonometric functions like `cos` operate on single field values, not on arrays. To compute cosine across multiple values, you typically expand the values into events and then apply the `eval cos(field)` transformation. In APL, `series_cos` works natively on dynamic arrays, so you can directly transform an entire series in one call.
```sql Splunk example
... | eval cos_val=cos(angle)
```
```kusto APL equivalent
print arr=dynamic([0, 1.57, 3.14])
| extend cos_arr=series_cos(arr)
```
ANSI SQL does not provide direct support for array-wide trigonometric functions. The `COS()` function only works on single numeric values. To achieve array-like functionality, you usually need to unnest arrays and apply `COS()` row by row. In APL, `series_cos` eliminates this need by directly accepting an array and returning a transformed array.
```sql SQL example
SELECT COS(angle) AS cos_val
FROM Angles;
```
```kusto APL equivalent
print arr=dynamic([0, 1.57, 3.14])
| extend cos_arr=series_cos(arr)
```
---
# series_cosine_similarity
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-cosine-similarity
The `series_cosine_similarity` function calculates the cosine similarity between two dynamic arrays (series) of numeric values. Cosine similarity measures the cosine of the angle between two vectors, providing a metric of similarity that ranges from -1 to 1. A value of 1 indicates identical direction, 0 indicates orthogonality (no similarity), and -1 indicates opposite directions. This function is particularly useful for comparing patterns, trends, and behaviors in time-series data.
You can use `series_cosine_similarity` when you need to identify similar patterns in different datasets, compare user behaviors, detect anomalies by measuring deviation from normal patterns, or find correlations between different metrics. Common applications include recommendation systems, anomaly detection, pattern matching in performance metrics, and behavioral analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_cosine_similarity(array1, array2)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ------------------------------------------- |
| `array1` | dynamic | The first dynamic array of numeric values. |
| `array2` | dynamic | The second dynamic array of numeric values. |
### Returns [#returns]
A `real` value between -1 and 1 representing the cosine similarity between the two arrays. Returns `null` if either array is empty or contains only zeros.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_cosine_similarity` to compare request duration patterns between different users to identify similar usage behaviors.
**Query**
```kusto
['sample-http-logs']
| summarize user1_durations = make_list(iff(id == 'user1', req_duration_ms, 0)), user2_durations = make_list(iff(id == 'user2', req_duration_ms, 0))
| extend similarity = series_cosine_similarity(user1_durations, user2_durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20user1_durations%20%3D%20make_list\(iff\(id%20%3D%3D%20'user1'%2C%20req_duration_ms%2C%200\)\)%2C%20user2_durations%20%3D%20make_list\(iff\(id%20%3D%3D%20'user2'%2C%20req_duration_ms%2C%200\)\)%20%7C%20extend%20similarity%20%3D%20series_cosine_similarity\(user1_durations%2C%20user2_durations\)%22%7D)
**Output**
| user1\_durations | user2\_durations | similarity |
| ----------------- | ----------------- | ---------- |
| \[120, 0, 300, 0] | \[0, 150, 0, 280] | 0.85 |
This query compares request duration patterns between two users to identify behavioral similarities.
In OpenTelemetry traces, you can use `series_cosine_similarity` to compare span duration patterns between different services to identify similar performance characteristics.
**Query**
```kusto
['otel-demo-traces']
| summarize frontend_durations = make_list(iff(['service.name'] == 'frontend', duration / 1ms, 0)), cart_durations = make_list(iff(['service.name'] == 'cart', duration / 1ms, 0))
| extend pattern_similarity = series_cosine_similarity(frontend_durations, cart_durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20frontend_durations%20%3D%20make_list\(iff\(%5B'service.name'%5D%20%3D%3D%20'frontend'%2C%20duration%20%2F%201ms%2C%200\)\)%2C%20cart_durations%20%3D%20make_list\(iff\(%5B'service.name'%5D%20%3D%3D%20'cart'%2C%20duration%20%2F%201ms%2C%200\)\)%20%7C%20extend%20pattern_similarity%20%3D%20series_cosine_similarity\(frontend_durations%2C%20cart_durations\)%22%7D)
**Output**
| frontend\_durations | cart\_durations | pattern\_similarity |
| ------------------- | ---------------- | ------------------- |
| \[200, 0, 150, 0] | \[0, 80, 0, 120] | 0.72 |
This query compares performance patterns between frontend and cart services to identify correlated behaviors.
In security logs, you can use `series_cosine_similarity` to compare request patterns between different HTTP status codes to detect anomalous behavior.
**Query**
```kusto
['sample-http-logs']
| summarize success_durations = make_list(iff(status == '200', req_duration_ms, 0)), error_durations = make_list(iff(status == '500', req_duration_ms, 0))
| extend behavior_similarity = series_cosine_similarity(success_durations, error_durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20success_durations%20%3D%20make_list\(iff\(status%20%3D%3D%20'200'%2C%20req_duration_ms%2C%200\)\)%2C%20error_durations%20%3D%20make_list\(iff\(status%20%3D%3D%20'500'%2C%20req_duration_ms%2C%200\)\)%20%7C%20extend%20behavior_similarity%20%3D%20series_cosine_similarity\(success_durations%2C%20error_durations\)%22%7D)
**Output**
| success\_durations | error\_durations | behavior\_similarity |
| ------------------ | ----------------- | -------------------- |
| \[100, 0, 150, 0] | \[0, 250, 0, 300] | 0.23 |
This query compares request duration patterns between successful and error responses to identify potential security anomalies.
## List of related functions [#list-of-related-functions]
* [series\_add](/apl/scalar-functions/time-series/series-add): Performs element-wise addition between two arrays. Use when you need to combine values instead of calculating ratios.
* [series\_divide](/apl/scalar-functions/time-series/series-divide): Performs element-wise division between two arrays. Use when you need to calculate ratios or normalize values.
* [series\_dot\_product](/apl/scalar-functions/time-series/series-dot-product): Calculates the dot product between two arrays. Use when you need the raw dot product value rather than normalized similarity.
* [series\_sum](/apl/scalar-functions/time-series/series-sum): Calculates the sum of all elements in a single array. Use when you need to sum elements within one array rather than computing dot products.
## Other query languages [#other-query-languages]
In Splunk SPL, calculating cosine similarity requires complex mathematical operations using `eval` commands with square roots and dot products. In APL, `series_cosine_similarity` provides this calculation directly for dynamic arrays.
```sql Splunk example
... | eval dot_product = mvzip(array1, array2) | eval similarity = dot_product / (sqrt(sum1) * sqrt(sum2))
```
```kusto APL equivalent
datatable(x: dynamic, y: dynamic)
[
dynamic([0.5*pi(), 1.0*pi(), 1.5*pi()]),
dynamic([2.0*pi(), 2.5*pi(), 3.0*pi()])
]
| extend similarity = series_cosine_similarity(x, y)
```
In SQL, calculating cosine similarity requires complex operations involving dot products, magnitudes, and square roots across multiple rows. You would typically need window functions and mathematical operations. In APL, `series_cosine_similarity` handles this calculation directly on dynamic arrays.
```sql SQL example
SELECT
SUM(a.value * b.value) /
(SQRT(SUM(a.value * a.value)) * SQRT(SUM(b.value * b.value))) AS similarity
FROM array_a a, array_b b
WHERE a.index = b.index;
```
```kusto APL equivalent
datatable(x: dynamic, y: dynamic)
[
dynamic([0.5*pi(), 1.0*pi(), 1.5*pi()]),
dynamic([2.0*pi(), 2.5*pi(), 3.0*pi()])
]
| extend similarity = series_cosine_similarity(x, y)
```
---
# series_divide
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-divide
The `series_divide` function performs element-wise division between two dynamic arrays (series) of numeric values. It divides corresponding elements from the first array by the corresponding elements in the second array and returns a new array containing the results. This function is useful when you need to calculate ratios, normalize data, or perform proportional analysis across time-series datasets.
You can use `series_divide` when you want to calculate rates, percentages, or ratios between different metrics. Common applications include calculating success rates, determining resource utilization ratios, computing performance improvements, and normalizing metrics for comparison across different scales.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_divide(array1, array2)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ------------------------------------------------ |
| `array1` | dynamic | The numerator dynamic array of numeric values. |
| `array2` | dynamic | The denominator dynamic array of numeric values. |
### Returns [#returns]
A dynamic array where each element is the result of dividing the corresponding element from `array1` by the corresponding element from `array2`. If the arrays have different lengths, the result array has the length of the shorter array. Division by zero returns infinity or negative infinity.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_divide` to calculate success rates by dividing successful requests by total requests for each time period.
**Query**
```kusto
['sample-http-logs']
| summarize successful_requests = make_list(iff(status == '200', 1, 0)), total_requests = make_list(1) by id
| extend success_rates = series_divide(successful_requests, total_requests)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20successful_requests%20%3D%20make_list\(iff\(status%20%3D%3D%20'200'%2C%201%2C%200\)\)%2C%20total_requests%20%3D%20make_list\(1\)%20by%20id%20%7C%20extend%20success_rates%20%3D%20series_divide\(successful_requests%2C%20total_requests\)%22%7D)
**Output**
| id | successful\_requests | total\_requests | success\_rates |
| ---- | -------------------- | --------------- | -------------- |
| u123 | \[1, 0, 1] | \[1, 1, 1] | \[1, 0, 1] |
| u456 | \[1, 1] | \[1, 1] | \[1, 1] |
This query calculates success rates by dividing successful requests by total requests for each user.
In OpenTelemetry traces, you can use `series_divide` to calculate performance improvement ratios by comparing current span durations to baseline durations.
**Query**
```kusto
['otel-demo-traces']
| summarize current_durations = make_list(duration / 1ms), baseline_durations = make_list(100.0) by ['service.name']
| extend performance_ratios = series_divide(current_durations, baseline_durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20current_durations%20%3D%20make_list\(duration%20%2F%201ms\)%2C%20baseline_durations%20%3D%20make_list\(100.0\)%20by%20%5B'service.name'%5D%20%7C%20extend%20performance_ratios%20%3D%20series_divide\(current_durations%2C%20baseline_durations\)%22%7D)
**Output**
| service.name | current\_durations | baseline\_durations | performance\_ratios |
| --------------------- | ------------------ | ------------------- | ------------------- |
| frontend | \[80, 120, 90] | \[100, 100, 100] | \[0.8, 1.2, 0.9] |
| productcatalogservice | \[50, 150] | \[100, 100] | \[0.5, 1.5] |
This query calculates performance ratios by dividing current span durations by baseline values for each service.
In security logs, you can use `series_divide` to calculate error rates by dividing error responses by total responses for different request types.
**Query**
```kusto
['sample-http-logs']
| summarize error_requests = make_list(iff(status != '200', 1, 0)), total_requests = make_list(1) by method
| extend error_rates = series_divide(error_requests, total_requests)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20error_requests%20%3D%20make_list\(iff\(status%20!%3D%20'200'%2C%201%2C%200\)\)%2C%20total_requests%20%3D%20make_list\(1\)%20by%20method%20%7C%20extend%20error_rates%20%3D%20series_divide\(error_requests%2C%20total_requests\)%22%7D)
**Output**
| method | error\_requests | total\_requests | error\_rates |
| ------ | --------------- | --------------- | ------------ |
| GET | \[0, 1, 0] | \[1, 1, 1] | \[0, 1, 0] |
| POST | \[1, 0] | \[1, 1] | \[1, 0] |
This query calculates error rates by dividing error requests by total requests for each HTTP method.
## List of related functions [#list-of-related-functions]
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns the absolute value of each element in an array. Use when you need to remove negative signs without rounding.
* [series\_add](/apl/scalar-functions/time-series/series-add): Performs element-wise addition between two arrays. Use when you need to combine values instead of calculating ratios.
* [series\_cosine\_similarity](/apl/scalar-functions/time-series/series-cosine-similarity): Calculates cosine similarity between two arrays. Use when you need normalized similarity measures rather than raw dot products.
* [series\_dot\_product](/apl/scalar-functions/time-series/series-dot-product): Calculates the dot product between two arrays. Use when you need the raw dot product value rather than normalized similarity.
* [series\_sum](/apl/scalar-functions/time-series/series-sum): Calculates the sum of all elements in a single array. Use when you need to sum elements within one array rather than computing dot products.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use the `eval` command with the division operator to divide values. However, dividing arrays element-wise requires more complex operations. In APL, `series_divide` directly performs element-wise division on dynamic arrays.
```sql Splunk example
... | eval ratio = field1 / field2
```
```kusto APL equivalent
datatable(x: dynamic, y: dynamic)
[
dynamic([10, 20, 30]), dynamic([2, 4, 5])
]
| extend ratio_series = series_divide(x, y)
```
In SQL, you divide individual values using the `/` operator, but there's no built-in function for element-wise array division. You would need to unnest arrays and perform complex operations. In APL, `series_divide` handles this operation directly on dynamic arrays.
```sql SQL example
SELECT value1 / value2 AS ratio
FROM my_table;
```
```kusto APL equivalent
datatable(x: dynamic, y: dynamic)
[
dynamic([10, 20, 30]), dynamic([2, 4, 5])
]
| extend ratio_series = series_divide(x, y)
```
---
# series_dot_product
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-dot-product
The `series_dot_product` function calculates the dot product between two dynamic arrays (series) of numeric values. The dot product is computed by multiplying corresponding elements from both arrays and then summing all the products. This mathematical operation is fundamental in linear algebra and is useful for measuring similarity, calculating projections, and performing various analytical computations on time-series data.
You can use `series_dot_product` when you need to measure the similarity between two datasets, calculate weighted sums, perform correlation analysis, or compute projections in multidimensional analysis. Common applications include recommendation systems, signal processing, pattern recognition, and statistical analysis of performance metrics.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_dot_product(array1, array2)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ------------------------------------------- |
| `array1` | dynamic | The first dynamic array of numeric values. |
| `array2` | dynamic | The second dynamic array of numeric values. |
### Returns [#returns]
A `real` value representing the dot product of the two arrays. If the arrays have different lengths, only elements up to the length of the shorter array are used in the calculation.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_dot_product` to calculate weighted similarity scores between user request patterns, where weights represent the importance of different time periods.
**Query**
```kusto
['sample-http-logs']
| summarize request_counts = make_list(1), importance_weights = make_list(req_duration_ms / 100.0) by id
| extend weighted_score = series_dot_product(request_counts, importance_weights)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20request_counts%20%3D%20make_list\(1\)%2C%20importance_weights%20%3D%20make_list\(req_duration_ms%20%2F%20100.0\)%20by%20id%20%7C%20extend%20weighted_score%20%3D%20series_dot_product\(request_counts%2C%20importance_weights\)%22%7D)
**Output**
| id | request\_counts | importance\_weights | weighted\_score |
| ---- | --------------- | ------------------- | --------------- |
| u123 | \[1, 1, 1] | \[1.2, 3.4, 0.8] | 5.4 |
| u456 | \[1, 1] | \[2.1, 1.5] | 3.6 |
This query calculates weighted activity scores by computing the dot product of request counts and duration-based importance weights.
In OpenTelemetry traces, you can use `series_dot_product` to calculate correlation scores between span durations and error rates across different services.
**Query**
```kusto
['otel-demo-traces']
| summarize durations = make_list(duration / 1ms), error_indicators = make_list(iff(status_code != '200', 1.0, 0.0)) by ['service.name']
| extend correlation_score = series_dot_product(durations, error_indicators)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(duration%20%2F%201ms\)%2C%20error_indicators%20%3D%20make_list\(iff\(status_code%20!%3D%20'200'%2C%201.0%2C%200.0\)\)%20by%20%5B'service.name'%5D%20%7C%20extend%20correlation_score%20%3D%20series_dot_product\(durations%2C%20error_indicators\)%22%7D)
**Output**
| service.name | durations | error\_indicators | correlation\_score |
| --------------------- | ---------------- | ----------------- | ------------------ |
| frontend | \[200, 150, 300] | \[0, 1, 0] | 150 |
| productcatalogservice | \[80, 120] | \[1, 0] | 80 |
This query calculates correlation scores between span durations and error occurrences to identify performance-error relationships.
In security logs, you can use `series_dot_product` to calculate risk scores by combining request frequencies with security threat levels.
**Query**
```kusto
['sample-http-logs']
| summarize request_frequencies = make_list(1), threat_levels = make_list(iff(status == '401', 3.0, iff(status == '403', 2.0, 1.0))) by ['geo.country']
| extend risk_score = series_dot_product(request_frequencies, threat_levels)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20request_frequencies%20%3D%20make_list\(1\)%2C%20threat_levels%20%3D%20make_list\(iff\(status%20%3D%3D%20'401'%2C%203.0%2C%20iff\(status%20%3D%3D%20'403'%2C%202.0%2C%201.0\)\)\)%20by%20%5B'geo.country'%5D%20%7C%20extend%20risk_score%20%3D%20series_dot_product\(request_frequencies%2C%20threat_levels\)%22%7D)
**Output**
| geo.country | request\_frequencies | threat\_levels | risk\_score |
| ----------- | -------------------- | -------------- | ----------- |
| US | \[1, 1, 1] | \[1, 3, 1] | 5 |
| UK | \[1, 1] | \[2, 1] | 3 |
This query calculates security risk scores by computing the dot product of request frequencies and threat levels by country.
## List of related functions [#list-of-related-functions]
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns the absolute value of each element in an array. Use when you need to remove negative signs without rounding.
* [series\_add](/apl/scalar-functions/time-series/series-add): Performs element-wise addition between two arrays. Use when you need to combine values instead of calculating ratios.
* [series\_cosine\_similarity](/apl/scalar-functions/time-series/series-cosine-similarity): Calculates cosine similarity between two arrays. Use when you need normalized similarity measures rather than raw dot products.
* [series\_divide](/apl/scalar-functions/time-series/series-divide): Performs element-wise division between two arrays. Use when you need to calculate ratios or normalize values.
* [series\_sum](/apl/scalar-functions/time-series/series-sum): Calculates the sum of all elements in a single array. Use when you need to sum elements within one array rather than computing dot products.
## Other query languages [#other-query-languages]
In Splunk SPL, calculating dot products requires complex operations using `eval` commands with array manipulation and mathematical functions. In APL, `series_dot_product` provides this calculation directly for dynamic arrays.
```sql Splunk example
... | eval products = mvzip(array1, array2, "*") | eval dot_product = mvsum(products)
```
```kusto APL equivalent
datatable(x: dynamic, y: dynamic)
[
dynamic([1, 2, 3]), dynamic([4, 5, 6])
]
| extend dot_product = series_dot_product(x, y)
```
In SQL, calculating dot products requires joining arrays, multiplying corresponding elements, and summing the results. This typically involves complex window functions and mathematical operations. In APL, `series_dot_product` handles this calculation directly on dynamic arrays.
```sql SQL example
SELECT SUM(a.value * b.value) AS dot_product
FROM array_a a
JOIN array_b b ON a.index = b.index;
```
```kusto APL equivalent
datatable(x: dynamic, y: dynamic)
[
dynamic([1, 2, 3]), dynamic([4, 5, 6])
]
| extend dot_product = series_dot_product(x, y)
```
---
# series_equals
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-equals
The `series_equals` function compares each element in a numeric dynamic array (series) to a specified value and returns a boolean array indicating which elements are equal to that value. This function is useful for filtering, conditional analysis, and identifying specific values within time series data.
You can use `series_equals` when you want to identify occurrences of specific values in your data, such as finding exact matches for thresholds, status codes, or target values. Typical applications include anomaly detection, data validation, and conditional processing of time series data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_equals(array, value)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ------------------------------------------------ |
| `array` | dynamic | A dynamic array of real numeric values. |
| `value` | numeric | The value to compare against each array element. |
### Returns [#returns]
A dynamic array of boolean values where each element indicates whether the corresponding input element equals the specified value.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_equals` to identify requests that match specific duration thresholds or status codes across multiple requests per user.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend is_200ms = series_equals(durations, 200)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20is_200ms%20%3D%20series_equals\(durations%2C%20200\)%22%7D)
**Output**
| id | durations | is\_200ms |
| ---- | ---------------- | --------------------- |
| u123 | \[150, 200, 250] | \[false, true, false] |
| u456 | \[200, 200, 180] | \[true, true, false] |
This query identifies which request durations exactly equal 200ms for each user, useful for finding requests that hit specific performance targets.
In OpenTelemetry traces, you can use `series_equals` to identify spans with specific duration values or status codes across multiple spans per service.
**Query**
```kusto
['otel-demo-traces']
| summarize durations = make_list(toreal(duration)) by ['service.name']
| extend is_1s = series_equals(durations, toreal(1s))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(toreal\(duration\)\)%20by%20%5B'service.name'%5D%20%7C%20extend%20is_1s%20%3D%20series_equals\(durations%2C%20toreal\(1s\)\)%22%7D)
**Output**
| service.name | durations | is\_1s |
| --------------------- | ------------------ | --------------------- |
| frontend | \[800, 1000, 1200] | \[false, true, false] |
| productcatalogservice | \[1000, 1000, 900] | \[true, true, false] |
This query identifies spans with exactly 1-second durations per service, useful for finding spans that hit specific latency targets.
In security logs, you can use `series_equals` to identify requests with specific status codes or durations that might indicate security events.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by status
| extend is_500ms = series_equals(durations, 500)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20is_500ms%20%3D%20series_equals\(durations%2C%20500\)%22%7D)
**Output**
| status | durations | is\_500ms |
| ------ | ---------------- | --------------------- |
| 200 | \[300, 500, 400] | \[false, true, false] |
| 500 | \[500, 500, 600] | \[true, true, false] |
This query identifies requests with exactly 500ms duration grouped by status code, useful for finding requests that hit specific timing thresholds.
## List of related functions [#list-of-related-functions]
* [series\_greater](/apl/scalar-functions/time-series/series-greater): Returns elements greater than a specified value. Use when you need threshold-based filtering instead of exact matches.
* [series\_greater\_equals](/apl/scalar-functions/time-series/series-greater-equals): Returns elements greater than or equal to a specified value. Use for inclusive threshold comparisons.
* [series\_less](/apl/scalar-functions/time-series/series-less): Returns elements less than a specified value. Use for lower-bound filtering.
* [series\_less\_equals](/apl/scalar-functions/time-series/series-less-equals): Returns elements less than or equal to a specified value. Use for inclusive lower-bound comparisons.
* [series\_not\_equals](/apl/scalar-functions/time-series/series-not-equals): Returns elements not equal to a specified value. Use for exclusion-based filtering.
## Other query languages [#other-query-languages]
In Splunk SPL, equality comparisons are typically done with the `eval` function and comparison operators like `==`. To compare multiple values, you usually need to expand arrays and apply comparisons row by row. In APL, `series_equals` works directly on dynamic arrays, making it efficient for series-wide comparisons.
```sql Splunk example
... | eval is_target=(duration==200)
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([150, 200, 250, 200])
]
| extend equals_200 = series_equals(values, 200)
```
In SQL, equality comparisons use the `=` operator, but this only works on single values, not arrays. To compare array elements, you typically need to unnest arrays and apply comparisons row by row. In APL, `series_equals` eliminates this complexity by directly comparing each element in an array to a target value.
```sql SQL example
SELECT CASE WHEN duration = 200 THEN 1 ELSE 0 END AS is_target
FROM requests;
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([150, 200, 250, 200])
]
| extend equals_200 = series_equals(values, 200)
```
---
# series_exp
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-exp
The `series_exp` function calculates the exponential (e^x) of each element in a numeric dynamic array (series). This function applies the mathematical exponential transformation element-wise across the entire array, which is useful for mathematical modeling, growth analysis, and data transformation in time series data.
You can use `series_exp` when you want to apply exponential transformations to your data, such as modeling exponential growth patterns, converting logarithmic data back to linear scale, or applying mathematical transformations for machine learning preprocessing. Typical applications include financial modeling, population growth analysis, and signal processing.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_exp(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------- |
| `array` | dynamic | A dynamic array of numeric values. |
### Returns [#returns]
A dynamic array where each element is the exponential (e^x) of the corresponding input element.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_exp` to transform logarithmic request durations back to linear scale or model exponential growth patterns in user activity.
**Query**
```kusto
['sample-http-logs']
| summarize log_durations = make_list(log(req_duration_ms)) by id
| extend exp_durations = series_exp(log_durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20log_durations%20%3D%20make_list\(log\(req_duration_ms\)\)%20by%20id%20%7C%20extend%20exp_durations%20%3D%20series_exp\(log_durations\)%22%7D)
**Output**
| id | log\_durations | exp\_durations |
| ---- | ---------------- | --------------------- |
| u123 | \[4.6, 5.0, 5.3] | \[99.5, 148.4, 200.3] |
| u456 | \[4.2, 4.8, 5.1] | \[66.7, 121.5, 164.0] |
This query transforms logarithmic request durations back to their original linear scale, useful for analyzing actual performance metrics.
In OpenTelemetry traces, you can use `series_exp` to model exponential growth patterns in span durations or transform logarithmic latency data for analysis.
**Query**
```kusto
['otel-demo-traces']
| summarize log_durations = make_list(log(toint(duration))) by ['service.name']
| extend exp_durations = series_exp(log_durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20log_durations%20%3D%20make_list\(log\(toint\(duration\)\)\)%20by%20%5B'service.name'%5D%20%7C%20extend%20exp_durations%20%3D%20series_exp\(log_durations\)%22%7D)
**Output**
| service.name | log\_durations | exp\_durations |
| --------------------- | ---------------- | ---------------------- |
| frontend | \[6.2, 6.5, 6.8] | \[492.7, 665.1, 897.9] |
| productcatalogservice | \[5.8, 6.1, 6.4] | \[330.3, 445.9, 601.8] |
This query transforms logarithmic span durations back to linear scale, useful for analyzing actual latency patterns across services.
In security logs, you can use `series_exp` to analyze exponential patterns in request frequencies or transform logarithmic attack intensity data.
**Query**
```kusto
['sample-http-logs']
| summarize log_durations = make_list(log(req_duration_ms)) by status
| extend exp_durations = series_exp(log_durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20log_durations%20%3D%20make_list\(log\(req_duration_ms\)\)%20by%20status%20%7C%20extend%20exp_durations%20%3D%20series_exp\(log_durations\)%22%7D)
**Output**
| status | log\_durations | exp\_durations |
| ------ | ---------------- | ---------------------- |
| 200 | \[4.5, 5.0, 5.5] | \[90.0, 148.4, 245.0] |
| 500 | \[5.2, 5.7, 6.2] | \[181.3, 298.9, 492.7] |
This query transforms logarithmic request durations back to linear scale grouped by status code, useful for analyzing actual performance patterns in different response types.
## List of related functions [#list-of-related-functions]
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns the absolute value of each element in an array. Use when you need to normalize values before applying exponential transformations.
* [series\_cos](/apl/scalar-functions/time-series/series-cos): Returns the cosine of each element in an array. Use for trigonometric transformations instead of exponential.
* [series\_sin](/apl/scalar-functions/time-series/series-sin): Returns the sine of each element in an array. Use for periodic transformations instead of exponential growth.
* [series\_tan](/apl/scalar-functions/time-series/series-tan): Returns the tangent of each element in an array. Use for trigonometric transformations with different periodicity.
* [series\_floor](/apl/scalar-functions/time-series/series-floor): Returns the floor of each element in an array. Use for rounding down instead of exponential transformation.
## Other query languages [#other-query-languages]
In Splunk SPL, exponential calculations are typically done with the `eval` function and the `exp()` expression. To compute exponentials across multiple values, you usually need to expand arrays and apply the transformation row by row. In APL, `series_exp` works directly on dynamic arrays, making it efficient for series-wide exponential transformations.
```sql Splunk example
... | eval exp_val=exp(log_value)
```
```kusto APL equivalent
datatable(log_values: dynamic)
[
dynamic([0, 1, 2, 3])
]
| extend exp_values = series_exp(log_values)
```
In SQL, exponential calculations use the `EXP()` function, but this only works on single values, not arrays. To compute exponentials for array elements, you typically need to unnest arrays and apply `EXP()` row by row. In APL, `series_exp` eliminates this complexity by directly applying exponential transformation to each element in an array.
```sql SQL example
SELECT EXP(log_value) AS exp_val
FROM measurements;
```
```kusto APL equivalent
datatable(log_values: dynamic)
[
dynamic([0, 1, 2, 3])
]
| extend exp_values = series_exp(log_values)
```
---
# series_fft
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-fft
The `series_fft` function applies the Fast Fourier Transform (FFT) on a series, taking a series of complex numbers in the time/spatial domain and transforming it to the frequency domain. The transformed complex series represents the magnitude and phase of the frequencies appearing in the original series. Use the complementary function `series_ifft` to transform from the frequency domain back to the time/spatial domain.
You can use `series_fft` when you want to analyze the frequency components of your data, detect periodic patterns, or perform spectral analysis. Typical applications include identifying seasonal patterns in logs, detecting anomalies in telemetry data, analyzing network traffic patterns, and performing signal processing on time series data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_fft(x_real [, x_imaginary])
```
### Parameters [#parameters]
| Parameter | Type | Description |
| ------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `x_real` | dynamic | A numeric array representing the real component of the series to transform. |
| `x_imaginary` | dynamic | Optional: A dynamic array of real numeric values representing the imaginary component of the series. Only specify this if the input series contains complex numbers. |
### Returns [#returns]
The function returns the complex FFT in two series. The first series for the real component and the second one for the imaginary component.
## Example [#example]
Use `series_fft` to identify periodic patterns in request durations, such as daily or hourly cycles in app performance.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by bin(_time, 1h)
| extend fft_analysis = series_fft(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20bin\(_time%2C%201h\)%20%7C%20extend%20fft_analysis%20%3D%20series_fft\(durations\)%22%7D)
**Output**
| durations | fft\_analysis |
| ------------------ | ------------------- |
| 0.5440150626057182 | 20.661607812192038 |
| 0.4854750276771741 | -0.9608495167924037 |
## List of related functions [#list-of-related-functions]
* [series\_ifft](/apl/scalar-functions/time-series/series-ifft): Performs the inverse FFT to convert frequency domain data back to time domain. Use when you need to reconstruct the original signal from frequency components.
* [series\_fir](/apl/scalar-functions/time-series/series-fir): Applies a finite impulse response filter to a series. Use for signal filtering and noise reduction.
* [series\_cos](/apl/scalar-functions/time-series/series-cos): Returns the cosine of each element in an array. Use for trigonometric analysis of periodic patterns.
* [series\_sin](/apl/scalar-functions/time-series/series-sin): Returns the sine of each element in an array. Use for analyzing periodic components in signals.
* [series\_exp](/apl/scalar-functions/time-series/series-exp): Calculates the exponential of each element in an array. Use for exponential growth analysis instead of frequency analysis.
## Other query languages [#other-query-languages]
In Splunk SPL, FFT operations aren’t natively available and typically require external tools or complex workarounds. Most Splunk users rely on statistical functions and time-based aggregations for pattern analysis. In APL, `series_fft` provides direct access to frequency domain analysis, enabling sophisticated signal processing capabilities.
ANSI SQL doesn’t provide FFT functionality. Database systems typically require specialized extensions or external libraries for frequency domain analysis. Most SQL users rely on window functions and statistical aggregations for time series analysis. In APL, `series_fft` brings advanced signal processing capabilities directly into the query language.
---
# series_fill_backward
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-fill-backward
The `series_fill_backward` function fills missing values (nulls) in a numeric dynamic array (series) by propagating the last known value backward through the array. This function is useful for handling gaps in time series data where you want to use the most recent available value to fill earlier missing data points.
You can use `series_fill_backward` when you have time series data with missing values and want to fill gaps using the last observed value. This is particularly useful for forward-looking analysis, forecasting scenarios, or when the most recent data point is the best estimate for missing earlier values. Typical applications include financial data analysis, sensor data processing, and performance monitoring where recent values are more relevant.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_fill_backward(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------------------------------- |
| `array` | dynamic | A dynamic array of numeric values that may contain null values. |
### Returns [#returns]
A dynamic array where null values are replaced by the last non-null value encountered when traversing the array backward.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_fill_backward` to fill missing request duration data using the most recent available values, which is useful for analyzing performance trends.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend filled_durations = series_fill_backward(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20filled_durations%20%3D%20series_fill_backward\(durations\)%22%7D)
**Output**
| id | durations | filled\_durations |
| ---- | ----------------------- | --------------------- |
| u123 | \[null, 150, null, 200] | \[150, 150, 150, 200] |
| u456 | \[100, null, null, 300] | \[100, 300, 300, 300] |
This query fills missing request durations with the most recent available values, useful for maintaining continuity in performance analysis.
In OpenTelemetry traces, you can use `series_fill_backward` to fill missing span duration data using the most recent observed values for better trace analysis.
**Query**
```kusto
['otel-demo-traces']
| summarize durations = make_list(duration) by ['service.name']
| extend filled_durations = series_fill_backward(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(duration\)%20by%20%5B'service.name'%5D%20%7C%20extend%20filled_durations%20%3D%20series_fill_backward\(durations\)%22%7D)
**Output**
| service.name | durations | filled\_durations |
| --------------------- | --------------------------- | ----------------------------- |
| frontend | \[null, 100ms, null, 200ms] | \[100ms, 100ms, 100ms, 200ms] |
| productcatalogservice | \[50ms, null, null, 150ms] | \[50ms, 150ms, 150ms, 150ms] |
This query fills missing span durations with the most recent available values, useful for maintaining continuity in service performance analysis.
In security logs, you can use `series_fill_backward` to fill missing request duration data using the most recent values for consistent security analysis.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by status
| extend filled_durations = series_fill_backward(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20filled_durations%20%3D%20series_fill_backward\(durations\)%22%7D)
**Output**
| status | durations | filled\_durations |
| ------ | ----------------------- | --------------------- |
| 200 | \[null, 150, null, 250] | \[150, 150, 150, 250] |
| 500 | \[100, null, null, 400] | \[100, 400, 400, 400] |
This query fills missing request durations with the most recent available values grouped by status code, useful for consistent security analysis across different response types.
## List of related functions [#list-of-related-functions]
* [series\_fill\_forward](/apl/scalar-functions/time-series/series-fill-forward): Fills missing values by propagating the first known value forward. Use when you want to use the earliest available value to fill gaps.
* [series\_fill\_const](/apl/scalar-functions/time-series/series-fill-const): Fills missing values with a constant value. Use when you want to replace nulls with a specific default value.
* [series\_fill\_linear](/apl/scalar-functions/time-series/series-fill-linear): Fills missing values using linear interpolation. Use when you want smooth transitions between known values.
* [series\_equals](/apl/scalar-functions/time-series/series-equals): Compares each element to a specified value. Use for identifying specific values after filling operations.
* [series\_greater](/apl/scalar-functions/time-series/series-greater): Returns elements greater than a specified value. Use for threshold analysis after filling missing data.
## Other query languages [#other-query-languages]
In Splunk SPL, filling missing values typically requires complex `eval` expressions with `fillnull` or custom logic using `streamstats` and `filldown`. The backward filling approach is less common and usually requires manual implementation. In APL, `series_fill_backward` provides a direct, efficient way to perform backward filling on dynamic arrays.
```sql Splunk example
... | fillnull value=0 | streamstats window=5 current=f last(field) as filled_field
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([null, null, 100, null, 200])
]
| extend filled_values = series_fill_backward(values)
```
In SQL, filling missing values backward requires complex window functions with `LAG()` or custom logic using `LAST_VALUE()` with specific window specifications. Most SQL implementations focus on forward filling rather than backward filling. In APL, `series_fill_backward` simplifies this operation by directly handling backward propagation of values in arrays.
```sql SQL example
SELECT LAST_VALUE(value) OVER (ORDER BY timestamp ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS filled_value
FROM measurements;
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([null, null, 100, null, 200])
]
| extend filled_values = series_fill_backward(values)
```
---
# series_fill_const
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-fill-const
The `series_fill_const` function fills missing values (nulls) in a numeric dynamic array (series) with a specified constant value. This function is useful for handling gaps in time series data where you want to replace missing data points with a known default value.
You can use `series_fill_const` when you have time series data with missing values and want to fill gaps with a specific constant value, such as zero, a default threshold, or a neutral value. This is particularly useful when missing values represent a specific state (like no activity, default configuration, or baseline values). Typical applications include financial data analysis, sensor data processing, and performance monitoring where a specific default value is meaningful.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_fill_const(array, constant_value)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| ---------------- | ------- | --------------------------------------------------------------- |
| `array` | dynamic | A dynamic array of numeric values that may contain null values. |
| `constant_value` | numeric | The constant value to use for filling null values. |
### Returns [#returns]
A dynamic array where null values are replaced with the specified constant value.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_fill_const` to fill missing request duration data with a default value like 0, which might represent no activity or baseline performance.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend filled_durations = series_fill_const(durations, 0)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20filled_durations%20%3D%20series_fill_const\(durations%2C%200\)%22%7D)
**Output**
| id | durations | filled\_durations |
| ---- | ----------------------- | ----------------- |
| u123 | \[null, 150, null, 200] | \[0, 150, 0, 200] |
| u456 | \[100, null, null, 300] | \[100, 0, 0, 300] |
This query fills missing request durations with 0, useful for representing periods of no activity or baseline performance.
In OpenTelemetry traces, you can use `series_fill_const` to fill missing span duration data with a default value, such as 0 for spans that didn't execute or a baseline latency value.
**Query**
```kusto
['otel-demo-traces']
| summarize durations = make_list(duration) by ['service.name']
| extend filled_durations = series_fill_const(durations, 0)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(duration\)%20by%20%5B'service.name'%5D%20%7C%20extend%20filled_durations%20%3D%20series_fill_const\(durations%2C%200\)%22%7D)
**Output**
| service.name | durations | filled\_durations |
| --------------------- | --------------------------- | ------------------------- |
| frontend | \[null, 100ms, null, 200ms] | \[0ms, 100ms, 0ms, 200ms] |
| productcatalogservice | \[50ms, null, null, 150ms] | \[50ms, 0ms, 0ms, 150ms] |
This query fills missing span durations with 0ms, useful for representing spans that didn't execute or had no measurable duration.
In security logs, you can use `series_fill_const` to fill missing request duration data with a default value, such as 0 for blocked requests or a baseline value for analysis.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by status
| extend filled_durations = series_fill_const(durations, 0)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20filled_durations%20%3D%20series_fill_const\(durations%2C%200\)%22%7D)
**Output**
| status | durations | filled\_durations |
| ------ | ----------------------- | ----------------- |
| 200 | \[null, 150, null, 250] | \[0, 150, 0, 250] |
| 500 | \[100, null, null, 400] | \[100, 0, 0, 400] |
This query fills missing request durations with 0 grouped by status code, useful for representing blocked or failed requests with no measurable duration.
## List of related functions [#list-of-related-functions]
* [series\_fill\_forward](/apl/scalar-functions/time-series/series-fill-forward): Fills missing values by propagating the first known value forward. Use when you want to use the earliest available value to fill gaps.
* [series\_fill\_backward](/apl/scalar-functions/time-series/series-fill-backward): Fills missing values by propagating the last known value backward. Use when you want to use the most recent available value to fill gaps.
* [series\_fill\_linear](/apl/scalar-functions/time-series/series-fill-linear): Fills missing values using linear interpolation. Use when you want smooth transitions between known values.
* [series\_equals](/apl/scalar-functions/time-series/series-equals): Compares each element to a specified value. Use for identifying specific values after filling operations.
* [series\_greater](/apl/scalar-functions/time-series/series-greater): Returns elements greater than a specified value. Use for threshold analysis after filling missing data.
## Other query languages [#other-query-languages]
In Splunk SPL, filling missing values with constants is typically done with the `fillnull` command or `eval` expressions using conditional logic. To fill arrays with constants, you usually need to expand arrays and apply the transformation row by row. In APL, `series_fill_const` works directly on dynamic arrays, making it efficient for series-wide constant filling.
```sql Splunk example
... | fillnull value=0 field_name
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([null, 100, null, 200])
]
| extend filled_values = series_fill_const(values, 0)
```
In SQL, filling missing values with constants is typically done with `COALESCE()` or `ISNULL()` functions, but these only work on single values, not arrays. To fill array elements with constants, you usually need to unnest arrays and apply the function row by row. In APL, `series_fill_const` eliminates this complexity by directly replacing null values with a constant in arrays.
```sql SQL example
SELECT COALESCE(value, 0) AS filled_value
FROM measurements;
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([null, 100, null, 200])
]
| extend filled_values = series_fill_const(values, 0)
```
---
# series_fill_forward
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-fill-forward
The `series_fill_forward` function fills missing values (nulls) in a numeric dynamic array (series) by propagating the first known value forward through the array. This function is useful for handling gaps in time series data where you want to use the earliest available value to fill subsequent missing data points.
You can use `series_fill_forward` when you have time series data with missing values and want to fill gaps using the first observed value. This is particularly useful for backward-looking analysis, historical data reconstruction, or when the initial data point is the best estimate for missing later values. Typical applications include financial data analysis, sensor data processing, and performance monitoring where the baseline value is most relevant.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_fill_forward(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------------------------------- |
| `array` | dynamic | A dynamic array of numeric values that may contain null values. |
### Returns [#returns]
A dynamic array where null values are replaced by the first non-null value encountered when traversing the array forward.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_fill_forward` to fill missing request duration data using the first available value, which is useful for maintaining baseline performance metrics.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend filled_durations = series_fill_forward(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20filled_durations%20%3D%20series_fill_forward\(durations\)%22%7D)
**Output**
| id | durations | filled\_durations |
| ---- | ----------------------- | --------------------- |
| u123 | \[150, null, null, 200] | \[150, 150, 150, 200] |
| u456 | \[null, 100, null, 300] | \[100, 100, 100, 300] |
This query fills missing request durations with the first available value, useful for maintaining baseline performance metrics in analysis.
In OpenTelemetry traces, you can use `series_fill_forward` to fill missing span duration data using the first observed value for consistent trace analysis.
**Query**
```kusto
['otel-demo-traces']
| summarize durations = make_list(duration) by ['service.name']
| extend filled_durations = series_fill_forward(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(duration\)%20by%20%5B'service.name'%5D%20%7C%20extend%20filled_durations%20%3D%20series_fill_forward\(durations\)%22%7D)
**Output**
| service.name | durations | filled\_durations |
| --------------------- | --------------------------- | ----------------------------- |
| frontend | \[100ms, null, null, 200ms] | \[100ms, 100ms, 100ms, 200ms] |
| productcatalogservice | \[null, 50ms, null, 150ms] | \[50ms, 50ms, 50ms, 150ms] |
This query fills missing span durations with the first available value, useful for maintaining baseline latency metrics in service performance analysis.
In security logs, you can use `series_fill_forward` to fill missing request duration data using the first available value for consistent security analysis.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by status
| extend filled_durations = series_fill_forward(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20filled_durations%20%3D%20series_fill_forward\(durations\)%22%7D)
**Output**
| status | durations | filled\_durations |
| ------ | ----------------------- | --------------------- |
| 200 | \[150, null, null, 250] | \[150, 150, 150, 250] |
| 500 | \[null, 100, null, 400] | \[100, 100, 100, 400] |
This query fills missing request durations with the first available value grouped by status code, useful for maintaining baseline metrics in security analysis across different response types.
## List of related functions [#list-of-related-functions]
* [series\_fill\_backward](/apl/scalar-functions/time-series/series-fill-backward): Fills missing values by propagating the last known value backward. Use when you want to use the most recent available value to fill gaps.
* [series\_fill\_const](/apl/scalar-functions/time-series/series-fill-const): Fills missing values with a constant value. Use when you want to replace nulls with a specific default value.
* [series\_fill\_linear](/apl/scalar-functions/time-series/series-fill-linear): Fills missing values using linear interpolation. Use when you want smooth transitions between known values.
* [series\_equals](/apl/scalar-functions/time-series/series-equals): Compares each element to a specified value. Use for identifying specific values after filling operations.
* [series\_greater](/apl/scalar-functions/time-series/series-greater): Returns elements greater than a specified value. Use for threshold analysis after filling missing data.
## Other query languages [#other-query-languages]
In Splunk SPL, filling missing values forward is typically done with `fillnull` or `eval` expressions using `filldown` logic. To fill arrays forward, you usually need to expand arrays and apply the transformation row by row. In APL, `series_fill_forward` provides a direct, efficient way to perform forward filling on dynamic arrays.
```sql Splunk example
... | fillnull value=0 | streamstats window=5 current=f first(field) as filled_field
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([100, null, null, 200, null])
]
| extend filled_values = series_fill_forward(values)
```
In SQL, filling missing values forward requires window functions with `FIRST_VALUE()` or custom logic using `LAG()` with specific window specifications. Most SQL implementations focus on forward filling as the default behavior. In APL, `series_fill_forward` simplifies this operation by directly handling forward propagation of values in arrays.
```sql SQL example
SELECT FIRST_VALUE(value) OVER (ORDER BY timestamp ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS filled_value
FROM measurements;
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([100, null, null, 200, null])
]
| extend filled_values = series_fill_forward(values)
```
---
# series_fill_linear
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-fill-linear
The `series_fill_linear` function fills missing values (nulls) in a numeric dynamic array (series) using linear interpolation between known values. This function creates smooth transitions between existing data points by calculating intermediate values based on the linear relationship between adjacent non-null values.
You can use `series_fill_linear` when you have time series data with missing values and want to create smooth, realistic interpolated values between known data points. This is particularly useful for maintaining data continuity, creating smooth visualizations, or when missing values represent gradual changes rather than abrupt shifts. Typical applications include sensor data processing, financial time series analysis, and performance monitoring where smooth trends are expected.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_fill_linear(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------------------------------- |
| `array` | dynamic | A dynamic array of numeric values that may contain null values. |
### Returns [#returns]
A dynamic array where null values are replaced with linearly interpolated values based on adjacent non-null values.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_fill_linear` to create smooth interpolated values for missing request durations, which is useful for maintaining realistic performance trends.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend interpolated_durations = series_fill_linear(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20interpolated_durations%20%3D%20series_fill_linear\(durations\)%22%7D)
**Output**
| id | durations | interpolated\_durations |
| ---- | ----------------------- | ------------------------- |
| u123 | \[100, null, null, 200] | \[100, 133.3, 166.7, 200] |
| u456 | \[150, null, 300] | \[150, 225, 300] |
This query creates smooth interpolated values for missing request durations, useful for maintaining realistic performance trends in analysis.
In OpenTelemetry traces, you can use `series_fill_linear` to create smooth interpolated values for missing span durations, which is useful for maintaining realistic latency trends.
**Query**
```kusto
['otel-demo-traces']
| summarize durations = make_list(duration) by ['service.name']
| extend interpolated_durations = series_fill_linear(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(duration\)%20by%20%5B'service.name'%5D%20%7C%20extend%20interpolated_durations%20%3D%20series_fill_linear\(durations\)%22%7D)
**Output**
| service.name | durations | interpolated\_durations |
| --------------------- | --------------------------- | --------------------------------- |
| frontend | \[100ms, null, null, 200ms] | \[100ms, 133.3ms, 166.7ms, 200ms] |
| productcatalogservice | \[50ms, null, 150ms] | \[50ms, 100ms, 150ms] |
This query creates smooth interpolated values for missing span durations, useful for maintaining realistic latency trends in service performance analysis.
In security logs, you can use `series_fill_linear` to create smooth interpolated values for missing request durations, which is useful for maintaining realistic attack pattern analysis.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by status
| extend interpolated_durations = series_fill_linear(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20interpolated_durations%20%3D%20series_fill_linear\(durations\)%22%7D)
**Output**
| status | durations | interpolated\_durations |
| ------ | ----------------------- | ----------------------- |
| 200 | \[100, null, null, 250] | \[100, 150, 200, 250] |
| 500 | \[200, null, 400] | \[200, 300, 400] |
This query creates smooth interpolated values for missing request durations grouped by status code, useful for maintaining realistic patterns in security analysis across different response types.
## List of related functions [#list-of-related-functions]
* [series\_fill\_forward](/apl/scalar-functions/time-series/series-fill-forward): Fills missing values by propagating the first known value forward. Use when you want to use the earliest available value to fill gaps.
* [series\_fill\_backward](/apl/scalar-functions/time-series/series-fill-backward): Fills missing values by propagating the last known value backward. Use when you want to use the most recent available value to fill gaps.
* [series\_fill\_const](/apl/scalar-functions/time-series/series-fill-const): Fills missing values with a constant value. Use when you want to replace nulls with a specific default value.
* [series\_equals](/apl/scalar-functions/time-series/series-equals): Compares each element to a specified value. Use for identifying specific values after filling operations.
* [series\_greater](/apl/scalar-functions/time-series/series-greater): Returns elements greater than a specified value. Use for threshold analysis after filling missing data.
## Other query languages [#other-query-languages]
In Splunk SPL, linear interpolation isn’t natively available and typically requires complex `eval` expressions with custom logic or external tools. Most Splunk users rely on `fillnull` with constants or forward/backward filling. In APL, `series_fill_linear` provides direct access to sophisticated interpolation capabilities for smooth data reconstruction.
```sql Splunk example
... | fillnull value=0 | streamstats window=5 current=f avg(field) as interpolated_field
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([100, null, null, 200])
]
| extend interpolated_values = series_fill_linear(values)
```
ANSI SQL does not provide native linear interpolation functionality. Database systems typically require specialized extensions, custom functions, or complex window function combinations to achieve interpolation. Most SQL users rely on simple filling methods or external processing. In APL, `series_fill_linear` brings advanced interpolation capabilities directly into the query language.
```sql SQL example
SELECT AVG(value) OVER (ORDER BY timestamp ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS interpolated_value
FROM measurements;
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([100, null, null, 200])
]
| extend interpolated_values = series_fill_linear(values)
```
---
# series_fir
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-fir
The `series_fir` function applies a Finite Impulse Response (FIR) filter to a numeric dynamic array (series) using a specified filter kernel. This function performs digital signal processing operations such as smoothing, noise reduction, and frequency filtering on time series data.
You can use `series_fir` when you want to apply signal processing techniques to your time series data, such as smoothing noisy data, removing high-frequency noise, or implementing custom filtering operations. This is particularly useful for preprocessing data before analysis, removing artifacts, or extracting specific frequency components. Typical applications include sensor data processing, financial time series analysis, and performance monitoring where noise reduction is important.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_fir(array, kernel)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | --------------------------------------------------------------------------- |
| `array` | dynamic | A dynamic array of numeric values representing the input signal. |
| `kernel` | dynamic | A dynamic array of numeric values representing the FIR filter coefficients. |
### Returns [#returns]
A dynamic array representing the filtered signal after applying the FIR filter.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_fir` to smooth noisy request duration data using a moving average filter, which helps identify underlying performance trends.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend smoothed_durations = series_fir(durations, dynamic([0.2, 0.2, 0.2, 0.2, 0.2]))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20smoothed_durations%20%3D%20series_fir\(durations%2C%20dynamic\(%5B0.2%2C%200.2%2C%200.2%2C%200.2%2C%200.2%5D\)\)%22%7D)
**Output**
| id | durations | smoothed\_durations |
| ---- | -------------------------- | ---------------------------- |
| u123 | \[100, 120, 110, 130, 105] | \[100, 110, 110, 115, 115] |
| u456 | \[150, 140, 160, 135, 145] | \[150, 145, 150, 147.5, 144] |
This query applies a 5-point moving average filter to request durations, useful for smoothing out noise and identifying underlying performance trends.
In OpenTelemetry traces, you can use `series_fir` to smooth noisy span duration data using a low-pass filter, which helps identify consistent latency patterns.
**Query**
```kusto
['otel-demo-traces']
| summarize durations = make_list(duration) by ['service.name']
| extend smoothed_durations = series_fir(durations, dynamic([0.25, 0.25, 0.25, 0.25]))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(duration\)%20by%20%5B'service.name'%5D%20%7C%20extend%20smoothed_durations%20%3D%20series_fir\(durations%2C%20dynamic\(%5B0.25%2C%200.25%2C%200.25%2C%200.25%5D\)\)%22%7D)
**Output**
| service.name | durations | smoothed\_durations |
| --------------- | ----------------------------- | ----------------------------- |
| frontend | \[100ms, 120ms, 110ms, 130ms] | \[100ms, 110ms, 110ms, 115ms] |
| product-catalog | \[50ms, 60ms, 55ms, 65ms] | \[50ms, 55ms, 55ms, 60ms] |
This query applies a 4-point moving average filter to span durations, useful for smoothing out noise and identifying consistent latency patterns across services.
In security logs, you can use `series_fir` to smooth noisy request duration data using a high-pass filter to detect anomalies while removing baseline noise.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by status
| extend filtered_durations = series_fir(durations, dynamic([-0.1, -0.1, 0.4, -0.1, -0.1]))
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20filtered_durations%20%3D%20series_fir\(durations%2C%20dynamic\(%5B-0.1%2C%20-0.1%2C%200.4%2C%20-0.1%2C%20-0.1%5D\)\)%22%7D)
**Output**
| status | durations | filtered\_durations |
| ------ | -------------------------- | ------------------- |
| 200 | \[100, 120, 110, 130, 105] | \[-2, 2, 4, 2, -2] |
| 500 | \[200, 220, 210, 230, 205] | \[-2, 2, 4, 2, -2] |
This query applies a high-pass filter to request durations grouped by status code, useful for detecting anomalies while removing baseline noise in security analysis.
## List of related functions [#list-of-related-functions]
* [series\_fft](/apl/scalar-functions/time-series/series-fft): Performs Fast Fourier Transform on a series. Use for frequency domain analysis before applying filters.
* [series\_ifft](/apl/scalar-functions/time-series/series-ifft): Performs inverse FFT to convert frequency domain back to time domain. Use after frequency domain filtering.
* [series\_fill\_linear](/apl/scalar-functions/time-series/series-fill-linear): Fills missing values using linear interpolation. Use for data preprocessing before filtering.
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns the absolute value of each element in an array. Use for analyzing filter output magnitudes.
* [series\_cos](/apl/scalar-functions/time-series/series-cos): Returns the cosine of each element in an array. Use for generating filter kernels or analyzing periodic components.
## Other query languages [#other-query-languages]
In Splunk SPL, FIR filtering isn’t natively available and typically requires external tools or complex workarounds using statistical functions like `movingavg` or `streamstats`. Most Splunk users rely on simple moving averages for smoothing. In APL, `series_fir` provides direct access to sophisticated digital signal processing capabilities with custom filter kernels.
```sql Splunk example
... | streamstats window=5 current=f avg(field) as smoothed_field
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([100, 120, 110, 130, 105])
]
| extend filtered_values = series_fir(values, dynamic([0.2, 0.2, 0.2, 0.2, 0.2]))
```
ANSI SQL does not provide FIR filtering functionality. Database systems typically require specialized extensions or external libraries for digital signal processing. Most SQL users rely on window functions with simple averages for smoothing. In APL, `series_fir` brings advanced signal processing capabilities directly into the query language.
```sql SQL example
SELECT AVG(value) OVER (ORDER BY timestamp ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS smoothed_value
FROM measurements;
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([100, 120, 110, 130, 105])
]
| extend filtered_values = series_fir(values, dynamic([0.2, 0.2, 0.2, 0.2, 0.2]))
```
---
# series_floor
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-floor
The `series_floor` function rounds down each element in a numeric dynamic array (series) to the nearest integer that’s less than or equal to the original value. This function applies the mathematical floor operation element-wise across the entire array, which is useful for data discretization, quantization, and integer conversion in time series data.
You can use `series_floor` when you want to convert floating-point values to integers by rounding down, discretize continuous data into bins, or prepare data for categorical analysis. This is particularly useful for creating integer-based categories, implementing quantization schemes, or when you need to ensure values don’t exceed certain thresholds. Typical applications include data binning, performance categorization, and mathematical modeling where integer values are required.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_floor(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------- |
| `array` | dynamic | A dynamic array of numeric values. |
### Returns [#returns]
A dynamic array where each element is the floor (largest integer less than or equal to) the corresponding input element.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_floor` to discretize request durations into integer bins for categorical analysis or performance categorization.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend floor_durations = series_floor(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20floor_durations%20%3D%20series_floor\(durations\)%22%7D)
**Output**
| id | durations | floor\_durations |
| ---- | ---------------------- | ---------------- |
| u123 | \[150.7, 200.3, 250.9] | \[150, 200, 250] |
| u456 | \[100.2, 300.8, 400.1] | \[100, 300, 400] |
This query converts floating-point request durations to integers by rounding down, useful for creating discrete performance categories.
In OpenTelemetry traces, you can use `series_floor` to discretize span durations into integer milliseconds for consistent latency analysis.
**Query**
```kusto
['otel-demo-traces']
| summarize durations = make_list(duration) by ['service.name']
| extend floor_durations = series_floor(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(duration\)%20by%20%5B'service.name'%5D%20%7C%20extend%20floor_durations%20%3D%20series_floor\(durations\)%22%7D)
**Output**
| service.name | durations | floor\_durations |
| --------------- | ---------------------------- | ---------------------- |
| frontend | \[100.7ms, 200.3ms, 300.9ms] | \[100ms, 200ms, 300ms] |
| product-catalog | \[50.2ms, 150.8ms, 250.1ms] | \[50ms, 150ms, 250ms] |
This query converts floating-point span durations to integer milliseconds by rounding down, useful for consistent latency categorization across services.
In security logs, you can use `series_floor` to discretize request durations into integer bins for security analysis and attack pattern detection.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by status
| extend floor_durations = series_floor(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20floor_durations%20%3D%20series_floor\(durations\)%22%7D)
**Output**
| status | durations | floor\_durations |
| ------ | ---------------------- | ---------------- |
| 200 | \[150.7, 200.3, 250.9] | \[150, 200, 250] |
| 500 | \[100.2, 300.8, 400.1] | \[100, 300, 400] |
This query converts floating-point request durations to integers by rounding down grouped by status code, useful for creating discrete performance categories in security analysis.
## List of related functions [#list-of-related-functions]
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns the absolute value of each element in an array. Use when you need to normalize values before applying floor operations.
* [series\_exp](/apl/scalar-functions/time-series/series-exp): Calculates the exponential of each element in an array. Use for exponential transformations instead of floor operations.
* [series\_cos](/apl/scalar-functions/time-series/series-cos): Returns the cosine of each element in an array. Use for trigonometric transformations instead of floor operations.
* [series\_sin](/apl/scalar-functions/time-series/series-sin): Returns the sine of each element in an array. Use for periodic transformations instead of floor operations.
* [series\_tan](/apl/scalar-functions/time-series/series-tan): Returns the tangent of each element in an array. Use for trigonometric transformations with different periodicity.
## Other query languages [#other-query-languages]
In Splunk SPL, floor operations are typically done with the `eval` function and the `floor()` expression. To compute floor across multiple values, you usually need to expand arrays and apply the transformation row by row. In APL, `series_floor` works directly on dynamic arrays, making it efficient for series-wide floor operations.
```sql Splunk example
... | eval floor_val=floor(duration)
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([3.7, 4.2, 5.9, 2.1])
]
| extend floor_values = series_floor(values)
```
In SQL, floor operations use the `FLOOR()` function, but this only works on single values, not arrays. To compute floor for array elements, you typically need to unnest arrays and apply `FLOOR()` row by row. In APL, `series_floor` eliminates this complexity by directly applying floor transformation to each element in an array.
```sql SQL example
SELECT FLOOR(duration) AS floor_duration
FROM requests;
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([3.7, 4.2, 5.9, 2.1])
]
| extend floor_values = series_floor(values)
```
---
# series_greater_equals
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-greater-equals
The `series_greater_equals` function compares two numeric arrays element by element and returns a new array of Boolean values. Each element in the result is `true` if the corresponding element in the first array is greater than or equal to the corresponding element in the second array, and `false` otherwise.
You use this function when you want to perform threshold comparisons across two series of values, such as checking performance metrics against baselines, comparing observed values to expected ranges, or evaluating time-aligned logs and traces.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_greater_equals(array1, array2)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | --------------------------------- | ------------------------------------------------------------ |
| `array1` | dynamic (array of numeric values) | The first input array. |
| `array2` | dynamic (array of numeric values) | The second input array. Must be the same length as `array1`. |
### Returns [#returns]
A dynamic array of Boolean values where each element is `true` if `array1[i] >= array2[i]`, and `false` otherwise.
## Use case examples [#use-case-examples]
In log analysis, you can compare observed request durations against a threshold series to identify requests that are slower than expected.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend threshold = dynamic([100,100,100])
| extend exceeds = series_greater_equals(durations, threshold)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20threshold%20%3D%20dynamic\(%5B100%2C100%2C100%5D\)%20%7C%20extend%20exceeds%20%3D%20series_greater_equals\(durations%2C%20threshold\)%22%7D)
**Output**
| id | durations | threshold | exceeds |
| ---- | ------------- | -------------- | ------------------ |
| u123 | \[120,80,150] | \[100,100,100] | \[true,false,true] |
This query groups request durations by user ID, builds a list of durations, and checks each against the threshold series of 100 ms.
In OpenTelemetry traces, you can compare span durations from one service with expected baselines to detect performance regressions.
**Query**
```kusto
['otel-demo-traces']
| where ['service.name'] == 'checkout'
| summarize durations = make_list(duration) by trace_id
| extend baseline = dynamic([100ms,200ms,300ms])
| extend slower = series_greater_equals(durations, baseline)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20%5B'service.name'%5D%20%3D%3D%20'checkout'%20%7C%20summarize%20durations%20%3D%20make_list\(duration\)%20by%20trace_id%20%7C%20extend%20baseline%20%3D%20dynamic\(%5B100ms%2C200ms%2C300ms%5D\)%20%7C%20extend%20slower%20%3D%20series_greater_equals\(durations%2C%20baseline\)%22%7D)
**Output**
| trace\_id | durations | baseline | slower |
| --------- | -------------------- | -------------------- | ------------------ |
| t001 | \[120ms,180ms,400ms] | \[100ms,200ms,300ms] | \[true,false,true] |
This query checks if spans in the checkout service are slower than the defined baseline series.
In security logs, you can compare the frequency of failed status codes against a threshold to detect suspicious behavior.
**Query**
```kusto
['sample-http-logs']
| where status == '500'
| summarize fails = make_list(req_duration_ms) by ['geo.country']
| extend threshold = dynamic([200,200,200])
| extend suspicious = series_greater_equals(fails, threshold)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20%3D%3D%20'500'%20%7C%20summarize%20fails%20%3D%20make_list\(req_duration_ms\)%20by%20%5B'geo.country'%5D%20%7C%20extend%20threshold%20%3D%20dynamic\(%5B200%2C200%2C200%5D\)%20%7C%20extend%20suspicious%20%3D%20series_greater_equals\(fails%2C%20threshold\)%22%7D)
**Output**
| geo.country | fails | threshold | suspicious |
| ----------- | -------------- | -------------- | ------------------ |
| US | \[210,190,300] | \[200,200,200] | \[true,false,true] |
This query aggregates failed requests by country, builds a series of durations, and compares them against a 200 ms threshold to highlight suspiciously slow failures.
## List of related functions [#list-of-related-functions]
* [series\_greater](/apl/scalar-functions/time-series/series-greater): Compares two arrays and returns `true` where the first array element is greater than the second.
* [series\_less](/apl/scalar-functions/time-series/series-less): Compares two arrays and returns `true` where the first array element is less than the second.
* [series\_less\_equals](/apl/scalar-functions/time-series/series-less-equals): Compares two arrays and returns `true` where the first array element is less than or equal to the second.
* [series\_not\_equals](/apl/scalar-functions/time-series/series-not-equals): Compares two arrays and returns `true` where elements aren’t equal.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically perform comparisons on fields or with `eval` expressions rather than array-based functions. If you want to compare series of values, you usually use `eval` with conditional expressions, but SPL doesn’t provide direct array-to-array comparison. In APL, `series_greater_equals` lets you apply the comparison element by element on arrays.
```sql Splunk example
... | eval greater_equals = if(field1 >= field2, true(), false())
```
```kusto APL equivalent
print result = series_greater_equals(dynamic([2,4,6]), dynamic([1,4,10]))
```
ANSI SQL does not natively support array-to-array operations in the same way. You often need to `UNNEST` arrays or join on row numbers to compare values across two arrays. APL provides a direct function, `series_greater_equals`, that simplifies these operations by applying the comparison across the entire array at once.
```sql SQL example
-- SQL-style comparison would require unnesting
SELECT a.value >= b.value AS greater_equals
FROM UNNEST(ARRAY[2,4,6]) WITH ORDINALITY a(value, i)
JOIN UNNEST(ARRAY[1,4,10]) WITH ORDINALITY b(value, j)
ON a.i = b.j
```
```kusto APL equivalent
print result = series_greater_equals(dynamic([2,4,6]), dynamic([1,4,10]))
```
---
# series_greater
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-greater
The `series_greater` function compares two numeric arrays (series) element by element and returns a new array of Boolean values. Each element in the result is `true` if the corresponding element in the first array is greater than the corresponding element in the second array, and `false` otherwise.
You use this function when you want to evaluate pairwise comparisons across time series or numeric arrays. It’s especially useful in scenarios such as anomaly detection, trend analysis, or validating thresholds against observed metrics.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_greater(array1, array2)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | --------------- | ----------------------------------------------------------------- |
| `array1` | dynamic (array) | The first array to compare. |
| `array2` | dynamic (array) | The second array to compare. Must be the same length as `array1`. |
### Returns [#returns]
A dynamic array of Boolean values, where each element is `true` if the corresponding element in `array1` is greater than the corresponding element in `array2`, and `false` otherwise.
## Use case examples [#use-case-examples]
When analyzing HTTP request durations, you can compare them against a fixed threshold to identify requests that exceed performance expectations.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend threshold = dynamic([200,200,200,200])
| extend above_threshold = series_greater(durations, threshold)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20threshold%20%3D%20dynamic\(%5B200%2C200%2C200%2C200%5D\)%20%7C%20extend%20above_threshold%20%3D%20series_greater\(durations%2C%20threshold\)%22%7D)
**Output**
| id | durations | threshold | above\_threshold |
| ---- | ------------------ | ------------------ | ------------------------ |
| u123 | \[180,220,150,300] | \[200,200,200,200] | \[false,true,false,true] |
This query shows which requests for a given user exceed a threshold of 200 ms.
You can compare span durations across services to see where certain spans take longer than others.
**Query**
```kusto
['otel-demo-traces']
| where ['service.name'] == 'frontend'
| summarize frontend_spans = make_list(duration) by trace_id
| join kind=inner (
['otel-demo-traces']
| where ['service.name'] == 'checkout'
| summarize checkout_spans = make_list(duration) by trace_id
) on trace_id
| extend longer_in_frontend = series_greater(frontend_spans, checkout_spans)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20%5B'service.name'%5D%20%3D%3D%20'frontend'%20%7C%20summarize%20frontend_spans%20%3D%20make_list\(duration\)%20by%20trace_id%20%7C%20join%20kind%3Dinner%20\(%20%5B'otel-demo-traces'%5D%20%7C%20where%20%5B'service.name'%5D%20%3D%3D%20'checkout'%20%7C%20summarize%20checkout_spans%20%3D%20make_list\(duration\)%20by%20trace_id%20\)%20on%20trace_id%20%7C%20extend%20longer_in_frontend%20%3D%20series_greater\(frontend_spans%2C%20checkout_spans\)%22%7D)
**Output**
| trace\_id | frontend\_spans | checkout\_spans | longer\_in\_frontend |
| --------- | ----------------- | ----------------- | -------------------- |
| t1 | \[30ms,50ms,10ms] | \[20ms,40ms,15ms] | \[true,true,false] |
This query compares span durations between `frontend` and `checkoutservice` services.
## List of related functions [#list-of-related-functions]
* [series\_greater\_equals](/apl/scalar-functions/time-series/series-greater-equals): Compares two arrays and returns `true` when elements in the first array are greater than or equal to the second array.
* [series\_less](/apl/scalar-functions/time-series/series-less): Compares two arrays and returns `true` where the first array element is less than the second.
* [series\_less\_equals](/apl/scalar-functions/time-series/series-less-equals): Compares two arrays and returns `true` where the first array element is less than or equal to the second.
* [series\_not\_equals](/apl/scalar-functions/time-series/series-not-equals): Compares two arrays and returns `true` where elements aren’t equal.
## Other query languages [#other-query-languages]
In Splunk SPL, comparisons are usually done across fields or using the `eval` command with conditional expressions. There is no direct equivalent to element-by-element array comparisons. In APL, `series_greater` performs this comparison across arrays in a single function call.
```sql Splunk example
... | eval comparison = if(fieldA > fieldB, true(), false())
```
```kusto APL equivalent
print result = series_greater(dynamic([1,2,3]), dynamic([2,2,2]))
```
In ANSI SQL, comparisons are scalar and operate on single values at a time. You usually need to use `CASE` statements for conditionals. SQL lacks a built-in function for element-wise array comparison. In APL, `series_greater` directly compares two arrays and returns an array of Boolean values.
```sql SQL example
SELECT CASE WHEN a > b THEN TRUE ELSE FALSE END as comparison
FROM numbers
```
```kusto APL equivalent
print result = series_greater(dynamic([10,20,30]), dynamic([15,10,30]))
```
---
# series_ifft
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-ifft
The `series_ifft` function applies the Inverse Fast Fourier Transform (IFFT) on a series, taking a series of complex numbers in the frequency domain and transforming it back to the time/spatial domain using the Fast Fourier Transform. This function is the complementary function of `series_fft`. Commonly the original series is transformed to the frequency domain for spectral processing and then back to the time/spatial domain.
You can use `series_ifft` when you want to reconstruct time-domain signals from frequency-domain data, implement frequency-domain filtering, or perform signal synthesis. This is particularly useful after applying frequency-domain operations like filtering or when you need to convert processed frequency data back to the original time domain. Typical applications include signal reconstruction, frequency-domain filtering, noise reduction, and advanced signal processing workflows.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_ifft(fft_real [, fft_imaginary])
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fft_real` | dynamic | A dynamic array of real numeric values representing the real component of the series to transform. |
| `fft_imaginary` | dynamic | Optional: A dynamic array of real numeric values representing the imaginary component of the series. Only specify this if the input series contains complex numbers. |
### Returns [#returns]
The function returns the complex inverse FFT in two series. The first series for the real component and the second one for the imaginary component.
## Example [#example]
The example below shows how `series_ifft` reverses the effect of `series_fft` by reconstructing the original time-domain signal from frequency-domain data.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by bin(_time, 1h)
| extend reconstructed_durations = series_ifft(series_fft(durations)[0])
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20bin\(_time%2C%201h\)%20%7C%20extend%20reconstructed_durations%20%3D%20series_ifft\(series_fft\(durations\)%5B0%5D\)%22%7D)
**Output**
| durations | reconstructed\_durations |
| ------------------ | ------------------------ |
| 0.5848084109504474 | 0.5848084110671455 |
| 0.5506109370041884 | 0.5506109371007348 |
## List of related functions [#list-of-related-functions]
* [series\_fft](/apl/scalar-functions/time-series/series-fft): Performs Fast Fourier Transform to convert time domain to frequency domain. Use before applying frequency-domain operations.
* [series\_fir](/apl/scalar-functions/time-series/series-fir): Applies a finite impulse response filter to a series. Use for time-domain filtering instead of frequency-domain processing.
* [series\_cos](/apl/scalar-functions/time-series/series-cos): Returns the cosine of each element in an array. Use for generating periodic components in signal synthesis.
* [series\_sin](/apl/scalar-functions/time-series/series-sin): Returns the sine of each element in an array. Use for generating periodic components with phase shifts.
* [series\_exp](/apl/scalar-functions/time-series/series-exp): Calculates the exponential of each element in an array. Use for exponential signal components instead of frequency reconstruction.
## Other query languages [#other-query-languages]
In Splunk SPL, IFFT operations aren’t natively available and typically require external tools or complex workarounds. Most Splunk users rely on statistical functions and time-based aggregations for signal reconstruction. In APL, `series_ifft` provides direct access to inverse frequency domain analysis, enabling sophisticated signal reconstruction capabilities.
ANSI SQL doesn’t provide IFFT functionality. Database systems typically require specialized extensions or external libraries for inverse frequency domain analysis. Most SQL users rely on window functions and statistical aggregations for signal reconstruction. In APL, `series_ifft` brings advanced inverse signal processing capabilities directly into the query language.
---
# series_iir
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-iir
The `series_iir` function applies an Infinite Impulse Response (IIR) filter to a numeric dynamic array (series). This filter processes the input series using coefficients for both the numerator (feedforward) and denominator (feedback) components, creating a filtered output series that incorporates both current and past values.
You can use `series_iir` when you need to apply digital signal processing techniques to time-series data. This is particularly useful for smoothing noisy data, removing high-frequency components, implementing custom filters, or applying frequency-selective transformations to time-series measurements.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_iir(array, numerator, denominator)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| ------------- | ------- | -------------------------------------------------------- |
| `array` | dynamic | A dynamic array of numeric values (input series). |
| `numerator` | dynamic | A dynamic array of numerator (feedforward) coefficients. |
| `denominator` | dynamic | A dynamic array of denominator (feedback) coefficients. |
### Returns [#returns]
A dynamic array containing the filtered output series after applying the IIR filter defined by the numerator and denominator coefficients.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_iir` to smooth noisy request duration measurements, making trends and patterns more visible.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend smoothed = series_iir(durations, dynamic([0.2, 0.6, 0.2]), dynamic([1.0]))
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20smoothed%20%3D%20series_iir\(durations%2C%20dynamic\(%5B0.2%2C%200.6%2C%200.2%5D\)%2C%20dynamic\(%5B1.0%5D\)\)%20%7C%20take%205%22%7D)
**Output**
| id | durations | smoothed |
| ---- | ----------------------- | ---------------------- |
| u123 | \[50, 120, 45, 200, 60] | \[50, 91, 62, 128, 88] |
| u456 | \[30, 35, 80, 40, 45] | \[30, 33, 54, 46, 45] |
This query applies an IIR filter to smooth request duration measurements, reducing noise while preserving the underlying trend.
In OpenTelemetry traces, you can use `series_iir` to filter span duration data, removing high-frequency noise to better identify sustained performance trends.
**Query**
```kusto
['otel-demo-traces']
| extend duration_ms = duration / 1ms
| summarize durations = make_list(duration_ms) by ['service.name']
| extend filtered = series_iir(durations, dynamic([0.1, 0.8, 0.1]), dynamic([1.0, -0.3]))
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20duration_ms%20%3D%20duration%20%2F%201ms%20%7C%20summarize%20durations%20%3D%20make_list\(duration_ms\)%20by%20%5B'service.name'%5D%20%7C%20extend%20filtered%20%3D%20series_iir\(durations%2C%20dynamic\(%5B0.1%2C%200.8%2C%200.1%5D\)%2C%20dynamic\(%5B1.0%2C%20-0.3%5D\)\)%20%7C%20take%205%22%7D)
**Output**
| service.name | durations | filtered |
| ------------ | -------------------------- | -------------------------- |
| frontend | \[100, 150, 95, 200, 120] | \[100, 130, 108, 152, 133] |
| checkout | \[200, 250, 180, 300, 220] | \[200, 230, 202, 248, 232] |
This query applies an IIR filter with feedback to span durations, smoothing out transient spikes while maintaining sensitivity to sustained changes.
In security logs, you can use `series_iir` to filter request rate data, separating sustained traffic changes from brief anomalies.
**Query**
```kusto
['sample-http-logs']
| summarize request_counts = make_list(req_duration_ms) by status
| extend filtered = series_iir(request_counts, dynamic([0.15, 0.7, 0.15]), dynamic([1.0, -0.4]))
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20request_counts%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20filtered%20%3D%20series_iir\(request_counts%2C%20dynamic\(%5B0.15%2C%200.7%2C%200.15%5D\)%2C%20dynamic\(%5B1.0%2C%20-0.4%5D\)\)%20%7C%20take%205%22%7D)
**Output**
| status | request\_counts | filtered |
| ------ | ------------------------- | -------------------------- |
| 200 | \[100, 105, 300, 110, 95] | \[100, 103, 180, 142, 120] |
| 401 | \[10, 12, 50, 15, 11] | \[10, 11, 27, 20, 16] |
This query uses IIR filtering to smooth security event patterns, helping distinguish between brief anomalies and sustained attack patterns.
## List of related functions [#list-of-related-functions]
* [series\_sum](/apl/scalar-functions/time-series/series-sum): Returns the sum of series elements. Use for simple aggregation instead of filtering.
* [series\_stats](/apl/scalar-functions/time-series/series-stats): Returns statistical measures. Use for statistical analysis instead of signal processing.
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns absolute values. Often used after IIR filtering to analyze magnitude.
* [make\_series](/apl/tabular-operators/make-series): Creates time-series from tabular data. Often used before applying `series_iir` for signal processing.
## Other query languages [#other-query-languages]
In Splunk SPL, signal processing typically requires external tools or complex manual calculations with `streamstats`. In APL, `series_iir` provides built-in digital filtering capabilities for array data.
```sql Splunk example
... | streamstats window=5 avg(value) as smoothed_value
... (limited to basic moving averages)
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
]
| extend filtered = series_iir(values, dynamic([0.25, 0.5, 0.25]), dynamic([1.0, -0.5]))
```
In SQL, implementing IIR filters requires complex recursive queries or user-defined functions. In APL, `series_iir` provides this functionality as a built-in operation on array data.
```sql SQL example
-- Complex recursive CTE required for IIR filtering
WITH RECURSIVE filtered AS (...)
SELECT * FROM filtered;
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
]
| extend filtered = series_iir(values, dynamic([0.25, 0.5, 0.25]), dynamic([1.0, -0.5]))
```
---
# series_less_equals
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-less-equals
The `series_less_equals` function compares two numeric arrays element by element and returns a new array of Boolean values. Each element in the result is `true` if the corresponding element in the first array is less than or equal to the corresponding element in the second array, and `false` otherwise.
You can use this function to analyze numeric sequences over time, such as detecting when one series of measurements stays below or matches another. This is useful in monitoring scenarios, anomaly detection, and when working with time-series data in logs, traces, or security events.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_less_equals(arr1, arr2)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | --------------- | -------------------------------------------------------------- |
| `arr1` | dynamic (array) | The first numeric array. |
| `arr2` | dynamic (array) | The second numeric array. Must have the same length as `arr1`. |
### Returns [#returns]
A dynamic array of Boolean values. Each element is `true` if the element of `arr1` is less than or equal to the corresponding element of `arr2`, otherwise `false`.
## Use case examples [#use-case-examples]
You want to check whether request durations for a user stay within an acceptable threshold over time.
**Query**
```kusto
['sample-http-logs']
| summarize durations=make_list(req_duration_ms), times=make_list(_time) by id
| extend threshold=dynamic([200, 200, 200])
| extend below_or_equal=series_less_equals(durations, threshold)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%3Dmake_list\(req_duration_ms\)%2C%20times%3Dmake_list\(_time\)%20by%20id%20%7C%20extend%20threshold%3Ddynamic\(%5B200%2C%20200%2C%20200%5D\)%20%7C%20extend%20below_or_equal%3Dseries_less_equals\(durations%2C%20threshold\)%22%7D)
**Output**
| id | durations | threshold | below\_or\_equal |
| -- | ---------------- | ---------------- | -------------------- |
| u1 | \[120, 180, 250] | \[200, 200, 200] | \[true, true, false] |
This query checks for each user whether the request duration at each point is less than or equal to the threshold of 200 ms.
You want to validate whether service durations stay within a performance baseline.
**Query**
```kusto
['otel-demo-traces']
| where ['service.name'] == 'frontend'
| summarize durations=make_list(duration), times=make_list(_time) by trace_id
| extend baseline=dynamic([1000000000, 1000000000, 1000000000])
| extend below_or_equal=series_less_equals(durations, baseline)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20%5B'service.name'%5D%20%3D%3D%20'frontend'%20%7C%20summarize%20durations%3Dmake_list\(duration\)%2C%20times%3Dmake_list\(_time\)%20by%20trace_id%20%7C%20extend%20baseline%3Ddynamic\(%5B1000000000%2C%201000000000%2C%201000000000%5D\)%20%7C%20extend%20below_or_equal%3Dseries_less_equals\(durations%2C%20baseline\)%22%7D)
**Output**
| trace\_id | durations | baseline | below\_or\_equal |
| --------- | ------------------------- | --------------------- | ---------------- |
| t1 | \[00:00:00.5, 00:00:01.2] | \[00:00:01, 00:00:01] | \[true, false] |
This query shows whether spans in the frontend service meet a performance baseline of 1 second.
You want to check whether requests from a given country stay within acceptable request duration limits.
**Query**
```kusto
['sample-http-logs']
| where ['geo.country'] == 'United States'
| summarize durations=make_list(req_duration_ms), times=make_list(_time) by id
| extend limits=dynamic([300, 300, 300])
| extend below_or_equal=series_less_equals(durations, limits)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20%5B'geo.country'%5D%20%3D%3D%20'United%20States'%20%7C%20summarize%20durations%3Dmake_list\(req_duration_ms\)%2C%20times%3Dmake_list\(_time\)%20by%20id%20%7C%20extend%20limits%3Ddynamic\(%5B300%2C%20300%2C%20300%5D\)%20%7C%20extend%20below_or_equal%3Dseries_less_equals\(durations%2C%20limits\)%22%7D)
**Output**
| id | durations | limit | below\_or\_equal |
| -- | ---------------- | ---------------- | -------------------- |
| u2 | \[220, 280, 350] | \[300, 300, 300] | \[true, true, false] |
This query checks whether requests originating in the United States remain within a 300 ms duration limit.
## List of related functions [#list-of-related-functions]
* [series\_greater\_equals](/apl/scalar-functions/time-series/series-greater-equals): Compares two arrays and returns `true` when elements in the first array are greater than or equal to the second array.
* [series\_greater](/apl/scalar-functions/time-series/series-greater): Compares two arrays and returns `true` where the first array element is greater than the second.
* [series\_less](/apl/scalar-functions/time-series/series-less): Compares two arrays and returns `true` where the first array element is less than the second.
* [series\_not\_equals](/apl/scalar-functions/time-series/series-not-equals): Compares two arrays and returns `true` where elements aren’t equal.
## Other query languages [#other-query-languages]
In Splunk SPL, comparisons across arrays aren’t directly supported in the same way. SPL typically works with single values or requires custom evaluation functions to iterate over arrays. In APL, `series_less_equals` provides a built-in way to compare arrays element by element.
```sql Splunk example
| eval result=if(value1 <= value2, true(), false())
```
```kusto APL equivalent
print arr1=dynamic([1,2,3]), arr2=dynamic([2,2,2])
| extend result=series_less_equals(arr1, arr2)
```
In ANSI SQL, comparisons are scalar by default. You cannot compare arrays directly without unnesting or joining them. In APL, `series_less_equals` lets you perform an element-wise comparison of two arrays with a single function call.
```sql SQL example
SELECT CASE WHEN a.value <= b.value THEN true ELSE false END
FROM array_table_a a
JOIN array_table_b b ON a.idx = b.idx;
```
```kusto APL equivalent
print arr1=dynamic([1,2,3]), arr2=dynamic([2,2,2])
| extend result=series_less_equals(arr1, arr2)
```
---
# series_less
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-less
The `series_less` function compares two numeric arrays element by element and returns a Boolean array. Each position in the result contains `true` if the element in the first array is less than the corresponding element in the second array, and `false` otherwise.
You use `series_less` when you want to evaluate trends across sequences of numeric values. It’s especially useful in time series analysis, anomaly detection, or comparing metrics side by side. For example, you can check if response times are decreasing compared to a baseline or if one service consistently performs faster than another.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_less(array1, array2)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ----- | -------------------------------------------------------------------------- |
| `array1` | array | The first array of numeric values. |
| `array2` | array | The second array of numeric values. Must have the same length as `array1`. |
### Returns [#returns]
An array of Boolean values. Each element is `true` if the corresponding element in `array1` is less than the element in `array2`, otherwise `false`.
## Use case examples [#use-case-examples]
You want to check whether the average request duration in each city is less than a fixed threshold of 150 milliseconds.
**Query**
```kusto
['sample-http-logs']
| take 50
| make-series city_avg = avg(req_duration_ms) on _time step 1h by ['geo.city']
| extend threshold = dynamic([150, 150, 150])
| extend is_less = series_less(city_avg, threshold)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20take%2050%20%7C%20make-series%20city_avg%20%3D%20avg\(req_duration_ms\)%20on%20_time%20step%201h%20by%20%5B'geo.city'%5D%20%7C%20extend%20threshold%20%3D%20dynamic\(%5B150%2C%20150%2C%20150%5D\)%20%7C%20extend%20is_less%20%3D%20series_less\(city_avg%2C%20threshold\)%22%7D)
**Output**
| geo.city | city\_avg | threshold | is\_less |
| -------- | ---------------- | ---------------- | ---------------------- |
| London | \[120, 90, 100] | \[150, 150, 150] | \[true, true, true] |
| Paris | \[180, 200, 190] | \[150, 150, 150] | \[false, false, false] |
This query shows whether each city’s request duration stays below a 150 ms threshold at each time step.
You want to detect if failed requests in each country are consistently less than successful requests.
**Query**
```kusto
['sample-http-logs']
| take 50
| summarize success = countif(status == '200'), failure = countif(status != '200') by ['geo.country'], bin(_time, 1h)
| make-series success_series = avg(success), failure_series = avg(failure) on _time step 1h by ['geo.country']
| extend failures_less = series_less(failure_series, success_series)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20take%2050%20%7C%20summarize%20success%20%3D%20countif\(status%20%3D%3D%20'200'\)%2C%20failure%20%3D%20countif\(status%20!%3D%20'200'\)%20by%20%5B'geo.country'%5D%2C%20bin\(_time%2C%201h\)%20%7C%20make-series%20success_series%20%3D%20avg\(success\)%2C%20failure_series%20%3D%20avg\(failure\)%20on%20_time%20step%201h%20by%20%5B'geo.country'%5D%20%7C%20extend%20failures_less%20%3D%20series_less\(failure_series%2C%20success_series\)%22%7D)
**Output**
| geo.country | success\_series | failure\_series | failures\_less |
| ----------- | ---------------- | --------------- | ------------------- |
| US | \[300, 280, 310] | \[10, 20, 15] | \[true, true, true] |
| UK | \[150, 140, 160] | \[20, 25, 30] | \[true, true, true] |
This query checks whether failures stay consistently lower than successful requests across time intervals.
## List of related functions [#list-of-related-functions]
* [series\_greater\_equals](/apl/scalar-functions/time-series/series-greater-equals): Compares two arrays and returns `true` when elements in the first array are greater than or equal to the second array.
* [series\_greater](/apl/scalar-functions/time-series/series-greater): Compares two arrays and returns `true` where the first array element is greater than the second.
* [series\_less\_equals](/apl/scalar-functions/time-series/series-less-equals): Compares two arrays and returns `true` where the first array element is less than or equal to the second.
* [series\_not\_equals](/apl/scalar-functions/time-series/series-not-equals): Compares two arrays and returns `true` where elements aren’t equal.
## Other query languages [#other-query-languages]
In Splunk SPL, comparisons across series typically rely on `eval` with conditional expressions or custom logic in combination with `timechart`. In contrast, APL provides specialized `series_*` functions like `series_less` to directly compare arrays element by element.
```sql Splunk example
... | timechart avg(req_duration_ms) as avg_dur
| eval faster = if(avg_dur < 200, true, false)
```
```kusto APL equivalent
['sample-http-logs']
| make-series avg(req_duration_ms) on _time step 1m
| extend is_less = series_less(avg_req_duration_ms, array_concat(dynamic([200])))
```
In ANSI SQL, you normally compare scalar values rather than arrays. To compare sequences, you need to join tables with offsets or use window functions. In APL, `series_less` simplifies this by applying the comparison across arrays in a single step.
```sql SQL example
SELECT t1._time,
CASE WHEN t1.req_duration_ms < t2.req_duration_ms THEN true ELSE false END AS is_less
FROM logs t1
JOIN logs t2
ON t1._time = t2._time
```
```kusto APL equivalent
['sample-http-logs']
| make-series avg(req_duration_ms) on _time step 1m
| extend compare = series_less(avg_req_duration_ms, avg_req_duration_ms[1:])
```
---
# series_log
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-log
The `series_log` function computes the natural logarithm (base e) of each element in a numeric dynamic array (series). This performs element-wise logarithmic transformation across the entire series.
You can use `series_log` when you need to apply logarithmic transformations to time-series data. This is particularly useful for normalizing exponentially distributed data, linearizing exponential growth patterns, compressing wide value ranges, or preparing data for analysis that assumes log-normal distributions.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_log(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ----------------------------------------------------------- |
| `array` | dynamic | A dynamic array of numeric values. Values must be positive. |
### Returns [#returns]
A dynamic array where each element is the natural logarithm of the corresponding input element. Returns `null` for non-positive values.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_log` to normalize request durations that follow an exponential distribution, making patterns easier to visualize and analyze.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend log_durations = series_log(durations)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20log_durations%20%3D%20series_log\(durations\)%20%7C%20take%205%22%7D)
**Output**
| id | durations | log\_durations |
| ---- | --------------------- | ------------------------- |
| u123 | \[50, 100, 500, 1000] | \[3.91, 4.61, 6.21, 6.91] |
| u456 | \[25, 75, 200, 800] | \[3.22, 4.32, 5.30, 6.68] |
This query applies logarithmic transformation to request durations, compressing the range and making it easier to compare values across different scales.
In OpenTelemetry traces, you can use `series_log` to linearize exponentially growing span durations, making trends more apparent in visualization.
**Query**
```kusto
['otel-demo-traces']
| extend duration_ms = duration / 1ms
| summarize durations = make_list(duration_ms) by ['service.name']
| extend log_durations = series_log(durations)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20duration_ms%20%3D%20duration%20%2F%201ms%20%7C%20summarize%20durations%20%3D%20make_list\(duration_ms\)%20by%20%5B'service.name'%5D%20%7C%20extend%20log_durations%20%3D%20series_log\(durations\)%20%7C%20take%205%22%7D)
**Output**
| service.name | durations | log\_durations |
| ------------ | --------------------- | ------------------------- |
| frontend | \[10, 50, 250, 1000] | \[2.30, 3.91, 5.52, 6.91] |
| checkout | \[20, 100, 500, 2000] | \[3.00, 4.61, 6.21, 7.60] |
This query applies logarithmic transformation to span durations, making exponential growth patterns appear linear for easier analysis.
In security logs, you can use `series_log` to normalize request volumes that follow exponential patterns, making anomaly detection more effective.
**Query**
```kusto
['sample-http-logs']
| summarize request_counts = make_list(req_duration_ms) by status
| extend log_counts = series_log(request_counts)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20request_counts%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20log_counts%20%3D%20series_log\(request_counts\)%20%7C%20take%205%22%7D)
**Output**
| status | request\_counts | log\_counts |
| ------ | ----------------------- | ------------------------- |
| 200 | \[100, 500, 1000, 5000] | \[4.61, 6.21, 6.91, 8.52] |
| 401 | \[10, 50, 100, 500] | \[2.30, 3.91, 4.61, 6.21] |
This query applies logarithmic transformation to request counts, making it easier to detect unusual patterns in security events across different scales.
## List of related functions [#list-of-related-functions]
* [series\_pow](/apl/scalar-functions/time-series/series-pow): Raises series elements to a power. Use as the inverse operation to logarithms when working with exponentials.
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns the absolute value of each element. Use before `series_log` to ensure positive values.
* [series\_magnitude](/apl/scalar-functions/time-series/series-magnitude): Computes the magnitude of a series. Use when you need Euclidean norm instead of logarithmic transformation.
* [log](/apl/scalar-functions/mathematical-functions#log): Scalar function for single values. Use for individual calculations instead of array operations.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use the `log()` function within an `eval` command to calculate logarithms. In APL, `series_log` applies the logarithm operation to every element in an array simultaneously.
```sql Splunk example
... | eval log_value=log(value)
```
```kusto APL equivalent
datatable(x: dynamic)
[
dynamic([1, 10, 100, 1000])
]
| extend log_values = series_log(x)
```
In SQL, you use the `LOG()` or `LN()` function to calculate natural logarithms on individual rows. In APL, `series_log` operates on entire arrays, applying the logarithm operation element-wise.
```sql SQL example
SELECT LN(value) AS log_value
FROM measurements;
```
```kusto APL equivalent
datatable(x: dynamic)
[
dynamic([1, 10, 100, 1000])
]
| extend log_values = series_log(x)
```
---
# series_magnitude
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-magnitude
The `series_magnitude` function calculates the Euclidean norm (magnitude) of a numeric dynamic array (series). This computes the square root of the sum of squared elements, representing the length or magnitude of the vector.
You can use `series_magnitude` when you need to measure the overall magnitude of a series, compare vector lengths, normalize data, or calculate distances in multi-dimensional space. This is particularly useful in signal processing, similarity analysis, and feature scaling for machine learning applications.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_magnitude(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------- |
| `array` | dynamic | A dynamic array of numeric values. |
### Returns [#returns]
A numeric scalar representing the Euclidean norm (magnitude) of the series, calculated as the square root of the sum of squared elements.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_magnitude` to calculate the overall load magnitude from multiple request duration measurements, creating a single metric representing total system stress.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by ['geo.city']
| extend load_magnitude = series_magnitude(durations)
| project ['geo.city'], load_magnitude
| order by load_magnitude desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20%5B'geo.city'%5D%20%7C%20extend%20load_magnitude%20%3D%20series_magnitude\(durations\)%20%7C%20project%20%5B'geo.city'%5D%2C%20load_magnitude%20%7C%20order%20by%20load_magnitude%20desc%22%7D)
**Output**
| geo.city | load\_magnitude |
| -------- | --------------- |
| Seattle | 325.5 ms |
| Portland | 285.2 ms |
| Denver | 245.8 ms |
This query calculates the magnitude of request duration vectors for each city, providing a single metric that represents the overall load intensity.
In OpenTelemetry traces, you can use `series_magnitude` to compute a composite performance metric that captures the overall latency footprint of each service.
**Query**
```kusto
['otel-demo-traces']
| extend duration_ms = duration / 1ms
| summarize durations = make_list(duration_ms) by ['service.name']
| extend performance_magnitude = series_magnitude(durations)
| project ['service.name'], performance_magnitude
| order by performance_magnitude desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20duration_ms%20%3D%20duration%20%2F%201ms%20%7C%20summarize%20durations%20%3D%20make_list\(duration_ms\)%20by%20%5B'service.name'%5D%20%7C%20extend%20performance_magnitude%20%3D%20series_magnitude\(durations\)%20%7C%20project%20%5B'service.name'%5D%2C%20performance_magnitude%20%7C%20order%20by%20performance_magnitude%20desc%22%7D)
**Output**
| service.name | performance\_magnitude |
| ------------ | ---------------------- |
| checkout | 1250.5 |
| frontend | 895.3 |
| cart | 650.2 |
This query computes a magnitude metric for each service's latency profile, helping prioritize optimization efforts for services with the highest overall latency impact.
In security logs, you can use `series_magnitude` to calculate an overall threat intensity score based on multiple security metrics, creating a composite risk indicator.
**Query**
```kusto
['sample-http-logs']
| summarize request_metrics = make_list(req_duration_ms) by status
| extend threat_magnitude = series_magnitude(request_metrics)
| project status, threat_magnitude
| order by threat_magnitude desc
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20request_metrics%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20threat_magnitude%20%3D%20series_magnitude\(request_metrics\)%20%7C%20project%20status%2C%20threat_magnitude%20%7C%20order%20by%20threat_magnitude%20desc%22%7D)
**Output**
| status | threat\_magnitude |
| ------ | ----------------- |
| 401 | 2850.5 ms |
| 500 | 1250.3 ms |
| 200 | 425.8 ms |
This query calculates the magnitude of request patterns for each HTTP status code, providing a single metric that represents the overall intensity of potentially concerning traffic.
## List of related functions [#list-of-related-functions]
* [series\_sum](/apl/scalar-functions/time-series/series-sum): Returns the sum of all values. Use when you need simple addition instead of Euclidean norm.
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns absolute values of elements. Often used before magnitude calculation to handle negative values.
* [series\_pearson\_correlation](/apl/scalar-functions/time-series/series-pearson-correlation): Computes correlation between series. Use when measuring similarity instead of magnitude.
* [series\_stats](/apl/scalar-functions/time-series/series-stats): Returns comprehensive statistics. Use when you need multiple measures instead of just magnitude.
## Other query languages [#other-query-languages]
In Splunk SPL, you would typically implement magnitude calculation manually using `eval` with square root and sum operations. In APL, `series_magnitude` provides this calculation as a built-in function.
```sql Splunk example
... | eval squared_sum=pow(val1,2)+pow(val2,2)+pow(val3,2)
| eval magnitude=sqrt(squared_sum)
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([3, 4, 5])
]
| extend magnitude = series_magnitude(values)
```
In SQL, you would need to manually compute the magnitude using square root and sum of squares. In APL, `series_magnitude` provides this calculation in a single function for array data.
```sql SQL example
SELECT SQRT(SUM(value * value)) AS magnitude
FROM measurements
GROUP BY group_id;
```
```kusto APL equivalent
datatable(values: dynamic)
[
dynamic([3, 4, 5])
]
| extend magnitude = series_magnitude(values)
```
---
# series_max
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-max
The `series_max` function compares two numeric arrays element by element and returns a new array. Each position in the result contains the maximum value between the corresponding elements from the two input arrays.
You use `series_max` when you want to create an envelope or upper bound from multiple series, combine baseline metrics with actual values, or merge data from different sources by keeping the higher value at each point. For example, you can compare response times across different servers and keep the higher value at each time point, or combine SLA thresholds with actual measurements.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_max(array1, array2)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ----- | -------------------------------------------------------------------------- |
| `array1` | array | The first array of numeric values. |
| `array2` | array | The second array of numeric values. Must have the same length as `array1`. |
### Returns [#returns]
An array of numeric values. Each element is the maximum of the corresponding elements from `array1` and `array2`.
## Use case examples [#use-case-examples]
You want to create an upper bound by comparing request durations across two different cities and keeping the higher value at each time point.
**Query**
```kusto
['sample-http-logs']
| take 50
| make-series london_avg = avgif(req_duration_ms, ['geo.city'] == 'London'),
paris_avg = avgif(req_duration_ms, ['geo.city'] == 'Paris')
on _time step 1h
| extend max_duration = series_max(london_avg, paris_avg)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20take%2050%20%7C%20make-series%20london_avg%20%3D%20avgif\(req_duration_ms%2C%20%5B'geo.city'%5D%20%3D%3D%20'London'\)%2C%20paris_avg%20%3D%20avgif\(req_duration_ms%2C%20%5B'geo.city'%5D%20%3D%3D%20'Paris'\)%20on%20_time%20step%201h%20%7C%20extend%20max_duration%20%3D%20series_max\(london_avg%2C%20paris_avg\)%22%7D)
**Output**
| london\_avg | paris\_avg | max\_duration |
| ---------------- | ---------------- | ---------------- |
| \[120, 150, 100] | \[180, 130, 190] | \[180, 150, 190] |
This query compares response times between two cities and creates a series containing the higher value at each time point.
You want to track the maximum count between successful and failed requests at each time point to identify the dominant request type.
**Query**
```kusto
['sample-http-logs']
| take 50
| make-series success_count = countif(status == '200'),
failure_count = countif(status != '200')
on _time step 1h
| extend max_count = series_max(success_count, failure_count)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20take%2050%20%7C%20make-series%20success_count%20%3D%20countif\(status%20%3D%3D%20'200'\)%2C%20failure_count%20%3D%20countif\(status%20!%3D%20'200'\)%20on%20_time%20step%201h%20%7C%20extend%20max_count%20%3D%20series_max\(success_count%2C%20failure_count\)%22%7D)
**Output**
| success\_count | failure\_count | max\_count |
| ---------------- | -------------- | ---------------- |
| \[300, 280, 310] | \[10, 290, 15] | \[300, 290, 310] |
This query compares success and failure counts and returns the higher value at each time point, helping you understand the dominant traffic pattern.
## List of related functions [#list-of-related-functions]
* [series\_min](/apl/scalar-functions/time-series/series-min): Compares two arrays and returns the minimum value at each position.
* [series\_less](/apl/scalar-functions/time-series/series-less): Compares two arrays and returns `true` where elements in the first array are less than the second.
* [series\_greater](/apl/scalar-functions/time-series/series-greater): Compares two arrays and returns `true` where the first array element is greater than the second.
* [max](/apl/aggregation-function/max): Aggregation function that returns the maximum value across grouped records.
## Other query languages [#other-query-languages]
In Splunk SPL, element-wise maximum comparisons typically require custom logic with `eval` or `foreach`. In contrast, APL provides the specialized `series_max` function to directly compare arrays element by element and return the maximum values.
```sql Splunk example
... | timechart avg(cpu_usage) as cpu1, avg(cpu_usage_backup) as cpu2
| eval max_cpu = if(cpu1 > cpu2, cpu1, cpu2)
```
```kusto APL equivalent
['sample-http-logs']
| make-series primary = avg(req_duration_ms), backup = avg(req_duration_ms) on _time step 1m
| extend max_values = series_max(primary, backup)
```
In ANSI SQL, you use the `GREATEST()` function to compare scalar values. To compare sequences element-wise, you need window functions or complex joins. In APL, `series_max` simplifies this by applying the maximum operation across arrays in a single step.
```sql SQL example
SELECT _time,
GREATEST(t1.req_duration_ms, t2.req_duration_ms) AS max_duration
FROM logs t1
JOIN logs t2
ON t1._time = t2._time
```
```kusto APL equivalent
['sample-http-logs']
| make-series series1 = avg(req_duration_ms), series2 = avg(req_duration_ms) on _time step 1m
| extend max_series = series_max(series1, series2)
```
---
# series_min
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-min
The `series_min` function compares two numeric arrays element by element and returns a new array. Each position in the result contains the minimum value between the corresponding elements from the two input arrays.
You use `series_min` when you want to create a lower bound from multiple series, combine baseline metrics with actual values while keeping the smaller value, or merge data from different sources by selecting the lower value at each point. For example, you can compare response times across different servers and keep the lower value at each time point, or create minimum thresholds from multiple sources.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_min(array1, array2)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ----- | -------------------------------------------------------------------------- |
| `array1` | array | The first array of numeric values. |
| `array2` | array | The second array of numeric values. Must have the same length as `array1`. |
### Returns [#returns]
An array of numeric values. Each element is the minimum of the corresponding elements from `array1` and `array2`.
## Use case examples [#use-case-examples]
You want to create a lower bound by comparing request durations across two different cities and keeping the lower value at each time point.
**Query**
```kusto
['sample-http-logs']
| take 50
| make-series london_avg = avgif(req_duration_ms, ['geo.city'] == 'London'),
paris_avg = avgif(req_duration_ms, ['geo.city'] == 'Paris')
on _time step 1h
| extend min_duration = series_min(london_avg, paris_avg)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20take%2050%20%7C%20make-series%20london_avg%20%3D%20avgif\(req_duration_ms%2C%20%5B'geo.city'%5D%20%3D%3D%20'London'\)%2C%20paris_avg%20%3D%20avgif\(req_duration_ms%2C%20%5B'geo.city'%5D%20%3D%3D%20'Paris'\)%20on%20_time%20step%201h%20%7C%20extend%20min_duration%20%3D%20series_min\(london_avg%2C%20paris_avg\)%22%7D)
**Output**
| london\_avg | paris\_avg | min\_duration |
| ---------------- | ---------------- | ---------------- |
| \[120, 150, 100] | \[180, 130, 190] | \[120, 130, 100] |
This query compares response times between two cities and creates a series containing the lower value at each time point.
You want to track the minimum count between successful and failed requests at each time point to identify which type has less traffic.
**Query**
```kusto
['sample-http-logs']
| take 50
| make-series success_count = countif(status == '200'),
failure_count = countif(status != '200')
on _time step 1h
| extend min_count = series_min(success_count, failure_count)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20take%2050%20%7C%20make-series%20success_count%20%3D%20countif\(status%20%3D%3D%20'200'\)%2C%20failure_count%20%3D%20countif\(status%20!%3D%20'200'\)%20on%20_time%20step%201h%20%7C%20extend%20min_count%20%3D%20series_min\(success_count%2C%20failure_count\)%22%7D)
**Output**
| success\_count | failure\_count | min\_count |
| ---------------- | -------------- | -------------- |
| \[300, 280, 310] | \[10, 290, 15] | \[10, 280, 15] |
This query compares success and failure counts and returns the lower value at each time point, helping you identify the minority traffic pattern.
## List of related functions [#list-of-related-functions]
* [series\_max](/apl/scalar-functions/time-series/series-max): Compares two arrays and returns the maximum value at each position.
* [series\_less](/apl/scalar-functions/time-series/series-less): Compares two arrays and returns `true` where elements in the first array are less than the second.
* [series\_greater](/apl/scalar-functions/time-series/series-greater): Compares two arrays and returns `true` where the first array element is greater than the second.
* [min](/apl/aggregation-function/min): Aggregation function that returns the minimum value across grouped records.
## Other query languages [#other-query-languages]
In Splunk SPL, element-wise minimum comparisons typically require custom logic with `eval` or `foreach`. In contrast, APL provides the specialized `series_min` function to directly compare arrays element by element and return the minimum values.
```sql Splunk example
... | timechart avg(latency) as latency1, avg(latency_backup) as latency2
| eval min_latency = if(latency1 < latency2, latency1, latency2)
```
```kusto APL equivalent
['sample-http-logs']
| make-series primary = avg(req_duration_ms), backup = avg(req_duration_ms) on _time step 1m
| extend min_values = series_min(primary, backup)
```
In ANSI SQL, you use the `LEAST()` function to compare scalar values. To compare sequences element-wise, you need window functions or complex joins. In APL, `series_min` simplifies this by applying the minimum operation across arrays in a single step.
```sql SQL example
SELECT _time,
LEAST(t1.req_duration_ms, t2.req_duration_ms) AS min_duration
FROM logs t1
JOIN logs t2
ON t1._time = t2._time
```
```kusto APL equivalent
['sample-http-logs']
| make-series series1 = avg(req_duration_ms), series2 = avg(req_duration_ms) on _time step 1m
| extend min_series = series_min(series1, series2)
```
---
# series_multiply
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-multiply
The `series_multiply` function performs element-wise multiplication between two numeric dynamic arrays (series). Each element in the first series is multiplied by the corresponding element at the same position in the second series.
You can use `series_multiply` when you need to scale time-series data, apply weights, or combine multiple metrics through multiplication. This is particularly useful for calculating weighted scores, applying normalization factors, or computing products of related measurements.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_multiply(series1, series2)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------- |
| `series1` | dynamic | A dynamic array of numeric values. |
| `series2` | dynamic | A dynamic array of numeric values. |
### Returns [#returns]
A dynamic array where each element is the result of multiplying the corresponding elements of `series1` and `series2`. If the arrays have different lengths, the shorter array is extended with `null` values.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_multiply` to apply weighting factors to request durations, calculating weighted performance metrics.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by ['geo.city']
| extend weights = dynamic([1.0, 1.2, 0.8, 1.1, 0.9])
| extend weighted_durations = series_multiply(durations, weights)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20%5B'geo.city'%5D%20%7C%20extend%20weights%20%3D%20dynamic\(%5B1.0%2C%201.2%2C%200.8%2C%201.1%2C%200.9%5D\)%20%7C%20extend%20weighted_durations%20%3D%20series_multiply\(durations%2C%20weights\)%20%7C%20take%205%22%7D)
**Output**
| geo.city | durations | weights | weighted\_durations |
| -------- | --------------------- | -------------------------- | --------------------------- |
| Seattle | \[50, 60, 55, 58, 52] | \[1.0, 1.2, 0.8, 1.1, 0.9] | \[50, 72, 44, 63.8, 46.8] |
| Portland | \[45, 50, 48, 52, 47] | \[1.0, 1.2, 0.8, 1.1, 0.9] | \[45, 60, 38.4, 57.2, 42.3] |
This query applies priority weights to request durations, emphasizing certain time periods or request types in performance analysis.
In OpenTelemetry traces, you can use `series_multiply` to calculate resource cost estimates by multiplying span durations with cost factors.
**Query**
```kusto
['otel-demo-traces']
| extend duration_ms = duration / 1ms
| summarize durations = make_list(duration_ms) by ['service.name']
| extend cost_factor = dynamic([0.001, 0.001, 0.001, 0.001, 0.001])
| extend estimated_cost = series_multiply(durations, cost_factor)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20duration_ms%20%3D%20duration%20%2F%201ms%20%7C%20summarize%20durations%20%3D%20make_list\(duration_ms\)%20by%20%5B'service.name'%5D%20%7C%20extend%20cost_factor%20%3D%20dynamic\(%5B0.001%2C%200.001%2C%200.001%2C%200.001%2C%200.001%5D\)%20%7C%20extend%20estimated_cost%20%3D%20series_multiply\(durations%2C%20cost_factor\)%20%7C%20take%205%22%7D)
**Output**
| service.name | durations | cost\_factor | estimated\_cost |
| ------------ | -------------------------- | ------------------------------------ | -------------------------------- |
| frontend | \[100, 120, 95, 110, 105] | \[0.001, 0.001, 0.001, 0.001, 0.001] | \[0.1, 0.12, 0.095, 0.11, 0.105] |
| checkout | \[200, 220, 195, 210, 205] | \[0.001, 0.001, 0.001, 0.001, 0.001] | \[0.2, 0.22, 0.195, 0.21, 0.205] |
This query multiplies span durations by a cost factor to estimate resource costs, useful for cost optimization analysis.
In security logs, you can use `series_multiply` to calculate risk scores by multiplying request frequencies with severity factors.
**Query**
```kusto
['sample-http-logs']
| summarize request_counts = make_list(req_duration_ms) by status
| extend severity = dynamic([1.0, 3.0, 2.0, 5.0, 4.0])
| extend risk_scores = series_multiply(request_counts, severity)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20request_counts%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20severity%20%3D%20dynamic\(%5B1.0%2C%203.0%2C%202.0%2C%205.0%2C%204.0%5D\)%20%7C%20extend%20risk_scores%20%3D%20series_multiply\(request_counts%2C%20severity\)%20%7C%20take%205%22%7D)
**Output**
| status | request\_counts | severity | risk\_scores |
| ------ | --------------------- | -------------------------- | ------------------------ |
| 200 | \[50, 55, 48, 52, 49] | \[1.0, 3.0, 2.0, 5.0, 4.0] | \[50, 165, 96, 260, 196] |
| 401 | \[10, 12, 8, 15, 11] | \[1.0, 3.0, 2.0, 5.0, 4.0] | \[10, 36, 16, 75, 44] |
This query multiplies request metrics by severity factors to calculate weighted risk scores for security analysis.
## List of related functions [#list-of-related-functions]
* [series\_subtract](/apl/scalar-functions/time-series/series-subtract): Performs element-wise subtraction of two series. Use when you need to subtract values instead of multiplying them.
* [series\_pow](/apl/scalar-functions/time-series/series-pow): Raises series elements to a power. Use when you need exponentiation instead of multiplication.
* [series\_sum](/apl/scalar-functions/time-series/series-sum): Returns the sum of all values in a series. Use to aggregate the results after multiplication.
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns absolute values of elements. Use when you need magnitude without direction.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use the `eval` command with the multiplication operator to calculate products between fields. In APL, `series_multiply` operates on entire arrays at once, performing element-wise multiplication efficiently.
```sql Splunk example
... | eval product=value1 * value2
```
```kusto APL equivalent
datatable(series1: dynamic, series2: dynamic)
[
dynamic([10, 20, 30]), dynamic([2, 3, 4])
]
| extend product = series_multiply(series1, series2)
```
In SQL, you multiply values using the `*` operator on individual columns. In APL, `series_multiply` performs element-wise multiplication across entire arrays stored in single columns.
```sql SQL example
SELECT value1 * value2 AS product
FROM measurements;
```
```kusto APL equivalent
datatable(series1: dynamic, series2: dynamic)
[
dynamic([10, 20, 30]), dynamic([2, 3, 4])
]
| extend product = series_multiply(series1, series2)
```
---
# series_not_equals
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-not-equals
The `series_not_equals` function compares two numeric arrays element by element and returns a new array of Boolean values. Each element in the output array indicates whether the corresponding elements in the input arrays aren’t equal.
You use this function when you want to detect differences between two time series or arrays of values. It’s particularly useful when analyzing request patterns, response times, or service traces, where identifying mismatches across parallel series matters.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_not_equals(series1, series2)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | --------------- | ---------------------------------------------------------------------------- |
| `series1` | dynamic (array) | The first numeric array to compare. |
| `series2` | dynamic (array) | The second numeric array to compare. Must have the same length as `series1`. |
### Returns [#returns]
A dynamic array of Boolean values. Each element is `true` if the corresponding elements in the input arrays aren’t equal, and `false` otherwise.
## Use case examples [#use-case-examples]
You can use `series_not_equals` to identify differences in request durations across two groups of HTTP requests.
**Query**
```kusto
['sample-http-logs']
| summarize durations1 = make_list(req_duration_ms) by method
| join (
['sample-http-logs']
| summarize durations2 = make_list(req_duration_ms) by method
) on method
| extend diff_flags = series_not_equals(durations1, durations2)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations1%20%3D%20make_list\(req_duration_ms\)%20by%20method%20%7C%20join%20\(%20%5B'sample-http-logs'%5D%20%7C%20summarize%20durations2%20%3D%20make_list\(req_duration_ms\)%20by%20method%20\)%20on%20method%20%7C%20extend%20diff_flags%20%3D%20series_not_equals\(durations1%2C%20durations2\)%22%7D)
**Output**
| method | diff\_flags |
| ------ | ------------------- |
| GET | \[false,true,false] |
| POST | \[true,false,true] |
This query builds two lists of request durations grouped by method, compares them element by element, and returns an array showing where values differ.
You can use `series_not_equals` to compare the duration of spans between two services in the same trace.
**Query**
```kusto
['otel-demo-traces']
| where ['service.name'] == 'frontend'
| summarize frontend_durations = make_list(duration) by trace_id
| join (
['otel-demo-traces']
| where ['service.name'] == 'checkout'
| summarize checkout_durations = make_list(duration) by trace_id
) on trace_id
| extend diff_flags = series_not_equals(frontend_durations, checkout_durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20%5B'service.name'%5D%20%3D%3D%20'frontend'%20%7C%20summarize%20frontend_durations%20%3D%20make_list\(duration\)%20by%20trace_id%20%7C%20join%20\(%20%5B'otel-demo-traces'%5D%20%7C%20where%20%5B'service.name'%5D%20%3D%3D%20'checkout'%20%7C%20summarize%20checkout_durations%20%3D%20make_list\(duration\)%20by%20trace_id%20\)%20on%20trace_id%20%7C%20extend%20diff_flags%20%3D%20series_not_equals\(frontend_durations%2C%20checkout_durations\)%22%7D)
**Output**
| trace\_id | diff\_flags |
| --------- | ------------- |
| abc123 | \[false,true] |
| def456 | \[true,false] |
This query compares span durations between `frontend` and `checkoutservice` for the same trace and shows where durations differ.
You can use `series_not_equals` to check if HTTP status codes differ between requests from different countries.
**Query**
```kusto
['sample-http-logs']
| where ['geo.country'] == 'United States'
| summarize us_statuses = make_list(status) by uri
| join (
['sample-http-logs']
| where ['geo.country'] == 'Germany'
| summarize de_statuses = make_list(status) by uri
) on uri
| extend diff_flags = series_not_equals(us_statuses, de_statuses)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20%5B'geo.country'%5D%20%3D%3D%20'United%20States'%20%7C%20summarize%20us_statuses%20%3D%20make_list\(status\)%20by%20uri%20%7C%20join%20\(%20%5B'sample-http-logs'%5D%20%7C%20where%20%5B'geo.country'%5D%20%3D%3D%20'Germany'%20%7C%20summarize%20de_statuses%20%3D%20make_list\(status\)%20by%20uri%20\)%20on%20uri%20%7C%20extend%20diff_flags%20%3D%20series_not_equals\(us_statuses%2C%20de_statuses\)%22%7D)
**Output**
| uri | diff\_flags |
| ------------- | ------------------- |
| /api/login | \[false,true,false] |
| /api/products | \[true,false] |
This query identifies differences in status codes returned by the same URI when accessed from the US and Germany.
## List of related functions [#list-of-related-functions]
* [series\_greater\_equals](/apl/scalar-functions/time-series/series-greater-equals): Compares two arrays and returns `true` when elements in the first array are greater than or equal to the second array.
* [series\_greater](/apl/scalar-functions/time-series/series-greater): Compares two arrays and returns `true` where the first array element is greater than the second.
* [series\_less](/apl/scalar-functions/time-series/series-less): Compares two arrays and returns `true` where the first array element is less than the second.
* [series\_less\_equals](/apl/scalar-functions/time-series/series-less-equals): Compares two arrays and returns `true` where the first array element is less than or equal to the second.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically compare fields directly with the `!=` operator. In APL, `series_not_equals` applies this logic to arrays, returning an array of Boolean values instead of a single Boolean.
```sql Splunk example
... | eval is_different = if(fieldA != fieldB, 1, 0)
```
```kusto APL equivalent
print result = series_not_equals(dynamic([1,2,3]), dynamic([1,5,3]))
```
In ANSI SQL, comparisons with `<>` return a single Boolean for each row. APL’s `series_not_equals` function extends this idea to arrays, producing a series of Boolean values instead of a single Boolean.
```sql SQL example
SELECT fieldA <> fieldB AS is_different
FROM my_table
```
```kusto APL equivalent
print result = series_not_equals(dynamic([10,20,30]), dynamic([10,25,30]))
```
---
# series_pearson_correlation
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-pearson-correlation
The `series_pearson_correlation` function calculates the Pearson correlation coefficient between two numeric dynamic arrays (series). This measures the linear relationship between the two series, returning a value between -1 and 1, where 1 indicates perfect positive correlation, -1 indicates perfect negative correlation, and 0 indicates no linear correlation.
You can use `series_pearson_correlation` when you need to measure the strength and direction of linear relationships between time-series datasets. This is particularly useful for identifying related metrics, detecting causal relationships, validating hypotheses about system behavior, or finding leading indicators of performance issues.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_pearson_correlation(series1, series2)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------- |
| `series1` | dynamic | A dynamic array of numeric values. |
| `series2` | dynamic | A dynamic array of numeric values. |
### Returns [#returns]
A numeric value between -1 and 1 representing the Pearson correlation coefficient:
* `1`: Perfect positive linear correlation
* `0`: No linear correlation
* `-1`: Perfect negative linear correlation
## Use case examples [#use-case-examples]
In log analysis, you can use `series_pearson_correlation` to identify relationships between request durations across different geographic regions, helping understand if performance issues are correlated.
**Query**
```kusto
['sample-http-logs']
| extend city1 = iff(['geo.city'] == 'Tokyo', req_duration_ms, 0)
| extend city2 = iff(['geo.city'] == 'Nagasaki', req_duration_ms, 0)
| summarize tokyo_times = make_list(city1), nagasaki_times = make_list(city2)
| extend correlation = series_pearson_correlation(tokyo_times, nagasaki_times)
| project correlation
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20city1%20%3D%20iff\(%5B'geo.city'%5D%20%3D%3D%20'Tokyo'%2C%20req_duration_ms%2C%200\)%20%7C%20extend%20city2%20%3D%20iff\(%5B'geo.city'%5D%20%3D%3D%20'Nagasaki'%2C%20req_duration_ms%2C%200\)%20%7C%20summarize%20tokyo_times%20%3D%20make_list\(city1\)%2C%20nagasaki_times%20%3D%20make_list\(city2\)%20%7C%20extend%20correlation%20%3D%20series_pearson_correlation\(tokyo_times%2C%20nagasaki_times\)%20%7C%20project%20correlation%22%7D)
**Output**
| correlation |
| ----------- |
| 0.87 |
This query calculates the correlation between request durations in Tokyo and Nagasaki, revealing if performance issues in one region tend to coincide with issues in another.
In OpenTelemetry traces, you can use `series_pearson_correlation` to analyze relationships between service latencies, identifying dependencies and bottlenecks.
**Query**
```kusto
['otel-demo-traces']
| extend duration_ms = duration / 1ms
| extend frontend_dur = iff(['service.name'] == 'frontend', duration_ms, 0)
| extend checkout_dur = iff(['service.name'] == 'checkout', duration_ms, 0)
| summarize frontend = make_list(frontend_dur), checkout = make_list(checkout_dur)
| extend correlation = series_pearson_correlation(frontend, checkout)
| project correlation
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20duration_ms%20%3D%20duration%20%2F%201ms%20%7C%20extend%20frontend_dur%20%3D%20iff\(%5B'service.name'%5D%20%3D%3D%20'frontend'%2C%20duration_ms%2C%200\)%20%7C%20extend%20checkout_dur%20%3D%20iff\(%5B'service.name'%5D%20%3D%3D%20'checkout'%2C%20duration_ms%2C%200\)%20%7C%20summarize%20frontend%20%3D%20make_list\(frontend_dur\)%2C%20checkout%20%3D%20make_list\(checkout_dur\)%20%7C%20extend%20correlation%20%3D%20series_pearson_correlation\(frontend%2C%20checkout\)%20%7C%20project%20correlation%22%7D)
**Output**
| correlation |
| ----------- |
| 0.65 |
This query measures the correlation between frontend and checkout service latencies, helping understand if performance of one service affects the other.
In security logs, you can use `series_pearson_correlation` to identify relationships between failed authentication attempts and successful requests, detecting potential attack patterns.
**Query**
```kusto
['sample-http-logs']
| extend success_count = iff(status == '200', 1, 0)
| extend failure_count = iff(status == '500', 1, 0)
| summarize successes = make_list(success_count), failures = make_list(failure_count) by bin(_time, 1h)
| extend correlation = series_pearson_correlation(successes, failures)
| project correlation
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20extend%20success_count%20%3D%20iff\(status%20%3D%3D%20'200'%2C%201%2C%200\)%20%7C%20extend%20failure_count%20%3D%20iff\(status%20%3D%3D%20'500'%2C%201%2C%200\)%20%7C%20summarize%20successes%20%3D%20make_list\(success_count\)%2C%20failures%20%3D%20make_list\(failure_count\)%20by%20bin\(_time%2C%201h\)%20%7C%20extend%20correlation%20%3D%20series_pearson_correlation\(successes%2C%20failures\)%20%7C%20project%20correlation%22%7D)
**Output**
| correlation |
| ----------- |
| -0.45 |
This query analyzes the correlation between successful and failed requests, where a negative correlation might indicate that high failure rates suppress successful requests, potentially signaling an attack.
## List of related functions [#list-of-related-functions]
* [series\_magnitude](/apl/scalar-functions/time-series/series-magnitude): Calculates the magnitude of a series. Use when you need vector length instead of correlation.
* [series\_stats](/apl/scalar-functions/time-series/series-stats): Returns comprehensive statistics. Use when you need variance and covariance components separately.
* [series\_subtract](/apl/scalar-functions/time-series/series-subtract): Performs element-wise subtraction. Often used to compute deviations before correlation analysis.
* [series\_multiply](/apl/scalar-functions/time-series/series-multiply): Performs element-wise multiplication. Use for weighted combinations instead of correlation.
## Other query languages [#other-query-languages]
In Splunk SPL, you would typically need to export data and use external statistical tools to calculate correlation. In APL, `series_pearson_correlation` provides built-in correlation analysis for array data.
```sql Splunk example
... | stats list(metric1) as m1, list(metric2) as m2 by group
... (manual correlation calculation or external tool)
```
```kusto APL equivalent
datatable(series1: dynamic, series2: dynamic)
[
dynamic([1, 2, 3, 4, 5]), dynamic([2, 4, 6, 8, 10])
]
| extend correlation = series_pearson_correlation(series1, series2)
```
In SQL, correlation functions exist but typically operate on row-based data. In APL, `series_pearson_correlation` works directly on array columns, making time-series correlation analysis more straightforward.
```sql SQL example
SELECT CORR(metric1, metric2) AS correlation
FROM measurements
GROUP BY group_id;
```
```kusto APL equivalent
datatable(series1: dynamic, series2: dynamic)
[
dynamic([1, 2, 3, 4, 5]), dynamic([2, 4, 6, 8, 10])
]
| extend correlation = series_pearson_correlation(series1, series2)
```
---
# series_pow
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-pow
The `series_pow` function raises each element in a numeric dynamic array (series) to a specified power. This performs element-wise exponentiation across the entire series.
You can use `series_pow` when you need to apply power transformations to time-series data. This is particularly useful for non-linear data transformations, calculating exponential growth patterns, applying polynomial features in analysis, or emphasizing larger values in your data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_pow(array, power)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | -------------------------------------------- |
| `array` | dynamic | A dynamic array of numeric values (base). |
| `power` | real | The exponent to which to raise each element. |
### Returns [#returns]
A dynamic array where each element is the result of raising the corresponding input element to the specified power.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_pow` to emphasize outliers by squaring request durations, making larger values more prominent in analysis.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend squared_durations = series_pow(durations, 2)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20squared_durations%20%3D%20series_pow\(durations%2C%202\)%20%7C%20take%205%22%7D)
**Output**
| id | durations | squared\_durations |
| ---- | ------------------- | --------------------------- |
| u123 | \[50, 100, 75, 200] | \[2500, 10000, 5625, 40000] |
| u456 | \[30, 45, 60, 90] | \[900, 2025, 3600, 8100] |
This query squares request durations to amplify the differences, making performance anomalies more visible for analysis.
In OpenTelemetry traces, you can use `series_pow` to calculate exponential penalty scores based on span durations, emphasizing longer spans.
**Query**
```kusto
['otel-demo-traces']
| extend duration_ms = duration / 1ms
| summarize durations = make_list(duration_ms) by ['service.name']
| extend penalty_score = series_pow(durations, 1.5)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20duration_ms%20%3D%20duration%20%2F%201ms%20%7C%20summarize%20durations%20%3D%20make_list\(duration_ms\)%20by%20%5B'service.name'%5D%20%7C%20extend%20penalty_score%20%3D%20series_pow\(durations%2C%201.5\)%20%7C%20take%205%22%7D)
**Output**
| service.name | durations | penalty\_score |
| ------------ | --------------------- | ------------------------- |
| frontend | \[100, 200, 150, 250] | \[1000, 2828, 1837, 3952] |
| checkout | \[50, 75, 60, 100] | \[353, 649, 464, 1000] |
This query applies a power transformation to span durations, creating a penalty score that disproportionately penalizes longer spans.
In security logs, you can use `series_pow` to calculate non-linear risk scores based on request counts, where higher volumes represent exponentially greater risk.
**Query**
```kusto
['sample-http-logs']
| summarize request_counts = make_list(req_duration_ms) by status
| extend risk_factor = series_pow(request_counts, 1.8)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20request_counts%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20risk_factor%20%3D%20series_pow\(request_counts%2C%201.8\)%20%7C%20take%205%22%7D)
**Output**
| status | request\_counts | risk\_factor |
| ------ | --------------------- | ------------------------- |
| 200 | \[50, 60, 55, 58] | \[1767, 2601, 2121, 2419] |
| 401 | \[100, 120, 110, 115] | \[6309, 8710, 7328, 7926] |
This query applies an exponential transformation to request counts, creating risk scores where high-volume patterns receive disproportionately higher scores.
## List of related functions [#list-of-related-functions]
* [series\_multiply](/apl/scalar-functions/time-series/series-multiply): Performs element-wise multiplication of two series. Use when you need multiplication between two series instead of raising to a power.
* [series\_log](/apl/scalar-functions/time-series/series-log): Computes the natural logarithm of each element. Use as the inverse operation to exponentials.
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns the absolute value of each element. Use when you need magnitude without power transformations.
* [series\_sign](/apl/scalar-functions/time-series/series-sign): Returns the sign of each element. Useful before applying power operations to handle negative values.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use the `pow()` function within an `eval` command to calculate powers. In APL, `series_pow` applies the power operation to every element in an array simultaneously.
```sql Splunk example
... | eval squared=pow(value, 2)
```
```kusto APL equivalent
datatable(x: dynamic)
[
dynamic([2, 3, 4, 5])
]
| extend squared = series_pow(x, 2)
```
In SQL, you use the `POWER()` function to raise values to a power on individual rows. In APL, `series_pow` operates on entire arrays, applying the exponentiation operation element-wise.
```sql SQL example
SELECT POWER(value, 2) AS squared
FROM measurements;
```
```kusto APL equivalent
datatable(x: dynamic)
[
dynamic([2, 3, 4, 5])
]
| extend squared = series_pow(x, 2)
```
---
# series_sign
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-sign
The `series_sign` function returns the sign of each element in a numeric dynamic array (series). The function returns -1 for negative numbers, 0 for zero, and 1 for positive numbers.
You can use `series_sign` when you need to identify the direction or polarity of values in time-series data. This is particularly useful for detecting changes in trends, classifying values by their sign, or preparing data for further analysis where only the direction matters, not the magnitude.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_sign(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------- |
| `array` | dynamic | A dynamic array of numeric values. |
### Returns [#returns]
A dynamic array where each element is:
* `-1` if the corresponding input element is negative
* `0` if the corresponding input element is zero
* `1` if the corresponding input element is positive
## Use case examples [#use-case-examples]
In log analysis, you can use `series_sign` to detect whether request durations are above or below a baseline by first subtracting the baseline, then examining the sign.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend baseline = 100
| extend deviations = series_subtract(durations, dynamic([100, 100, 100, 100, 100]))
| extend trend = series_sign(deviations)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20baseline%20%3D%20100%20%7C%20extend%20deviations%20%3D%20series_subtract\(durations%2C%20dynamic\(%5B100%2C%20100%2C%20100%2C%20100%2C%20100%5D\)\)%20%7C%20extend%20trend%20%3D%20series_sign\(deviations\)%20%7C%20take%205%22%7D)
**Output**
| id | durations | deviations | trend |
| ---- | ------------------------ | --------------------- | ------------------- |
| u123 | \[120, 95, 105, 80, 110] | \[20, -5, 5, -20, 10] | \[1, -1, 1, -1, 1] |
| u456 | \[85, 100, 90, 105, 95] | \[-15, 0, -10, 5, -5] | \[-1, 0, -1, 1, -1] |
This query calculates deviations from a baseline and uses `series_sign` to classify whether each request was slower (1), faster (-1), or equal (0) to the baseline.
In OpenTelemetry traces, you can use `series_sign` to identify performance improvements or degradations by comparing current spans against previous measurements.
**Query**
```kusto
['otel-demo-traces']
| extend duration_ms = duration / 1ms
| summarize current = make_list(duration_ms) by ['service.name']
| extend previous = dynamic([100, 120, 95, 110, 105])
| extend change = series_subtract(current, previous)
| extend direction = series_sign(change)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20duration_ms%20%3D%20duration%20%2F%201ms%20%7C%20summarize%20current%20%3D%20make_list\(duration_ms\)%20by%20%5B'service.name'%5D%20%7C%20extend%20previous%20%3D%20dynamic\(%5B100%2C%20120%2C%2095%2C%20110%2C%20105%5D\)%20%7C%20extend%20change%20%3D%20series_subtract\(current%2C%20previous\)%20%7C%20extend%20direction%20%3D%20series_sign\(change\)%20%7C%20take%205%22%7D)
**Output**
| service.name | current | change | direction |
| ------------ | ------------------------- | ------------------- | ------------------- |
| frontend | \[95, 115, 100, 105, 110] | \[-5, -5, 5, -5, 5] | \[-1, -1, 1, -1, 1] |
| checkout | \[105, 125, 90, 115, 100] | \[5, 5, -5, 5, -5] | \[1, 1, -1, 1, -1] |
This query compares current and previous span durations, using `series_sign` to classify each change as improvement (-1), degradation (1), or no change (0).
In security logs, you can use `series_sign` to classify request patterns as above or below normal thresholds, helping identify potential security anomalies.
**Query**
```kusto
['sample-http-logs']
| summarize counts = make_list(req_duration_ms) by status
| extend threshold = dynamic([50, 50, 50, 50, 50])
| extend difference = series_subtract(counts, threshold)
| extend alert_flag = series_sign(difference)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20counts%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20threshold%20%3D%20dynamic\(%5B50%2C%2050%2C%2050%2C%2050%2C%2050%5D\)%20%7C%20extend%20difference%20%3D%20series_subtract\(counts%2C%20threshold\)%20%7C%20extend%20alert_flag%20%3D%20series_sign\(difference\)%20%7C%20take%205%22%7D)
**Output**
| status | counts | difference | alert\_flag |
| ------ | --------------------- | -------------------- | ------------------ |
| 200 | \[45, 52, 48, 55, 50] | \[-5, 2, -2, 5, 0] | \[-1, 1, -1, 1, 0] |
| 401 | \[60, 75, 55, 80, 70] | \[10, 25, 5, 30, 20] | \[1, 1, 1, 1, 1] |
This query compares request metrics against thresholds and uses `series_sign` to create alert flags, where 1 indicates above-threshold activity that might warrant investigation.
## List of related functions [#list-of-related-functions]
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns the absolute value of each element. Use when you need magnitude without direction information.
* [series\_subtract](/apl/scalar-functions/time-series/series-subtract): Performs element-wise subtraction. Often used before `series_sign` to compute deviations from baselines.
* [series\_greater](/apl/scalar-functions/time-series/series-greater): Returns boolean comparison results. Use when you need explicit comparison against a threshold.
* [series\_less](/apl/scalar-functions/time-series/series-less): Returns boolean comparison results. Use for direct comparison instead of sign-based classification.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically implement sign detection using conditional statements with `eval`. In APL, `series_sign` provides a built-in function that operates on entire arrays efficiently.
```sql Splunk example
... | eval sign=case(value>0, 1, value<0, -1, true(), 0)
```
```kusto APL equivalent
datatable(x: dynamic)
[
dynamic([-5, -2, 0, 3, 7])
]
| extend signs = series_sign(x)
```
In SQL, you use the `SIGN()` function to determine the sign of individual values. In APL, `series_sign` applies this operation element-wise across entire arrays.
```sql SQL example
SELECT SIGN(value) AS sign_value
FROM measurements;
```
```kusto APL equivalent
datatable(x: dynamic)
[
dynamic([-5, -2, 0, 3, 7])
]
| extend signs = series_sign(x)
```
---
# series_sin
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-sin
The `series_sin` function in APL returns the sine of each element in a numeric array. It applies the mathematical sine function element by element, producing a new array of the same length.
You use `series_sin` when you want to transform numeric sequences into their trigonometric equivalents. This is useful for signal processing, data transformations, or preparing time series data for statistical and mathematical analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_sin(arr)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | --------------------------- |
| `arr` | dynamic | An array of numeric values. |
### Returns [#returns]
A dynamic array where each element is the sine of the corresponding input element.
## Use case examples [#use-case-examples]
You can use `series_sin` to transform request durations into trigonometric values for advanced analysis, such as periodicity detection.
**Query**
```kusto
['sample-http-logs']
| summarize arr = make_list(req_duration_ms, 10)
| extend sin_arr = series_sin(arr)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20arr%20%3D%20make_list\(req_duration_ms%2C%2010\)%20%7C%20extend%20sin_arr%20%3D%20series_sin\(arr\)%22%7D)
**Output**
| arr | sin\_arr |
| --------------------- | ---------------------------- |
| \[120, 250, 500, 750] | \[−0.58, −0.97, −0.52, 0.94] |
This query collects a sample of request durations and applies `series_sin` to generate a transformed series for analysis.
You can use `series_sin` to apply trigonometric transformation to span durations for modeling or feature extraction.
**Query**
```kusto
['otel-demo-traces']
| summarize arr = make_list(toint(duration), 10) by ['service.name']
| extend sin_arr = series_sin(arr)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20arr%20%3D%20make_list\(toint\(duration\)%2C%2010\)%20by%20%5B'service.name'%5D%20%7C%20extend%20sin_arr%20%3D%20series_sin\(arr\)%22%7D)
**Output**
| service.name | arr | sin\_arr |
| ------------ | ---------------- | --------------------- |
| frontend | \[200, 400, 600] | \[0.91, −0.73, −0.28] |
This query groups spans by service, aggregates durations into arrays, and transforms them using `series_sin`.
You can use `series_sin` to apply mathematical transformations to response times in security-related traffic to detect unusual patterns.
**Query**
```kusto
['sample-http-logs']
| summarize arr = make_list(req_duration_ms, 10) by ['geo.country']
| extend sin_arr = series_sin(arr)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20arr%20%3D%20make_list\(req_duration_ms%2C%2010\)%20by%20%5B'geo.country'%5D%20%7C%20extend%20sin_arr%20%3D%20series_sin\(arr\)%22%7D)
**Output**
| geo.country | arr | sin\_arr |
| ----------- | ---------------- | ---------------------- |
| US | \[180, 360, 540] | \[−0.80, −0.99, −0.84] |
This query filters failed requests (`403`) by country, aggregates durations, and applies `series_sin` to identify periodic anomalies.
## List of related functions [#list-of-related-functions]
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns the absolute value of each element in an array. Use it to normalize negative values in arrays.
* [series\_acos](/apl/scalar-functions/time-series/series-acos): Computes the arccosine of each element in an array. Use when you want the inverse cosine.
* [series\_atan](/apl/scalar-functions/time-series/series-atan): Computes the arctangent of each element in an array. Use when you want the inverse tangent.
* [series\_cos](/apl/scalar-functions/time-series/series-cos): Returns the cosine of each element in an array. Use it when analyzing cyclical data with a phase shift.
* [series\_tan](/apl/scalar-functions/time-series/series-tan): Returns the tangent of each element in an array. Use it when you want to transform arrays with tangent-based periodicity.
## Other query languages [#other-query-languages]
Splunk SPL doesn’t provide a direct equivalent to `series_sin` for arrays. Instead, you typically use the `eval` command with the `sin()` function to compute the sine of a single numeric value. In APL, `series_sin` applies `sin()` across an array in one step.
```sql Splunk example
... | eval sin_val=sin(duration)
```
```kusto APL equivalent
datatable(arr: dynamic)
[
dynamic([0, 1.57, 3.14])
]
| extend sin_values = series_sin(arr)
```
ANSI SQL provides the `SIN()` function, but it operates on single values rather than arrays. In APL, `series_sin` is vectorized and works directly on arrays without requiring iteration.
```sql SQL example
SELECT SIN(duration) AS sin_val
FROM traces;
```
```kusto APL equivalent
datatable(arr: dynamic)
[
dynamic([0, 1.57, 3.14])
]
| extend sin_values = series_sin(arr)
```
---
# series_stats_dynamic
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-stats-dynamic
The `series_stats_dynamic` function computes comprehensive statistical measures for a numeric dynamic array (series), returning results in a dynamic object format with named properties. This provides the same statistics as `series_stats` but with more convenient access through property names instead of array indices.
You can use `series_stats_dynamic` when you need statistical summaries with easier property-based access, better code readability, or when integrating with other dynamic data structures. This is particularly useful in complex analytical workflows where referring to statistics by name (`stats.min`, `stats.avg`) is clearer than using array indices.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_stats_dynamic(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------- |
| `array` | dynamic | A dynamic array of numeric values. |
### Returns [#returns]
A dynamic object containing the following statistical properties:
| Property | Description |
| ---------- | ----------------------------------------------------- |
| `min` | The minimum value in the input array. |
| `min_idx` | The first position of the minimum value in the array. |
| `max` | The maximum value in the input array. |
| `max_idx` | The first position of the maximum value in the array. |
| `avg` | The average value of the input array. |
| `variance` | The sample variance of the input array. |
| `stdev` | The sample standard deviation of the input array. |
## Use case examples [#use-case-examples]
In log analysis, you can use `series_stats_dynamic` to generate comprehensive statistical reports with readable property names.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend stats = series_stats_dynamic(durations)
| extend performance_score = 100 - (stats.stdev / stats.avg * 100)
| project id,
min = stats.min,
max = stats.max,
avg = stats.avg,
performance_score
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20stats%20%3D%20series_stats_dynamic\(durations\)%20%7C%20extend%20performance_score%20%3D%20100%20-%20\(stats.stdev%20%2F%20stats.avg%20*%20100\)%20%7C%20project%20id%2C%20min%20%3D%20stats.min%2C%20max%20%3D%20stats.max%2C%20avg%20%3D%20stats.avg%2C%20performance_score%20%7C%20take%205%22%7D)
**Output**
| id | min | max | avg | performance\_score |
| ---- | --- | --- | --- | ------------------ |
| u123 | 15 | 245 | 95 | 52.4 |
| u456 | 8 | 189 | 78 | 50.4 |
This query uses property names to access statistics and calculate a custom performance score based on the coefficient of variation.
In security logs, you can use `series_stats_dynamic` to build adaptive anomaly detection thresholds with clear, self-documenting property access.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by status
| extend stats = series_stats_dynamic(durations)
| extend lower_bound = stats.avg - (2 * stats.stdev)
| extend upper_bound = stats.avg + (2 * stats.stdev)
| extend range_ratio = (stats.max - stats.min) / stats.avg
| project status,
avg_duration = stats.avg,
stdev = stats.stdev,
lower_bound,
upper_bound,
range_ratio
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20stats%20%3D%20series_stats_dynamic\(durations\)%20%7C%20extend%20lower_bound%20%3D%20stats.avg%20-%20\(2%20*%20stats.stdev\)%20%7C%20extend%20upper_bound%20%3D%20stats.avg%20%2B%20\(2%20*%20stats.stdev\)%20%7C%20extend%20range_ratio%20%3D%20\(stats.max%20-%20stats.min\)%20%2F%20stats.avg%20%7C%20project%20status%2C%20avg_duration%20%3D%20stats.avg%2C%20stdev%20%3D%20stats.stdev%2C%20lower_bound%2C%20upper_bound%2C%20range_ratio%22%7D)
**Output**
| status | avg\_duration | stdev | lower\_bound | upper\_bound | range\_ratio |
| ------ | ------------- | ----- | ------------ | ------------ | ------------ |
| 200 | 52 | 12.5 | 27 | 77 | 6.44 |
| 401 | 450 | 850.2 | -1250.4 | 2150.4 | 19.68 |
| 500 | 125 | 95.3 | -65.6 | 315.6 | 4.36 |
This query uses named properties to calculate confidence intervals and assess the relative range of values for adaptive security monitoring.
## List of related functions [#list-of-related-functions]
* [series\_stats](/apl/scalar-functions/time-series/series-stats): Returns the same statistics as a 7-element array instead of a dynamic object with named properties.
* [series\_max](/apl/scalar-functions/time-series/series-max): Compares two arrays element-wise and returns the maximum values.
* [series\_min](/apl/scalar-functions/time-series/series-min): Compares two arrays element-wise and returns the minimum values.
* [todynamic](/apl/scalar-functions/conversion-functions/todynamic): Converts values to dynamic type for custom object construction.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use multiple `stats` functions and store results as separate fields. In APL, `series_stats_dynamic` provides all statistics in a dynamic object that you can access by property names.
```sql Splunk example
... | stats min(value) as min_val, max(value) as max_val,
avg(value) as avg_val, stdev(value) as stdev_val by user
```
```kusto APL equivalent
['sample-http-logs']
| summarize values = make_list(req_duration_ms) by id
| extend stats = series_stats_dynamic(values)
| extend min_val = stats.min, max_val = stats.max, avg_val = stats.avg
```
In SQL, you calculate statistics separately and work with individual columns. In APL, `series_stats_dynamic` provides all statistics in a single dynamic object with named properties that you can query and transform.
```sql SQL example
SELECT
user_id,
MIN(value) as min_val,
MAX(value) as max_val,
AVG(value) as avg_val,
STDDEV(value) as std_val
FROM measurements
GROUP BY user_id;
```
```kusto APL equivalent
['sample-http-logs']
| summarize values = make_list(req_duration_ms) by id
| extend stats = series_stats_dynamic(values)
| extend min_val = stats.min, max_val = stats.max
```
---
# series_stats
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-stats
The `series_stats` function computes comprehensive statistical measures for a numeric dynamic array (series), returning an array with seven elements containing minimum, maximum, average, variance, standard deviation, and the positions of minimum and maximum values.
You can use `series_stats` when you need a complete statistical summary of time-series data in a single operation. This is particularly useful for understanding data distribution, identifying outliers, calculating confidence intervals, or performing comprehensive data quality assessments without running multiple separate aggregations.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_stats(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ---------------------------------- |
| `array` | dynamic | A dynamic array of numeric values. |
### Returns [#returns]
An array with seven numeric elements in the following order:
| Index | Statistic | Description |
| ----- | --------- | ----------------------------------------------------- |
| 0 | min | The minimum value in the input array. |
| 1 | min\_idx | The first position of the minimum value in the array. |
| 2 | max | The maximum value in the input array. |
| 3 | max\_idx | The first position of the maximum value in the array. |
| 4 | avg | The average value of the input array. |
| 5 | variance | The sample variance of the input array. |
| 6 | stdev | The sample standard deviation of the input array. |
## Use case examples [#use-case-examples]
In log analysis, you can use `series_stats` to get a comprehensive statistical summary of request durations for each user, helping identify performance patterns and outliers.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend stats_array = series_stats(durations)
| project id,
min_duration = stats_array[0],
max_duration = stats_array[2],
avg_duration = stats_array[4],
stdev_duration = stats_array[6]
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20stats_array%20%3D%20series_stats\(durations\)%20%7C%20project%20id%2C%20min_duration%20%3D%20stats_array%5B0%5D%2C%20max_duration%20%3D%20stats_array%5B2%5D%2C%20avg_duration%20%3D%20stats_array%5B4%5D%2C%20stdev_duration%20%3D%20stats_array%5B6%5D%20%7C%20take%205%22%7D)
**Output**
| id | min\_duration | max\_duration | avg\_duration | stdev\_duration |
| ---- | ------------- | ------------- | ------------- | --------------- |
| u123 | 15 | 245 | 95 | 45.2 |
| u456 | 8 | 189 | 78 | 38.7 |
This query calculates comprehensive statistics for each user's request durations by extracting specific elements from the 7-element stats array.
In security logs, you can use `series_stats` to establish behavioral baselines and calculate anomaly detection thresholds based on variance.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by status
| extend stats_array = series_stats(durations)
| project status,
typical_duration = stats_array[4],
variance = stats_array[5],
stdev = stats_array[6],
max_observed = stats_array[2]
| extend anomaly_threshold = typical_duration + (3 * stdev)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20stats_array%20%3D%20series_stats\(durations\)%20%7C%20project%20status%2C%20typical_duration%20%3D%20stats_array%5B4%5D%2C%20variance%20%3D%20stats_array%5B5%5D%2C%20stdev%20%3D%20stats_array%5B6%5D%2C%20max_observed%20%3D%20stats_array%5B2%5D%20%7C%20extend%20anomaly_threshold%20%3D%20typical_duration%20%2B%20\(3%20*%20stdev\)%22%7D)
**Output**
| status | typical\_duration | variance | stdev | max\_observed | anomaly\_threshold |
| ------ | ----------------- | -------- | ----- | ------------- | ------------------ |
| 200 | 52 | 156.25 | 12.5 | 340 | 89.5 |
| 401 | 450 | 722840 | 850.2 | 8900 | 3000.6 |
| 500 | 125 | 9082 | 95.3 | 550 | 410.9 |
This query uses statistical analysis to establish normal behavior patterns and calculate anomaly detection thresholds based on standard deviations.
## List of related functions [#list-of-related-functions]
* [series\_stats\_dynamic](/apl/scalar-functions/time-series/series-stats-dynamic): Returns the same statistics as a dynamic object with named properties instead of an array.
* [series\_max](/apl/scalar-functions/time-series/series-max): Compares two arrays element-wise and returns the maximum values.
* [series\_min](/apl/scalar-functions/time-series/series-min): Compares two arrays element-wise and returns the minimum values.
* [avg](/apl/aggregation-function/avg): Aggregation function for calculating averages across rows.
* [stdev](/apl/aggregation-function/stdev): Aggregation function for standard deviation across rows.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use multiple `stats` functions to calculate different statistics. In APL, `series_stats` provides all common statistics in a single operation on array data, returning them as a 7-element array.
```sql Splunk example
... | stats min(value) as min_val, max(value) as max_val,
avg(value) as avg_val, stdev(value) as stdev_val by user
```
```kusto APL equivalent
['sample-http-logs']
| summarize values = make_list(req_duration_ms) by id
| extend stats = series_stats(values)
| extend min_val = stats[0], max_val = stats[2], avg_val = stats[4]
```
In SQL, you calculate multiple aggregate functions separately. In APL, `series_stats` provides all these statistics in a single function call on array data, returned as a 7-element array.
```sql SQL example
SELECT
MIN(value) as min_val,
MAX(value) as max_val,
AVG(value) as avg_val,
STDDEV(value) as std_val
FROM measurements
GROUP BY user_id;
```
```kusto APL equivalent
['sample-http-logs']
| summarize values = make_list(req_duration_ms) by id
| extend stats = series_stats(values)
| extend min_val = stats[0], max_val = stats[2], avg_val = stats[4]
```
---
# series_subtract
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-subtract
The `series_subtract` function performs element-wise subtraction between two numeric dynamic arrays (series). Each element in the first series is subtracted by the corresponding element at the same position in the second series.
You can use `series_subtract` when you need to compute differences between two time-series datasets. This is particularly useful for calculating deltas, deviations from baselines, changes over time, or comparing metrics between different groups or time periods.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_subtract(series1, series2)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | ------- | ----------------------------------------------- |
| `series1` | dynamic | A dynamic array of numeric values (minuend). |
| `series2` | dynamic | A dynamic array of numeric values (subtrahend). |
### Returns [#returns]
A dynamic array where each element is the result of subtracting the corresponding element of `series2` from `series1`. If the arrays have different lengths, the shorter array is extended with `null` values.
## Use case examples [#use-case-examples]
In log analysis, you can use `series_subtract` to calculate the difference between current and baseline request durations, helping identify performance degradations.
**Query**
```kusto
['sample-http-logs']
| summarize current = make_list(req_duration_ms) by ['geo.city']
| extend baseline = dynamic([50, 55, 48, 52, 49])
| extend delta = series_subtract(current, baseline)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20current%20%3D%20make_list\(req_duration_ms\)%20by%20%5B'geo.city'%5D%20%7C%20extend%20baseline%20%3D%20dynamic\(%5B50%2C%2055%2C%2048%2C%2052%2C%2049%5D\)%20%7C%20extend%20delta%20%3D%20series_subtract\(current%2C%20baseline\)%20%7C%20take%205%22%7D)
**Output**
| geo.city | current | baseline | delta |
| -------- | --------------------- | --------------------- | --------------------- |
| Seattle | \[60, 65, 58, 62, 59] | \[50, 55, 48, 52, 49] | \[10, 10, 10, 10, 10] |
| Portland | \[45, 50, 43, 47, 44] | \[50, 55, 48, 52, 49] | \[-5, -5, -5, -5, -5] |
This query calculates the difference between current request durations and baseline values, showing performance changes per city.
In OpenTelemetry traces, you can use `series_subtract` to compare span durations between different service versions or time periods.
**Query**
```kusto
['otel-demo-traces']
| extend duration_ms = duration / 1ms
| summarize current = make_list(duration_ms) by ['service.name']
| extend previous = dynamic([100, 120, 95, 110, 105])
| extend improvement = series_subtract(previous, current)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20extend%20duration_ms%20%3D%20duration%20%2F%201ms%20%7C%20summarize%20current%20%3D%20make_list\(duration_ms\)%20by%20%5B'service.name'%5D%20%7C%20extend%20previous%20%3D%20dynamic\(%5B100%2C%20120%2C%2095%2C%20110%2C%20105%5D\)%20%7C%20extend%20improvement%20%3D%20series_subtract\(previous%2C%20current\)%20%7C%20take%205%22%7D)
**Output**
| service.name | current | previous | improvement |
| ------------ | -------------------------- | ------------------------- | ------------------------- |
| frontend | \[80, 95, 75, 90, 85] | \[100, 120, 95, 110, 105] | \[20, 25, 20, 20, 20] |
| checkout | \[110, 125, 105, 120, 115] | \[100, 120, 95, 110, 105] | \[-10, -5, -10, -10, -10] |
This query compares current span durations with previous measurements, calculating performance improvements (positive values) or degradations (negative values).
In security logs, you can use `series_subtract` to detect anomalous behavior by comparing request patterns against expected baselines.
**Query**
```kusto
['sample-http-logs']
| summarize observed = make_list(req_duration_ms) by status
| extend expected = dynamic([45, 50, 48, 49, 47])
| extend anomaly_score = series_subtract(observed, expected)
| take 5
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20observed%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20expected%20%3D%20dynamic\(%5B45%2C%2050%2C%2048%2C%2049%2C%2047%5D\)%20%7C%20extend%20anomaly_score%20%3D%20series_subtract\(observed%2C%20expected\)%20%7C%20take%205%22%7D)
**Output**
| status | observed | expected | anomaly\_score |
| ------ | -------------------------- | --------------------- | -------------------------- |
| 200 | \[46, 51, 49, 50, 48] | \[45, 50, 48, 49, 47] | \[1, 1, 1, 1, 1] |
| 500 | \[145, 150, 148, 149, 147] | \[45, 50, 48, 49, 47] | \[100, 100, 100, 100, 100] |
This query calculates anomaly scores by comparing observed request durations against expected baselines, with large positive values indicating potential issues.
## List of related functions [#list-of-related-functions]
* [series\_multiply](/apl/scalar-functions/time-series/series-multiply): Performs element-wise multiplication of two series. Use when you need to multiply rather than subtract.
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns the absolute value of each element. Use after subtraction to get magnitude of differences.
* [series\_stats](/apl/scalar-functions/time-series/series-stats): Returns statistical summary of a series. Use to analyze the result of subtraction operations.
* [series\_sign](/apl/scalar-functions/time-series/series-sign): Returns the sign of each element. Use after subtraction to determine direction of changes.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use the `eval` command with the subtraction operator to calculate differences between fields. In APL, `series_subtract` operates on entire arrays at once, performing element-wise subtraction efficiently.
```sql Splunk example
... | eval difference=value1 - value2
```
```kusto APL equivalent
datatable(series1: dynamic, series2: dynamic)
[
dynamic([10, 20, 30]), dynamic([5, 8, 12])
]
| extend difference = series_subtract(series1, series2)
```
In SQL, you subtract values using the `-` operator on individual columns. In APL, `series_subtract` performs element-wise subtraction across entire arrays stored in single columns.
```sql SQL example
SELECT value1 - value2 AS difference
FROM measurements;
```
```kusto APL equivalent
datatable(series1: dynamic, series2: dynamic)
[
dynamic([10, 20, 30]), dynamic([5, 8, 12])
]
| extend difference = series_subtract(series1, series2)
```
---
# series_sum
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-sum
The `series_sum` function in APL calculates the total of all numeric elements in a dynamic array. You use it when you have a series of values and you want to condense them into a single aggregate number. For example, if you create arrays of request durations or span times, `series_sum` lets you quickly compute the total across each series.
This function is useful in scenarios such as:
* Aggregating request latencies across sessions or users.
* Summing the duration of spans in distributed traces.
* Calculating total counts or values across arrays in security log analysis.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_sum(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | --------------- | ----------------------------------- |
| `array` | dynamic (array) | The array of numeric values to sum. |
### Returns [#returns]
A `real` value representing the sum of all numeric elements in the array. If the array is empty, the function returns `0`.
## Use case examples [#use-case-examples]
When you want to calculate the total request duration per user across multiple requests.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend total_duration = series_sum(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20total_duration%20%3D%20series_sum\(durations\)%22%7D)
**Output**
| id | durations | total\_duration |
| ---- | --------------- | --------------- |
| u123 | \[120, 300, 50] | 470 |
| u456 | \[200, 150] | 350 |
This query collects request durations for each user, creates an array, and then sums the array to compute the total request time per user.
When you want to compute the total span duration per service within a trace.
**Query**
```kusto
['otel-demo-traces']
| summarize durations = make_list(duration) by ['service.name'], trace_id
| extend total_span_duration = series_sum(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(duration\)%20by%20%5B'service.name'%5D%2C%20trace_id%20%7C%20extend%20total_span_duration%20%3D%20series_sum\(durations\)%22%7D)
**Output**
| service.name | trace\_id | durations | total\_span\_duration |
| --------------- | --------- | ----------------------------- | --------------------- |
| frontend | t123 | \[00:00:01.2000000, 00:00:02] | 00:00:03.2000000 |
| checkoutservice | t456 | \[00:00:00.5000000] | 00:00:00.5000000 |
This query groups spans by service and trace, collects the span durations, and computes the total execution time of spans.
When you want to evaluate the total request duration for each HTTP status code.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by status
| extend total_duration = series_sum(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20status%20%7C%20extend%20total_duration%20%3D%20series_sum\(durations\)%22%7D)
**Output**
| status | durations | total\_duration |
| ------ | --------------- | --------------- |
| 200 | \[100, 300, 50] | 450 |
| 500 | \[250, 400] | 650 |
This query aggregates request durations for each HTTP status code, then sums them to provide insight into the total duration of successful vs. failed requests.
## List of related functions [#list-of-related-functions]
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns the absolute value of each element in an array. Use it to normalize negative values in arrays.
* [series\_acos](/apl/scalar-functions/time-series/series-acos): Computes the arccosine of each element in an array. Use when you want the inverse cosine.
* [series\_atan](/apl/scalar-functions/time-series/series-atan): Computes the arctangent of each element in an array. Use when you want the inverse tangent.
* [series\_cos](/apl/scalar-functions/time-series/series-cos): Returns the cosine of each element in an array. Use it when analyzing cyclical data with a phase shift.
* [series\_tan](/apl/scalar-functions/time-series/series-tan): Returns the tangent of each element in an array. Use it when you want to transform arrays with tangent-based periodicity.
## Other query languages [#other-query-languages]
In Splunk SPL, you typically use the `eval` command with `mvsum` to sum values in a multivalue field. In APL, you use `series_sum` for the same purpose. Both functions collapse an array into a single scalar value.
```sql Splunk example
... | eval total_duration = mvsum(req_duration_ms)
```
```kusto APL equivalent
['sample-http-logs']
| extend total_duration = series_sum(pack_array(req_duration_ms))
```
In SQL, you normally use `SUM()` as an aggregate over rows. If you want to sum elements inside an array, you must use functions such as `UNNEST` first. In APL, `series_sum` directly operates on dynamic arrays, so you don’t need to flatten them.
```sql SQL example
SELECT user_id, SUM(val) AS total
FROM my_table, UNNEST(values) AS val
GROUP BY user_id
```
```kusto APL equivalent
['sample-http-logs']
| summarize total = series_sum(pack_array(req_duration_ms)) by id
```
---
# series_tan
Source: https://axiom.co/docs/apl/scalar-functions/time-series/series-tan
The `series_tan` function computes the tangent of each numeric element in a dynamic array. You use it when you work with time series or other array-based datasets and want to transform values using the trigonometric tangent. For example, you can convert request durations, latency values, or span durations into their tangent values for mathematical modeling, anomaly detection, or visualization.
This function is useful when you want to:
* Apply trigonometric transformations to time series data.
* Prepare data for advanced mathematical or statistical analysis.
* Detect periodic or angular patterns in logs, traces, or security events.
## Usage [#usage]
### Syntax [#syntax]
```kusto
series_tan(array)
```
### Parameters [#parameters]
| Parameter | Type | Description |
| --------- | --------------------------------- | ------------------------------------------------------------- |
| `array` | dynamic (array of numeric values) | The array of numeric values to apply the tangent function to. |
### Returns [#returns]
A dynamic array where each element is the tangent of the corresponding input element.
## Use case examples [#use-case-examples]
You want to analyze request durations and apply a trigonometric transformation to highlight periodic anomalies.
**Query**
```kusto
['sample-http-logs']
| summarize durations = make_list(req_duration_ms) by id
| extend tan_series = series_tan(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20id%20%7C%20extend%20tan_series%20%3D%20series_tan\(durations\)%22%7D)
**Output**
| id | durations | tan\_series |
| ---- | ---------------- | --------------------- |
| A123 | \[100, 200, 300] | \[1.56, -2.19, -0.14] |
| B456 | \[150, 250, 350] | \[14.10, -0.75, 0.51] |
This query groups request durations by user ID, transforms them into arrays, and applies the tangent function element-wise.
You want to transform span durations for mathematical modeling of system behavior.
**Query**
```kusto
['otel-demo-traces']
| summarize spans = make_list(duration) by ['service.name']
| extend tan_series = series_tan(spans)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20summarize%20spans%20%3D%20make_list\(duration\)%20by%20%5B'service.name'%5D%20%7C%20extend%20tan_series%20%3D%20series_tan\(spans\)%22%7D)
**Output**
| service.name | spans | tan\_series |
| ------------ | --------------------- | ------------------- |
| frontend | \[50ms, 100ms, 150ms] | \[0.05, 0.10, 0.15] |
| cartservice | \[75ms, 125ms, 175ms] | \[0.07, 0.13, 0.18] |
This query collects span durations for each service, builds arrays, and applies tangent transformation for further modeling.
You want to detect anomalies in request durations for failed HTTP requests by applying the tangent transformation.
**Query**
```kusto
['sample-http-logs']
| where status != '200'
| summarize durations = make_list(req_duration_ms) by geo.country
| extend tan_series = series_tan(durations)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20!%3D%20'200'%20%7C%20summarize%20durations%20%3D%20make_list\(req_duration_ms\)%20by%20%5B'geo.country'%5D%20%7C%20extend%20tan_series%20%3D%20series_tan\(durations\)%22%7D)
**Output**
| geo.country | durations | tan\_series |
| ----------- | ---------------- | -------------------- |
| US | \[120, 220, 320] | \[2.57, -1.37, 0.73] |
| UK | \[140, 240, 340] | \[9.96, -0.46, 0.28] |
This query filters failed requests, groups their durations by country, and applies tangent transformation to detect anomalies.
## List of related functions [#list-of-related-functions]
* [series\_abs](/apl/scalar-functions/time-series/series-abs): Returns the absolute value of each element in an array. Use it to normalize negative values in arrays.
* [series\_acos](/apl/scalar-functions/time-series/series-acos): Computes the arccosine of each element in an array. Use when you want the inverse cosine.
* [series\_atan](/apl/scalar-functions/time-series/series-atan): Computes the arctangent of each element in an array. Use when you want the inverse tangent.
* [series\_cos](/apl/scalar-functions/time-series/series-cos): Returns the cosine of each element in an array. Use it when analyzing cyclical data with a phase shift.
* [series\_sin](/apl/scalar-functions/time-series/series-sin): Returns the sine of each element in an array. Use it when analyzing cyclical data with a phase shift.
## Other query languages [#other-query-languages]
Splunk SPL doesn’t include a direct `tan` function for arrays. In SPL, you often use `eval` with `tan()` on scalar values. In APL, `series_tan` applies the tangent function element-wise to an entire array, which makes it better suited for time series or dynamic arrays.
```sql Splunk example
... | eval tan_val=tan(duration)
```
```kusto APL equivalent
['sample-http-logs']
| extend series = pack_array(req_duration_ms, req_duration_ms*2, req_duration_ms*3)
| extend tan_series = series_tan(series)
```
In ANSI SQL, you use the `TAN()` function on scalar values, but there is no native array type or array-wide trigonometric function. In APL, `series_tan` applies `tan` to each array element, which makes it easy to work with time series data.
```sql SQL example
SELECT TAN(duration) AS tan_val
FROM otel_demo_traces;
```
```kusto APL equivalent
['otel-demo-traces']
| extend series = pack_array(duration, duration*2, duration*3)
| extend tan_series = series_tan(series)
```
---
# in
Source: https://axiom.co/docs/apl/scalar-operators/in-operators/in-operator
The `in` operator in APL filters records based on whether a value matches any element in a specified set using case-sensitive comparison. Use this operator to check if a field value equals one of several values, which is more concise and efficient than chaining multiple equality checks with `or`. The `in` operator works with any scalar type, including strings, numbers, booleans, datetime values, and dynamic arrays.
Use the `in` operator when you need exact case-sensitive matching against multiple values, such as filtering logs by specific status codes, identifying requests from particular regions, or isolating traces from a subset of services.
## Usage [#usage]
### Syntax [#syntax]
```kusto
Expression in (Value1, Value2, ...)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---------- | ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Expression | scalar | Yes | The value to find in the given set. |
| Value | scalar or tabular | Yes | The values to compare against the expression. Specify individual scalar values, a dynamic array, or a subquery. When using a subquery with multiple columns, APL uses the first column. The operator supports up to 1,000,000 unique values in the set. |
### Returns [#returns]
Returns `true` if the expression value is found in the specified set. Returns `false` otherwise.
## Use case examples [#use-case-examples]
Filter HTTP logs to find requests with successful status codes.
**Query**
```kusto
['sample-http-logs']
| where status in ('200', '201', '204')
| project _time, method, uri, status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20in%20\('200'%2C%20'201'%2C%20'204'\)%20%7C%20project%20_time%2C%20method%2C%20uri%2C%20status%22%7D)
**Output**
| \_time | method | uri | status |
| ------------------- | ------ | ---------- | ------ |
| 2024-10-17 10:15:00 | GET | /api/users | 200 |
| 2024-10-17 10:16:30 | POST | /api/data | 201 |
| 2024-10-17 10:17:45 | DELETE | /api/item | 204 |
This query filters the HTTP logs to return only requests that resulted in successful status codes (200, 201, or 204), helping you focus on completed requests.
Identify traces from specific services in your microservices architecture.
**Query**
```kusto
['otel-demo-traces']
| where ['service.name'] in ('frontend', 'checkout', 'cart')
| project _time, trace_id, ['service.name'], kind, duration
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20%5B'service.name'%5D%20in%20\('frontend'%2C%20'checkout'%2C%20'cart'\)%20%7C%20project%20_time%2C%20trace_id%2C%20%5B'service.name'%5D%2C%20kind%2C%20duration%22%7D)
**Output**
| \_time | trace\_id | service.name | kind | duration |
| ------------------- | --------- | ------------ | ------ | -------- |
| 2024-10-17 11:00:00 | abc123 | frontend | server | 45ms |
| 2024-10-17 11:00:05 | def456 | checkout | server | 120ms |
| 2024-10-17 11:00:10 | ghi789 | cart | client | 30ms |
This query filters traces to show only spans from the frontend, checkout, and cart services, helping you analyze traffic flow through critical user-facing services.
## Use with dynamic arrays [#use-with-dynamic-arrays]
When you pass a dynamic array with nested arrays, APL flattens them into a single list. For instance, `x in (dynamic([1, [2, 3]]))` is equivalent to `x in (1, 2, 3)`.
```kusto
let methods = dynamic(['GET', 'POST']);
['sample-http-logs']
| where method in (methods)
```
## List of related operators [#list-of-related-operators]
* [!in](/apl/scalar-operators/in-operators/not-in-operator): Use for case-sensitive matching to exclude values. Returns `true` if the value isn't in the set.
* [in\~](/apl/scalar-operators/in-operators/in-tilde-operator): Use for case-insensitive matching. Matches values regardless of case.
* [!in\~](/apl/scalar-operators/in-operators/not-in-tilde-operator): Use for case-insensitive exclusion. Excludes values regardless of case.
* [where](/apl/tabular-operators/where-operator): Use to filter rows based on conditions. The `in` operator is commonly used within `where` clauses.
* [has\_any](/apl/scalar-operators/string-operators): Use for term matching against multiple values. Unlike `in` which checks for exact equality, `has_any` checks if a string contains any of the specified terms.
## Other query languages [#other-query-languages]
In Splunk SPL, you use the `IN` function within a `search` or `where` command to check if a field value matches any value in a list. APL's `in` operator works similarly but uses a different syntax with parentheses around the set of values. The APL `in` operator is case-sensitive by default.
```sql Splunk example
index=web_logs | where method IN ("GET", "POST", "PUT")
```
```kusto APL equivalent
['sample-http-logs']
| where method in ('GET', 'POST', 'PUT')
```
In ANSI SQL, you use the `IN` operator within a `WHERE` clause to filter rows where a column value matches any value in a list. APL's `in` operator behaves the same way but is case-sensitive for string comparisons.
```sql SQL example
SELECT * FROM sample_http_logs WHERE method IN ('GET', 'POST', 'PUT')
```
```kusto APL equivalent
['sample-http-logs']
| where method in ('GET', 'POST', 'PUT')
```
---
# in~
Source: https://axiom.co/docs/apl/scalar-operators/in-operators/in-tilde-operator
The `in~` operator in APL filters records based on whether a value matches any element in a specified set using case-insensitive comparison. Use this operator to check if a field value equals one of several values regardless of letter case, which is more concise and efficient than chaining multiple equality checks with `or`. The `in~` operator works with any scalar type, including strings, numbers, booleans, datetime values, and dynamic arrays.
Use the `in~` operator when you need case-insensitive matching against multiple values, such as when filtering logs where the case of values might vary (for example, HTTP methods that could be 'GET', 'get', or 'Get').
## Usage [#usage]
### Syntax [#syntax]
```kusto
Expression in~ (Value1, Value2, ...)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---------- | ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Expression | scalar | Yes | The value to find in the given set, ignoring letter case. |
| Value | scalar or tabular | Yes | The values to compare against the expression. Specify individual scalar values, a dynamic array, or a subquery. When using a subquery with multiple columns, APL uses the first column. The operator supports up to 1,000,000 unique values in the set. |
### Returns [#returns]
Returns `true` if the expression value matches any value in the specified set (case-insensitive). Returns `false` otherwise.
## Use case examples [#use-case-examples]
Filter HTTP logs by method regardless of case.
**Query**
```kusto
['sample-http-logs']
| where method in~ ('get', 'post')
| project _time, method, uri, status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20method%20in~%20\('get'%2C%20'post'\)%20%7C%20project%20_time%2C%20method%2C%20uri%2C%20status%22%7D)
**Output**
| \_time | method | uri | status |
| ------------------- | ------ | ---------- | ------ |
| 2024-10-17 10:15:00 | GET | /api/users | 200 |
| 2024-10-17 10:16:30 | Post | /api/data | 201 |
| 2024-10-17 10:17:45 | get | /api/items | 200 |
This query filters the HTTP logs to return requests with GET or POST methods, regardless of how the method is capitalized in the data.
Identify traces by span kind regardless of case variations.
**Query**
```kusto
['otel-demo-traces']
| where kind in~ ('server', 'client')
| project _time, trace_id, ['service.name'], kind, duration
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20kind%20in~%20\('server'%2C%20'client'\)%20%7C%20project%20_time%2C%20trace_id%2C%20%5B'service.name'%5D%2C%20kind%2C%20duration%22%7D)
**Output**
| \_time | trace\_id | service.name | kind | duration |
| ------------------- | --------- | ------------ | ------ | -------- |
| 2024-10-17 11:00:00 | abc123 | frontend | Server | 45ms |
| 2024-10-17 11:00:05 | def456 | checkout | CLIENT | 120ms |
| 2024-10-17 11:00:10 | ghi789 | cart | server | 30ms |
This query filters traces to show spans with server or client kind, regardless of case, helping you analyze external-facing operations.
## Performance considerations [#performance-considerations]
When two operators perform the same task, use the case-sensitive one (`in`) for better performance. Use `in~` only when case-insensitive matching is necessary.
## Use with dynamic arrays [#use-with-dynamic-arrays]
When you pass a dynamic array with nested arrays, APL flattens them into a single list. For instance, `x in~ (dynamic(['a', ['b', 'c']]))` is equivalent to `x in~ ('a', 'b', 'c')`.
```kusto
let methods = dynamic(['get', 'post']);
['sample-http-logs']
| where method in~ (methods)
```
## List of related operators [#list-of-related-operators]
* [in](/apl/scalar-operators/in-operators/in-operator): Use for case-sensitive matching to include values. Better performance than `in~`.
* [!in](/apl/scalar-operators/in-operators/not-in-operator): Use for case-sensitive exclusion. Returns `true` if the value isn't in the set.
* [!in\~](/apl/scalar-operators/in-operators/not-in-tilde-operator): Use for case-insensitive exclusion. Excludes values regardless of case.
* [where](/apl/tabular-operators/where-operator): Use to filter rows based on conditions. The `in~` operator is commonly used within `where` clauses.
* [=\~](/apl/scalar-operators/string-operators): Use for single value case-insensitive equality checks. Use `in~` when checking against multiple values.
## Other query languages [#other-query-languages]
In Splunk SPL, string comparisons are case-insensitive by default. APL requires the explicit `in~` operator for case-insensitive matching. Use `in~` when you want to match values regardless of case.
```sql Splunk example
index=web_logs | where method IN ("get", "post", "put")
```
```kusto APL equivalent
['sample-http-logs']
| where method in~ ('get', 'post', 'put')
```
In ANSI SQL, the `IN` operator's case sensitivity depends on the database collation. APL's `in~` operator explicitly performs case-insensitive matching, similar to SQL databases with case-insensitive collation.
```sql SQL example
SELECT * FROM sample_http_logs WHERE LOWER(method) IN ('get', 'post', 'put')
```
```kusto APL equivalent
['sample-http-logs']
| where method in~ ('get', 'post', 'put')
```
---
# !in
Source: https://axiom.co/docs/apl/scalar-operators/in-operators/not-in-operator
The `!in` operator in APL filters records based on whether a value doesn't match any element in a specified set using case-sensitive comparison. Use this operator to exclude records where a field value equals one of several values, which is more concise and efficient than chaining multiple inequality checks with `and`. The `!in` operator works with any scalar type, including strings, numbers, booleans, datetime values, and dynamic arrays.
Use the `!in` operator when you need to exclude specific values with exact case-sensitive matching, such as filtering out known good status codes, excluding requests from specific regions, or removing traces from certain services.
## Usage [#usage]
### Syntax [#syntax]
```kusto
Expression !in (Value1, Value2, ...)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---------- | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Expression | scalar | Yes | The value to check against the exclusion set. |
| Value | scalar or tabular | Yes | The values to exclude. Specify individual scalar values, a dynamic array, or a subquery. When using a subquery with multiple columns, APL uses the first column. The operator supports up to 1,000,000 unique values in the set. |
### Returns [#returns]
Returns `true` if the expression value isn't found in the specified set. Returns `false` otherwise.
## Use case examples [#use-case-examples]
Filter HTTP logs to exclude successful responses and focus on potential issues.
**Query**
```kusto
['sample-http-logs']
| where status !in ('200', '201', '204')
| project _time, method, uri, status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20status%20!in%20\('200'%2C%20'201'%2C%20'204'\)%20%7C%20project%20_time%2C%20method%2C%20uri%2C%20status%22%7D)
**Output**
| \_time | method | uri | status |
| ------------------- | ------ | -------------- | ------ |
| 2024-10-17 10:20:00 | GET | /api/missing | 404 |
| 2024-10-17 10:25:30 | POST | /api/data | 500 |
| 2024-10-17 10:30:45 | GET | /api/forbidden | 403 |
This query filters the HTTP logs to return only requests that did not result in successful status codes, helping you identify errors and issues.
Exclude traces from infrastructure services to focus on application-level spans.
**Query**
```kusto
['otel-demo-traces']
| where ['service.name'] !in ('load-generator', 'flagd', 'frontendproxy')
| project _time, trace_id, ['service.name'], kind, duration
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20%5B'service.name'%5D%20!in%20\('load-generator'%2C%20'flagd'%2C%20'frontendproxy'\)%20%7C%20project%20_time%2C%20trace_id%2C%20%5B'service.name'%5D%2C%20kind%2C%20duration%22%7D)
**Output**
| \_time | trace\_id | service.name | kind | duration |
| ------------------- | --------- | --------------- | ------ | -------- |
| 2024-10-17 11:00:00 | abc123 | frontend | server | 45ms |
| 2024-10-17 11:00:05 | def456 | checkout | server | 120ms |
| 2024-10-17 11:00:10 | ghi789 | product-catalog | client | 30ms |
This query filters traces to exclude infrastructure and support services, helping you analyze only the core application services.
## Use with dynamic arrays [#use-with-dynamic-arrays]
When you pass a dynamic array with nested arrays, APL flattens them into a single list. For instance, `x !in (dynamic([1, [2, 3]]))` is equivalent to `x !in (1, 2, 3)`.
```kusto
let error_codes = dynamic(['500', '502', '503', '504']);
['sample-http-logs']
| where status !in (error_codes)
```
## List of related operators [#list-of-related-operators]
* [in](/apl/scalar-operators/in-operators/in-operator): Use for case-sensitive matching to include values. Returns `true` if the value is in the set.
* [in\~](/apl/scalar-operators/in-operators/in-tilde-operator): Use for case-insensitive matching. Matches values regardless of case.
* [!in\~](/apl/scalar-operators/in-operators/not-in-tilde-operator): Use for case-insensitive exclusion. Excludes values regardless of case.
* [where](/apl/tabular-operators/where-operator): Use to filter rows based on conditions. The `!in` operator is commonly used within `where` clauses.
* [!=](/apl/scalar-operators/string-operators): Use for single value inequality checks. Use `!in` when checking against multiple values for more concise queries.
## Other query languages [#other-query-languages]
In Splunk SPL, you negate the `IN` function using `NOT` to exclude values. APL's `!in` operator provides a more concise syntax for the same operation and is case-sensitive by default.
```sql Splunk example
index=web_logs | where NOT method IN ("OPTIONS", "HEAD", "TRACE")
```
```kusto APL equivalent
['sample-http-logs']
| where method !in ('OPTIONS', 'HEAD', 'TRACE')
```
In ANSI SQL, you use `NOT IN` within a `WHERE` clause to exclude rows where a column value matches any value in a list. APL's `!in` operator behaves the same way but is case-sensitive for string comparisons.
```sql SQL example
SELECT * FROM sample_http_logs WHERE method NOT IN ('OPTIONS', 'HEAD', 'TRACE')
```
```kusto APL equivalent
['sample-http-logs']
| where method !in ('OPTIONS', 'HEAD', 'TRACE')
```
---
# !in~
Source: https://axiom.co/docs/apl/scalar-operators/in-operators/not-in-tilde-operator
The `!in~` operator in APL filters records based on whether a value doesn't match any element in a specified set using case-insensitive comparison. Use this operator to exclude records where a field value equals one of several values regardless of letter case, which is more concise and efficient than chaining multiple inequality checks with `and`. The `!in~` operator works with any scalar type, including strings, numbers, booleans, datetime values, and dynamic arrays.
Use the `!in~` operator when you need to exclude specific values with case-insensitive matching, such as filtering out known HTTP methods or status codes regardless of their capitalization in the data.
## Usage [#usage]
### Syntax [#syntax]
```kusto
Expression !in~ (Value1, Value2, ...)
```
### Parameters [#parameters]
| Name | Type | Required | Description |
| ---------- | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Expression | scalar | Yes | The value to check against the exclusion set, ignoring letter case. |
| Value | scalar or tabular | Yes | The values to exclude. Specify individual scalar values, a dynamic array, or a subquery. When using a subquery with multiple columns, APL uses the first column. The operator supports up to 1,000,000 unique values in the set. |
### Returns [#returns]
Returns `true` if the expression value doesn't match any value in the specified set (case-insensitive). Returns `false` otherwise.
## Use case examples [#use-case-examples]
Filter HTTP logs to exclude certain methods regardless of case.
**Query**
```kusto
['sample-http-logs']
| where method !in~ ('options', 'head')
| project _time, method, uri, status
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'sample-http-logs'%5D%20%7C%20where%20method%20!in~%20\('options'%2C%20'head'\)%20%7C%20project%20_time%2C%20method%2C%20uri%2C%20status%22%7D)
**Output**
| \_time | method | uri | status |
| ------------------- | ------ | ---------- | ------ |
| 2024-10-17 10:15:00 | GET | /api/users | 200 |
| 2024-10-17 10:16:30 | POST | /api/data | 201 |
| 2024-10-17 10:17:45 | DELETE | /api/item | 204 |
This query filters the HTTP logs to exclude OPTIONS and HEAD requests regardless of case, helping you focus on substantive requests.
Exclude internal span kinds regardless of case variations.
**Query**
```kusto
['otel-demo-traces']
| where kind !in~ ('internal', 'producer', 'consumer')
| project _time, trace_id, ['service.name'], kind, duration
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22%5B'otel-demo-traces'%5D%20%7C%20where%20kind%20!in~%20\('internal'%2C%20'producer'%2C%20'consumer'\)%20%7C%20project%20_time%2C%20trace_id%2C%20%5B'service.name'%5D%2C%20kind%2C%20duration%22%7D)
**Output**
| \_time | trace\_id | service.name | kind | duration |
| ------------------- | --------- | ------------ | ------ | -------- |
| 2024-10-17 11:00:00 | abc123 | frontend | server | 45ms |
| 2024-10-17 11:00:05 | def456 | checkout | client | 120ms |
| 2024-10-17 11:00:10 | ghi789 | cart | Server | 30ms |
This query filters traces to exclude internal and messaging spans, helping you focus on client-server interactions.
## Performance considerations [#performance-considerations]
When two operators perform the same task, use the case-sensitive one (`!in`) for better performance. Use `!in~` only when case-insensitive exclusion is necessary.
## Use with dynamic arrays [#use-with-dynamic-arrays]
When you pass a dynamic array with nested arrays, APL flattens them into a single list. For instance, `x !in~ (dynamic(['a', ['b', 'c']]))` is equivalent to `x !in~ ('a', 'b', 'c')`.
```kusto
let excluded_methods = dynamic(['options', 'head', 'trace']);
['sample-http-logs']
| where method !in~ (excluded_methods)
```
## List of related operators [#list-of-related-operators]
* [in](/apl/scalar-operators/in-operators/in-operator): Use for case-sensitive matching to include values.
* [!in](/apl/scalar-operators/in-operators/not-in-operator): Use for case-sensitive exclusion. Better performance than `!in~`.
* [in\~](/apl/scalar-operators/in-operators/in-tilde-operator): Use for case-insensitive matching to include values.
* [where](/apl/tabular-operators/where-operator): Use to filter rows based on conditions. The `!in~` operator is commonly used within `where` clauses.
* [!\~](/apl/scalar-operators/string-operators): Use for single value case-insensitive inequality checks. Use `!in~` when checking against multiple values.
## Other query languages [#other-query-languages]
In Splunk SPL, string comparisons are case-insensitive by default. APL requires the explicit `!in~` operator for case-insensitive exclusion. Use `!in~` when you want to exclude values regardless of case.
```sql Splunk example
index=web_logs | where NOT method IN ("options", "head")
```
```kusto APL equivalent
['sample-http-logs']
| where method !in~ ('options', 'head')
```
In ANSI SQL, the `NOT IN` operator's case sensitivity depends on the database collation. APL's `!in~` operator explicitly performs case-insensitive exclusion, similar to SQL databases with case-insensitive collation.
```sql SQL example
SELECT * FROM sample_http_logs WHERE LOWER(method) NOT IN ('options', 'head')
```
```kusto APL equivalent
['sample-http-logs']
| where method !in~ ('options', 'head')
```
---
# Set membership operators
Source: https://axiom.co/docs/apl/scalar-operators/in-operators/overview
The table summarizes the set membership operators available in APL. These operators filter records based on whether a value matches any element in a specified set.
| Name | Description | Case-sensitive |
| ----------------------------------------------------------------- | -------------------------------------------------------------- | -------------- |
| [in](/apl/scalar-operators/in-operators/in-operator) | Returns `true` if the value equals any of the elements. | Yes |
| [!in](/apl/scalar-operators/in-operators/not-in-operator) | Returns `true` if the value doesn't equal any of the elements. | Yes |
| [in\~](/apl/scalar-operators/in-operators/in-tilde-operator) | Returns `true` if the value equals any of the elements. | No |
| [!in\~](/apl/scalar-operators/in-operators/not-in-tilde-operator) | Returns `true` if the value doesn't equal any of the elements. | No |