# Concepts
Source: https://axiom.co/docs/ai-engineering/concepts
Learn about the core concepts in AI engineering: Capabilities, Collections, Evals, Scorers, Annotations, and User Feedback.
This page defines the core terms used in the AI engineering workflow. Understanding these concepts is the first step toward building robust and reliable generative AI capabilities.
## AI engineering lifecycle
The concepts in AI engineering are best understood within the context of the development lifecycle. While AI capabilities can become highly sophisticated, they typically start simple and evolve through a disciplined, iterative process:
Development starts by defining a task and prototyping a capability with a prompt to solve it.
The prototype is then tested against a collection of reference examples (so called "ground truth") to measure its quality and effectiveness using scorers. This process is known as an eval.
Once a capability meets quality benchmarks, it's deployed. In production, scorers can be applied to live traffic (online evals) to monitor performance and cost in real-time.
Insights from production monitoring reveal edge cases and opportunities for improvement. These new examples are used to refine the capability, expand the ground truth collection, and begin the cycle anew.
## AI engineering terms
### Capability
A generative AI capability is a system that uses large language models to perform a specific task by transforming inputs into desired outputs.
Capabilities exist on a spectrum of complexity, ranging from simple to sophisticated architectures:
* **Single-turn model interactions**: A single prompt and response, such as classifying a support ticket's intent or summarizing a document.
* **Workflows**: Multi-step processes where each step's output feeds into the next, such as research → analysis → report generation.
* **Single-agent**: An agent that can reasons and make decisions to accomplish a goal, such as a customer support agent that can search documentation, check order status, and draft responses.
* **Multi-agent**: Multiple specialized agents collaborating to solve complex problems, such as software engineering through architectural planning, coding, testing, and review.
### Collection
A collection is a curated set of reference records used for development, testing, and evaluation of a capability. Collections serve as the test cases for prompt engineering.
### Collection record
Collection records are the individual input-output pairs within a collection. Each record consists of an input and its corresponding expected output (ground truth).
### Ground truth
Ground truth is the validated, expert-approved correct output for a given input. It represents the gold standard that the AI capability should aspire to match.
### Scorer
A scorer is a function that evaluates a capability's output, returning a score that indicates quality or correctness. There are two types of scorers: reference-based scorers and reference-free scorers.
#### Reference-based scorer
A reference-based scorer depends on an expected value (ground truth) to evaluate a capability's output. It compares the generated output against domain expert knowledge to determine correctness or similarity.
Examples include:
* **Exact match scorer**: Checks if the output exactly matches the expected value.
* **Similarity scorer**: Measures how similar the output is to the expected value when an exact match isn't required.
Reference-based scorers require domain expert knowledge and can be used in offline evaluation and backtesting (if historical traces have been reviewed by domain experts). They can't be used in online evaluation because live production data lacks expected values.
#### Reference-free scorer
A reference-free scorer evaluates a capability's output without needing an expected value. It uses an LLM or other criteria to assess output quality based on general standards rather than comparison to ground truth.
Examples include:
* **Toxicity scorer**: Uses an LLM to assess whether the output is offensive, harmful, or could upset the recipient.
* **Coherence scorer**: Evaluates whether the output is logically consistent and well-structured.
* **Relevance scorer**: Assesses whether the output appropriately addresses the input query.
Reference-free scorers can be used in all evaluation contexts: offline evaluation, online evaluation, and backtesting. They're the only type of scorer available for online evaluation because live production data hasn't been reviewed by domain experts.
### Evaluation or "eval"
An evaluation, or eval, is the process of testing a capability's performance using one or more scorers. There are three ways to run evaluations: offline evaluation, online evaluation, and backtesting.
#### Offline evaluation
Offline evaluation is the process of testing a capability against a curated set of test cases (collection records) before deployment. You curate what good looks like with domain expert knowledge and provide expected values for your test cases.
Offline evaluation can use both reference-based scorers (which compare output to expected values) and reference-free scorers (which assess output quality without needing expected values).
#### Online evaluation
Online evaluation is the process of applying scorers to a capability's live production traffic in real-time, just after conversations or interactions finish. Online evaluation runs on a sample of traces to provide feedback on performance degradation, cost, and quality drift.
Because online evaluation happens on live data without domain expert review, it can only use reference-free scorers. Reference-based scorers require expected values that don't exist for live production traffic.
#### Backtesting
Backtesting is a form of evaluation that runs a capability against historical production traces from a specified time period. Unlike online evaluation which runs in real-time as conversations happen, backtesting performs batch evaluation over many stored conversations at once. This allows you to compare how a new version of your capability performs on previous real-world conversations compared to the original version.
Backtesting can always use reference-free scorers. It can also use reference-based scorers if domain experts have reviewed the historical traces and provided ground truth expected values.
### Flag
A flag is a configuration parameter that controls how your AI capability behaves. Flags let you parameterize aspects like model choice, tool availability, prompting strategies, or retrieval approaches. By defining flags, you can run experiments to compare different configurations and systematically determine which approach performs best.
### Experiment
An experiment is an evaluation run with a specific set of flag values. By running multiple experiments with different flag configurations, you can compare performance across different models, prompts, or strategies to find the optimal setup for your capability.
### Reviews
Reviews are expert-provided observations, labels, or corrections added to production traces or evaluation results. Domain experts review AI capability runs and document what went wrong, what should have happened differently, or categorize failure modes. These reviews help identify patterns in capability failures, validate scorer accuracy, and create new test cases for collections.
### User feedback
User feedback is direct signal from end users about AI capability performance, typically collected through ratings (thumbs up/down, stars) or text comments. Feedback events are associated with traces to provide context about both system behavior and user perception. Aggregated feedback reveals quality trends, helps prioritize improvements, and surfaces issues that might not appear in evaluations.
## What's next?
Now that you understand the core concepts, get started with the [Quickstart](/ai-engineering/quickstart) or dive into [Evaluate](/ai-engineering/evaluate/overview) to learn about systematic testing.
# Create
Source: https://axiom.co/docs/ai-engineering/create
Build AI capabilities using any framework, with best support for TypeScript-based tools.
Building an AI capability starts with prototyping. You can use whichever framework you prefer. Axiom is focused on helping you evaluate and observe your capabilities rather than prescribing how to build them.
TypeScript-based frameworks like Vercel’s [AI SDK](https://sdk.vercel.ai) do integrate most seamlessly with Axiom’s tooling today, but that’s likely to evolve over time.
## Build your capability
Define your capability using your framework of choice. Here’s an example using Vercel's [AI SDK](https://ai-sdk.dev/), which includes [many examples](https://sdk.vercel.ai/examples) covering different capability design patterns. Popular alternatives like [Mastra](https://mastra.ai) also exist.
```ts src/lib/capabilities/classify-ticket.ts expandable theme={null}
import { generateObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { wrapAISDKModel } from 'axiom/ai';
import { z } from 'zod';
export async function classifyTicket(input: {
subject?: string;
content: string
}) {
const result = await generateObject({
model: wrapAISDKModel(openai('gpt-4o-mini')),
messages: [
{
role: 'system',
content: 'Classify support tickets as: question, bug_report, or feature_request.',
},
{
role: 'user',
content: input.subject
? `Subject: ${input.subject}\n\n${input.content}`
: input.content,
},
],
schema: z.object({
category: z.enum(['question', 'bug_report', 'feature_request']),
confidence: z.number().min(0).max(1),
}),
});
return result.object;
}
```
The `wrapAISDKModel` function instruments your model calls for Axiom’s observability features. Learn more in the [Observe](/ai-engineering/observe) section.
## Gather reference examples
As you prototype, collect examples of inputs and their correct outputs.
```ts theme={null}
const referenceExamples = [
{
input: {
subject: 'How do I reset my password?',
content: 'I forgot my password and need help.'
},
expected: { category: 'question' },
},
{
input: {
subject: 'App crashes on startup',
content: 'The app immediately crashes when I open it.'
},
expected: { category: 'bug_report' },
},
];
```
These become your ground truth for evaluation. Learn more in the [Evaluate](/ai-engineering/evaluate/overview) section.
## Structured prompt management
The features below are experimental. Axiom’s current focus is on the evaluation and observability stages of the AI engineering workflow.
For teams wanting more structure around prompt definitions, Axiom’s SDK includes experimental utilities for managing prompts as versioned objects.
### Define prompts as objects
Represent capabilities as structured `Prompt` objects:
```ts src/prompts/ticket-classifier.prompt.ts theme={null}
import {
experimental_Type,
type experimental_Prompt
} from 'axiom/ai';
export const ticketClassifierPrompt = {
name: "Ticket Classifier",
slug: "ticket-classifier",
version: "1.0.0",
model: "gpt-4o-mini",
messages: [
{
role: "system",
content: "Classify support tickets as: {{ categories }}",
},
{
role: "user",
content: "{{ ticket_content }}",
},
],
arguments: {
categories: experimental_Type.String(),
ticket_content: experimental_Type.String(),
},
} satisfies experimental_Prompt;
```
### Type-safe arguments
The `experimental_Type` system provides type safety for prompt arguments:
```ts theme={null}
arguments: {
user: experimental_Type.Object({
name: experimental_Type.String(),
preferences: experimental_Type.Array(experimental_Type.String()),
}),
priority: experimental_Type.Union([
experimental_Type.Literal("high"),
experimental_Type.Literal("medium"),
experimental_Type.Literal("low"),
]),
}
```
### Local testing
Test prompts locally before using them:
```ts theme={null}
import { experimental_parse } from 'axiom/ai';
const parsed = await experimental_parse(ticketClassifierPrompt, {
context: {
categories: 'question, bug_report, feature_request',
ticket_content: 'How do I reset my password?',
},
});
console.log(parsed.messages);
```
These utilities help organize prompts in your codebase. Centralized prompt management and versioning features may be added in future releases.
## What's next?
Once you have a working capability and reference examples, systematically evaluate its performance.
To learn how to set up and run evaluations, see [Evaluate](/ai-engineering/evaluate/overview).
# Analyze results
Source: https://axiom.co/docs/ai-engineering/evaluate/analyze-results
Understand how changes to your AI capabilities impact performance, cost, and quality.
The Axiom Console displays results from evaluations. After you run `axiom eval` from the CLI, offline evaluation results appear in the Console and the CLI provides a link to view them. For example:
```
your-eval-name (your-eval.eval.ts)
• scorer-one 95.00%
• scorer-two 87.50%
• scorer-three 100.00%
View full report:
https://app.axiom.co/:org-id/ai-engineering/evaluations?runId=:run-id
Test Files 1 passed (1)
Tests 4 passed (4)
Duration 5.2 s
```
The evaluation interface helps you answer three core questions:
1. How well does this configuration perform?
2. How does it compare to previous versions?
3. Which tradeoffs are acceptable?
## Compare configurations
To understand the impact of changes, compare evaluation runs to see deltas in accuracy, latency, and cost.
### Using the Console
Run your evaluation before and after making changes, then compare both runs in the Axiom Console:
```bash theme={null}
# Run baseline
axiom eval your-eval-name
# Make changes to your capability (update prompt, switch models, etc.)
# Run again
axiom eval your-eval-name
```
The Console shows both runs where you can analyze differences side-by-side.
### Using the baseline flag
For direct CLI comparison, specify a baseline evaluation ID:
```bash theme={null}
# Run baseline and note the trace ID from the output
axiom eval your-eval-name
# Make changes, then run with baseline
axiom eval your-eval-name --baseline
```
The CLI output will show deltas for each metric.
The `--baseline` flag expects a trace ID. After running an evaluation, copy the trace ID from the CLI output or Console URL to use as a baseline for comparison.
Example: Switching from `gpt-4o-mini` to `gpt-4o` might show:
* Accuracy: 85% → 95% (+10%)
* Latency: 800 ms → 1.6 s (+100%)
* Cost per run: $0.002 → $0.020 (+900%)
This data helps you decide whether the quality improvement justifies the cost and latency increase for your use case.
## Investigate failures
When test cases fail, click into them to see:
* The exact input that triggered the failure
* What your capability output vs what was expected
* The full trace of LLM calls and tool executions
Look for patterns:
* Do failures cluster around specific input types?
* Are certain scorers failing consistently?
* Is high token usage correlated with failures?
Use these insights to add targeted test cases or refine your capability.
## Experiment with flags
Flags let you test multiple configurations systematically. Run several experiments:
```bash theme={null}
# Compare model and retrieval configurations
axiom eval --flag.model=gpt-4o-mini --flag.retrieval.topK=3
axiom eval --flag.model=gpt-4o-mini --flag.retrieval.topK=10
axiom eval --flag.model=gpt-4o --flag.retrieval.topK=3
axiom eval --flag.model=gpt-4o --flag.retrieval.topK=10
```
Compare all four runs in the Console to find the configuration that best balances quality, cost, and latency for your requirements.
## Track progress over time
For teams running evaluations regularly (nightly or in CI), the Console shows whether your capability is improving or regressing across iterations.
Compare your latest run against your initial baseline to verify that accumulated changes are moving in the right direction.
## What's next?
* To learn how to use flags for experimentation, see [Flags and experiments](/ai-engineering/evaluate/flags-experiments).
* To iterate on your capability based on evaluation results, see [Iterate](/ai-engineering/iterate).
# Flags and experiments
Source: https://axiom.co/docs/ai-engineering/evaluate/flags-experiments
Use flags to parameterize AI capabilities and run experiments comparing different configurations.
Flags are configuration parameters that control how your AI capability behaves. By defining flags, you can run experiments that systematically compare different models, prompts, retrieval strategies, or architectural approaches - all without changing your code.
This is one of Axiom’s key differentiators: type-safe, version-controlled configuration that integrates seamlessly with your evaluation workflow.
This page covers flags and experiments in offline evaluations. In [online evaluations](/ai-engineering/evaluate/online-evaluations/write-run-evaluations), you can't use flags because each request is a unique real-world input.
## Why flags matter
AI capabilities have many tunable parameters: which model to use, which tools to enable, which prompting strategy, how to structure retrieval, and more. Without flags, you’d need to:
* Hard-code values and manually change them between tests
* Maintain multiple versions of the same code
* Lose track of which configuration produced which results
* Struggle to reproduce experiments
Flags solve this by:
* **Parameterizing behavior**: Define what can vary in your capability
* **Enabling experimentation**: Test multiple configurations systematically
* **Tracking results**: Axiom records which flag values produced which scores
* **Automating optimization**: Run experiments in CI/CD to find the best configuration
## Set up flags
Flags are defined using [Zod](https://zod.dev/) schemas in an "app scope" file. This provides type safety and ensures flag values are validated at runtime.
### Create app scope
Create a file to define your flags (typically `src/lib/app-scope.ts`):
```ts src/lib/app-scope.ts theme={null}
import { createAppScope } from 'axiom/ai';
import { z } from 'zod';
export const flagSchema = z.object({
// Flags for ticket classification capability
ticketClassification: z.object({
model: z.string().default('gpt-4o-mini'),
systemPrompt: z.enum(['concise', 'detailed']).default('concise'),
useStructuredOutput: z.boolean().default(true),
}),
// Flags for document summarization capability
summarization: z.object({
model: z.string().default('gpt-4o'),
maxTokens: z.number().default(500),
style: z.enum(['bullet-points', 'paragraph']).default('bullet-points'),
}),
});
const { flag, pickFlags } = createAppScope({ flagSchema });
export { flag, pickFlags };
```
### Use flags in your capability
Reference flags in your capability code using the `flag()` function:
```ts src/lib/capabilities/classify-ticket/prompts.ts theme={null}
import { generateObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { wrapAISDKModel } from 'axiom/ai';
import { flag } from '../../app-scope';
import { z } from 'zod';
const systemPrompts = {
concise: 'Classify tickets briefly as: spam, question, feature_request, or bug_report.',
detailed: `You are an expert customer support engineer. Carefully analyze each ticket
and classify it as spam, question, feature_request, or bug_report. Consider context and intent.`,
};
export async function classifyTicket(input: { subject?: string; content: string }) {
// Get flag values
const model = flag('ticketClassification.model');
const promptStyle = flag('ticketClassification.systemPrompt');
const useStructured = flag('ticketClassification.useStructuredOutput');
const result = await generateObject({
model: wrapAISDKModel(openai(model)),
messages: [
{
role: 'system',
content: systemPrompts[promptStyle],
},
{
role: 'user',
content: input.subject
? `Subject: ${input.subject}\n\n${input.content}`
: input.content,
},
],
schema: z.object({
category: z.enum(['spam', 'question', 'feature_request', 'bug_report']),
}),
});
return result.object;
}
```
### Declare flags in evaluations
Tell your evaluation which flags it depends on using `pickFlags()`. This provides two key benefits:
* **Documentation**: Makes flag dependencies explicit and visible
* **Validation**: Warns about undeclared flag usage, catching configuration drift early
```ts src/lib/capabilities/classify-ticket/evaluations/spam-classification.eval.ts theme={null}
import { Eval, Scorer } from 'axiom/ai/evals';
import { pickFlags } from '../../../app-scope';
import { classifyTicket } from '../prompts';
Eval('spam-classification', {
// Declare which flags this eval uses
configFlags: pickFlags('ticketClassification'),
capability: 'classify-ticket',
data: [/* test cases */],
task: async ({ input }) => await classifyTicket(input),
scorers: [/* scorering functions */],
});
```
## Run experiments
With flags defined, you can run experiments by overriding flag values at runtime.
### CLI flag overrides
Override individual flags directly in the command:
```bash theme={null}
# Test with GPT-4o instead of the default
axiom eval --flag.ticketClassification.model=gpt-4o
# Test with different prompt style
axiom eval --flag.ticketClassification.systemPrompt=detailed
# Test multiple flags
axiom eval \
--flag.ticketClassification.model=gpt-4o \
--flag.ticketClassification.systemPrompt=detailed \
--flag.ticketClassification.useStructuredOutput=false
```
### JSON configuration files
For complex experiments, define flag overrides in JSON files:
```json experiments/gpt4-detailed.json theme={null}
{
"ticketClassification": {
"model": "gpt-4o",
"systemPrompt": "detailed",
"useStructuredOutput": true
}
}
```
```json experiments/gpt4-mini-concise.json theme={null}
{
"ticketClassification": {
"model": "gpt-4o-mini",
"systemPrompt": "concise",
"useStructuredOutput": false
}
}
```
Run evaluations with these configurations:
```bash theme={null}
# Run with first configuration
axiom eval --flags-config=experiments/gpt4-detailed.json
# Run with second configuration
axiom eval --flags-config=experiments/gpt4-mini-concise.json
```
Store experiment configurations in version control. This makes it easy to reproduce results and track which experiments you've tried.
### Compare experiments
Run the same evaluation with different flag values to compare approaches:
```bash theme={null}
# Baseline: default flags (gpt-4o-mini, concise, structured output)
axiom eval spam-classification
# Experiment 1: Try GPT-4o
axiom eval spam-classification --flag.ticketClassification.model=gpt-4o
# Experiment 2: Use detailed prompting
axiom eval spam-classification --flag.ticketClassification.systemPrompt=detailed
# Experiment 3: Test without structured output
axiom eval spam-classification --flag.ticketClassification.useStructuredOutput=false
```
Axiom tracks all these runs in the Console, making it easy to compare scores and identify the best configuration.
## Best practices
### Organize flags by capability
Group related flags together to make them easier to manage:
```ts theme={null}
export const flagSchema = z.object({
// One group per capability
ticketClassification: z.object({
model: z.string().default('gpt-4o-mini'),
temperature: z.number().default(0.7),
}),
emailGeneration: z.object({
model: z.string().default('gpt-4o'),
tone: z.enum(['formal', 'casual']).default('formal'),
}),
documentRetrieval: z.object({
topK: z.number().default(5),
similarityThreshold: z.number().default(0.7),
}),
});
```
### Set sensible defaults
Choose defaults that work well for most cases. Experiments then test variations:
```ts theme={null}
ticketClassification: z.object({
model: z.enum(['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo']).default('gpt-4o-mini'),
systemPrompt: z.enum(['concise', 'detailed']).default('concise'),
useStructuredOutput: z.boolean().default(true),
}),
```
For evaluations that test your application code, it’s best to use the same defaults as your production configuration.
### Use enums for discrete choices
When flags have a fixed set of valid values, use enums for type safety:
```ts theme={null}
// Good: type-safe, prevents invalid values
model: z.enum(['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo']).default('gpt-4o-mini'),
tone: z.enum(['formal', 'casual', 'friendly']).default('formal'),
// Avoid: any string is valid, causes runtime errors with AI SDK
model: z.string().default('gpt-4o-mini'),
tone: z.string().default('formal'),
```
## Advanced patterns
### Model comparison matrix
Test your capability across multiple models systematically:
```bash theme={null}
# Create experiment configs for each model
echo '{"ticketClassification":{"model":"gpt-4o-mini"}}' > exp-mini.json
echo '{"ticketClassification":{"model":"gpt-4o"}}' > exp-4o.json
echo '{"ticketClassification":{"model":"gpt-4-turbo"}}' > exp-turbo.json
# Run all experiments
axiom eval --flags-config=exp-mini.json
axiom eval --flags-config=exp-4o.json
axiom eval --flags-config=exp-turbo.json
```
### Prompt strategy testing
Compare different prompting approaches:
```ts theme={null}
export const flagSchema = z.object({
summarization: z.object({
strategy: z.enum([
'chain-of-thought',
'few-shot',
'zero-shot',
'structured-output',
]).default('zero-shot'),
}),
});
```
```bash theme={null}
# Test each strategy
for strategy in chain-of-thought few-shot zero-shot structured-output; do
axiom eval --flag.summarization.strategy=$strategy
done
```
### Cost vs quality optimization
Find the sweet spot between performance and cost:
```json experiments/cost-quality-matrix.json theme={null}
[
{ "model": "gpt-4o-mini", "temperature": 0.7 },
{ "model": "gpt-4o-mini", "temperature": 0.3 },
{ "model": "gpt-4o", "temperature": 0.7 },
{ "model": "gpt-4o", "temperature": 0.3 }
]
```
Run experiments and compare cost (from telemetry) against accuracy scores to find the optimal configuration.
### CI/CD integration
Run experiments automatically in your CI pipeline:
```yaml .github/workflows/eval.yml theme={null}
name: Run Evaluations
on: [pull_request]
jobs:
eval:
runs-on: ubuntu-latest
strategy:
matrix:
model: [gpt-4o-mini, gpt-4o]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm install
- run: |
npx axiom eval \
--flag.ticketClassification.model=${{ matrix.model }}
env:
AXIOM_TOKEN: ${{ secrets.AXIOM_TOKEN }}
AXIOM_DATASET: ${{ secrets.AXIOM_DATASET }}
```
This automatically tests your capability with different configurations on every pull request.
## What's next?
* To learn all CLI commands for running evaluations, see [Run evaluations](/ai-engineering/evaluate/run-evaluations).
* To view results in the Console and compare experiments, see [Analyze results](/ai-engineering/evaluate/analyze-results).
# Handle non-deterministic outputs
Source: https://axiom.co/docs/ai-engineering/evaluate/handling-non-determinism
Use trials and scorer aggregations to get statistically meaningful results from non-deterministic LLM outputs.
LLM outputs are inherently non-deterministic. The same input can produce different outputs across runs. This variability can make evaluation results noisy and hard to interpret.
Trials and scorer aggregations help you get statistically meaningful results by running each test case multiple times and combining scores in ways that match your evaluation goals.
This page covers handling non-determinism in offline evaluations. In [online evaluations](/ai-engineering/evaluate/online-evaluations/write-run-evaluations), production traffic volume provides natural statistical significance, and you don't need trials because each request is a unique real-world input.
## When to use trials
Use trials when:
* **Measuring consistency**: You want to know how reliably your capability produces correct outputs, not just whether it can.
* **Reducing noise**: A single run might fail due to random variation, masking an otherwise good capability.
* **Testing robustness**: You want to verify that your capability performs well across multiple attempts.
For simple deterministic checks or when evaluation time is critical, a single run (the default) is sufficient.
## Configure trials
Add the `trials` parameter to your evaluation to run each case multiple times:
```ts theme={null}
import { Eval, Scorer } from 'axiom/ai/evals';
Eval('classify-intent', {
trials: 3, // Run each case 3 times
data: [
{ input: 'How do I reset my password?', expected: 'account' },
{ input: 'The app crashes on startup', expected: 'bug' },
],
task: async ({ input }) => classifyIntent(input),
scorers: [ExactMatch],
});
```
Each case runs independently `trials` times. Scores from each trial are then aggregated according to the scorer's aggregation strategy (Mean by default).
## Aggregation strategies
Aggregations control how individual trial scores combine into a final score. Import them from `axiom/ai/scorers/aggregations`:
```ts theme={null}
import { Mean, Median, PassAtK, PassHatK } from 'axiom/ai/scorers/aggregations';
```
### Mean (default)
Computes the arithmetic mean of all trial scores. Use this when you care about average performance.
```ts theme={null}
const AccuracyScorer = Scorer(
'accuracy',
({ output, expected }) => output === expected ? 1 : 0,
{ aggregation: Mean() }
);
// Trials: [1, 0, 1] → Score: 0.67
```
### Median
Returns the middle value of trial scores. Use this when you want to reduce the impact of outliers.
```ts theme={null}
const LatencyScorer = Scorer(
'latency-acceptable',
({ output }) => output.latencyMs < 1000 ? 1 : 0,
{ aggregation: Median() }
);
// Trials: [0, 1, 1] → Score: 1
```
### PassAtK (pass\@k)
Returns 1 if at least one trial meets the threshold, 0 otherwise. Use this when success on any attempt counts as a pass—common for generative tasks where multiple valid outputs exist.
```ts theme={null}
const ToolCalledScorer = Scorer(
'tool-called',
({ output }) => output.toolCalls.length > 0 ? 1 : 0,
{ aggregation: PassAtK({ threshold: 1 }) }
);
// Trials: [0, 1, 0] → Score: 1 (at least one passed)
```
### PassHatK (pass^k)
Returns 1 if all trials meet the threshold, 0 otherwise. Use this when you need consistent, reliable behavior across every attempt.
```ts theme={null}
const ConsistencyScorer = Scorer(
'consistent-output',
({ output, expected }) => output.category === expected.category ? 1 : 0,
{ aggregation: PassHatK({ threshold: 1 }) }
);
// Trials: [1, 1, 0] → Score: 0 (not all passed)
```
### User-friendly aliases
For readability, you can use these aliases:
* `AtLeastOneTrialPasses` — alias for `PassAtK`
* `AllTrialsPass` — alias for `PassHatK`
```ts theme={null}
import { AtLeastOneTrialPasses, AllTrialsPass } from 'axiom/ai/scorers/aggregations';
```
### Custom aggregations
Create your own aggregation by returning an object with a `type` string and an `aggregate` function:
```ts theme={null}
import type { Aggregation } from 'axiom/ai/scorers/aggregations';
const Min = (): Aggregation<'min'> => ({
type: 'min',
aggregate: (scores: number[]) =>
scores.length === 0 ? 0 : Math.min(...scores),
});
const WorstCaseScorer = Scorer(
'worst-case',
({ output, expected }) => output === expected ? 1 : 0,
{ aggregation: Min() }
);
```
## Complete example
This example runs each case 5 times with different aggregation strategies for different scorers:
```ts theme={null}
import { Eval, Scorer } from 'axiom/ai/evals';
import { Mean, PassAtK, PassHatK } from 'axiom/ai/scorers/aggregations';
import { classifyTicket } from './classify-ticket';
// Average accuracy across trials
const CategoryMatch = Scorer(
'category-match',
({ output, expected }) => output.category === expected.category ? 1 : 0,
{ aggregation: Mean() }
);
// Pass if the model ever gets it right
const CanClassify = Scorer(
'can-classify',
({ output, expected }) => output.category === expected.category ? 1 : 0,
{ aggregation: PassAtK({ threshold: 1 }) }
);
// Pass only if the model is consistent
const AlwaysCorrect = Scorer(
'always-correct',
({ output, expected }) => output.category === expected.category ? 1 : 0,
{ aggregation: PassHatK({ threshold: 1 }) }
);
Eval('ticket-classification-reliability', {
trials: 5,
data: [
{ input: { content: 'App crashes on startup' }, expected: { category: 'bug' } },
{ input: { content: 'How do I export data?' }, expected: { category: 'question' } },
],
task: async ({ input }) => classifyTicket(input),
scorers: [CategoryMatch, CanClassify, AlwaysCorrect],
});
```
With 5 trials producing scores `[1, 1, 0, 1, 1]` for a single case:
| Scorer | Aggregation | Result |
| ---------------- | ----------- | ------ |
| `category-match` | Mean | 0.8 |
| `can-classify` | PassAtK | 1 |
| `always-correct` | PassHatK | 0 |
## Builder API
You can also configure trials using the builder pattern:
```ts theme={null}
import { createEvalBuilder } from 'axiom/ai/evals';
createEvalBuilder('classify-intent', {
data: testCases,
task: classifyIntent,
scorers: [ExactMatch],
})
.withTrials(3)
.run();
```
## Best practices
### Choose trials based on variability
More variable capabilities need more trials. Start with 3-5 trials for typical LLM tasks. For highly variable outputs (creative generation, complex reasoning), consider 10+.
### Match aggregation to intent
* Use **Mean** for general accuracy metrics
* Use **PassAtK** for "can it ever do this?" questions
* Use **PassHatK** for "is it reliable?" questions
### Consider evaluation time
Trials multiply execution time. For a 10-case evaluation with 5 trials, you're running 50 task executions. Balance statistical confidence against practical constraints.
### Combine with flags
Use trials alongside [flags and experiments](/ai-engineering/evaluate/flags-experiments) to compare configurations with statistical rigor:
```bash theme={null}
axiom eval --flag.model=gpt-4o
axiom eval --flag.model=gpt-4o-mini
```
Comparing mean scores across trials gives more reliable signals than single-run comparisons.
## What's next?
* To run evaluations and compare results, see [Run evaluations](/ai-engineering/evaluate/run-evaluations).
* To analyze trial-level data in the Console, see [Analyze results](/ai-engineering/evaluate/analyze-results).
# Analyze results
Source: https://axiom.co/docs/ai-engineering/evaluate/online-evaluations/analyze-results
Monitor production quality, investigate score drops, and feed insights back to offline evaluations.
The Axiom Console displays results from evaluations. Online evaluation results stream in continuously from production traffic scored by `onlineEval`.
Each `onlineEval` call creates spans tagged with `eval.tags: ["online"]` that appear alongside your production traces in the Console. The evaluation interface helps you answer three core questions about production quality:
1. How well is your capability performing on real traffic?
2. Is quality improving or regressing across deployments?
3. Which production edge cases need attention?
## Find online evaluation scores
Online evaluation scores appear in the Console as spans linked to the originating generation span. To view only online evaluation results, filter spans by `eval.tags: ["online"]`.
For details on all emitted attributes, see [Scorers](/ai-engineering/evaluate/scorers#telemetry).
## Monitor score trends
Use online evaluation scores to track production quality over time. Look for:
* **Score drops** that correlate with deployments, prompt changes, or upstream API updates.
* **Scorer disagreement** where heuristic scorers pass but LLM judges flag quality issues, or vice versa. This often reveals that a heuristic is too coarse or that a judge prompt needs refinement.
* **Sampling gaps** where low sampling rates on expensive scorers leave blind spots in coverage. If a scorer runs on only 1% of traffic, a brief quality regression could go undetected.
For teams running online evaluations across multiple capabilities, compare score distributions between capabilities to identify which areas of your product need the most attention.
## Investigate score drops
When a scorer's average drops, click into the failing spans to see:
* The exact input that triggered the low score
* What your capability returned
* The full trace of LLM calls and tool executions that produced the output
* Any metadata the scorer attached to explain the result
Look for patterns:
* Do failures cluster around specific input types or user segments?
* Are certain scorers failing consistently while others pass?
* Did a deployment or model update coincide with the drop?
* Is high token usage or latency correlated with lower scores?
Use these insights to prioritize fixes and add targeted test cases to your [offline evaluations](/ai-engineering/evaluate/write-evaluations).
## Compare across deployments
Because online evaluations run continuously, you can compare score distributions before and after a deployment to understand its impact on production quality.
1. Note the timestamp of your deployment.
2. In the Console, filter online evaluation spans to a time window before the deployment and note the average scores.
3. Compare against the same time window after the deployment.
Example: Switching from `gpt-4o-mini` to `gpt-4o` in production might show:
* Relevance score: 0.82 → 0.94
* Format score: 0.97 → 0.95
* Latency: 800 ms → 1.6 s
This data helps you decide whether the quality improvement justifies the cost and latency increase for your use case.
For controlled experiments where you test changes against a fixed collection of test cases before deploying, use [offline evaluations](/ai-engineering/evaluate/analyze-results). Online evaluations complement offline evaluations by confirming that improvements hold on real traffic.
## Adjust sampling based on results
Review your sampling rates periodically as your system matures:
* **Increase sampling** on scorers that have recently detected regressions. More data points help you understand the scope and severity of an issue.
* **Decrease sampling** on scorers that consistently pass. A scorer at 100% pass rate over weeks of traffic may not need to run on every request.
* **Add conditional sampling** for scorers that should focus on specific traffic segments. For example, sample more aggressively on long inputs where your capability is more likely to struggle.
For details on configuring sampling, see [Sampling](/ai-engineering/evaluate/online-evaluations/write-run-evaluations#sampling).
## Feed insights back to offline evaluations
Online evaluations often surface edge cases that your offline test collections don't cover. When you spot a pattern of failures in production:
1. Add the failing inputs to your offline collection as new test cases with expected outputs.
2. Run offline evaluations to reproduce the issue and verify your fix.
3. Deploy the fix and confirm that online evaluation scores recover.
This feedback loop between online and offline evaluations is where the two approaches reinforce each other. Online evaluations catch problems you didn't anticipate; offline evaluations let you systematically fix and prevent them from recurring.
## What's next?
* Set up [user feedback](/ai-engineering/observe/user-feedback) for human-in-the-loop signals that complement automated scoring.
* Write [offline evaluations](/ai-engineering/evaluate/write-evaluations) to test against known-good answers before shipping.
* To iterate on your capability based on evaluation results, see [Iterate](/ai-engineering/iterate).
# Write and run online evaluations
Source: https://axiom.co/docs/ai-engineering/evaluate/online-evaluations/write-run-evaluations
Score AI outputs on live production traffic using lightweight scorers and sampling.
Online evaluations let you score your AI capability's outputs on live production traffic. Unlike [offline evaluations](/ai-engineering/evaluate/overview) that run against a fixed collection of test cases with expected values, online evaluations are reference-free.
Use online evaluations to monitor quality in production: catch format regressions, run heuristic checks, or sample traffic for LLM-as-judge scoring without affecting your capability's response.
Online evaluations never throw errors into your app's code. Scorer failures are recorded on the eval span as OTel events, so a broken scorer won't affect your capability's response.
## Prerequisites
* Follow the procedure in [Quickstart](/ai-engineering/quickstart) to set up Axiom AI SDK in your TypeScript project.
* Wrap your AI model with `wrapAISDKModel` for automatic tracing. See [Instrumentation with Axiom AI SDK](/ai-engineering/observe/axiom-ai-sdk-instrumentation) for details.
## Import evaluation functions
Import `onlineEval` and `Scorer` from the Axiom AI SDK and add `onlineEval` to the `withSpan` callback:
```ts theme={null}
import { withSpan } from 'axiom/ai';
import { onlineEval } from 'axiom/ai/evals/online';
import { Scorer } from 'axiom/ai/scorers';
import { generateText } from 'ai';
import { gpt4oMini } from './lib/model'; // Your wrapped model (see prerequisites)
const formatScorer = Scorer('format', ({ output }: { output: string }) => {
const trimmed = output.trim();
return /[.!?]$/.test(trimmed) && !trimmed.includes('\n') && trimmed.length <= 200;
});
const result = await withSpan({ capability: 'demo', step: 'generate' }, async () => {
const response = await generateText({
model: gpt4oMini,
messages: [{ role: 'user', content: prompt }],
});
// Fire-and-forget — doesn't block the response
void onlineEval('generate-format', {
capability: 'demo',
step: 'generate',
output: response.text,
scorers: [formatScorer],
});
return response.text;
});
```
## Write scorers
Online evaluations use the same `Scorer` API as [offline evaluations](/ai-engineering/evaluate/write-evaluations). The key difference is that online scorers are reference-free: they receive `input` and `output` but no `expected` value. For the full `Scorer` API reference including return types, patterns, and LLM-as-judge examples, see [Scorers](/ai-engineering/evaluate/scorers).
Here's a quick example of an online scorer that validates output format:
```ts theme={null}
const isKnownCategory = Scorer(
'is-known-category',
({ output }: { output: string }) => {
return ['support', 'complaint', 'spam', 'unknown'].includes(output);
},
);
```
## Sampling
Use sampling to control the percentage of production traffic that gets evaluated. You can set different sampling for each scorer. This is useful for expensive scorers like LLM judges while letting cheap heuristic scorers run on every request.
Wrap a scorer in `{ scorer, sampling }` to control the percentage of production traffic it evaluates. You can mix sampled and unsampled scorers in the same call. Scorers without a `sampling` wrapper run on every request.
| `sampling` value | Behavior |
| ----------------- | ----------------------------------------------------------------------------------- |
| not set (default) | Evaluate every request |
| `0.5` | Evaluate \~50% of requests |
| `0.1` | Evaluate \~10% of requests |
| `0.0` | Never evaluate. The scorer is skipped and its key is omitted from the result record |
```ts theme={null}
void onlineEval('categorize-message', {
capability: 'support-agent',
step: 'categorize-message',
input: userMessage,
output: result,
scorers: [
// Wrap each scorer with its own sampling rate
{ scorer: validCategoryScorer, sampling: 0.1 }, // Evaluate 10% of traffic
formatConfidenceScorer // Evaluate every request
],
});
```
Additionally, you can set the `sampling` value to a synchronous or asynchronous function that receives `{ input, output }` and returns a Boolean (or `Promise`) for conditional sampling logic. This is useful when the sampling decision depends on an async lookup such as a feature flag service.
## Connect to traces
Online evaluations create OTel spans that link back to the originating generation span. The linking mechanism depends on where you call `onlineEval`.
### Inside `withSpan` (recommended)
When called inside `withSpan`, the active span is automatically detected and linked. The eval span becomes a child of the `withSpan` span.
```ts theme={null}
await withSpan({ capability: 'qa', step: 'answer' }, async () => {
const response = await generateText({ model, messages });
void onlineEval('answer-format', {
capability: 'qa',
step: 'answer',
output: response.text,
scorers: [formatScorer],
});
return response.text;
});
```
### Deferred evaluation
For cases where you want to evaluate after `withSpan` returns, capture `span.spanContext()` and pass it as `links`:
```ts theme={null}
import type { SpanContext } from '@opentelemetry/api';
let originCtx: SpanContext;
const result = await withSpan(
{ capability: 'demo', step: 'answer' },
async (span) => {
originCtx = span.spanContext();
return await generateText({ model, messages });
},
);
// Called outside withSpan — explicit link connects eval to originating span
void onlineEval('answer-relevance', {
capability: 'demo',
step: 'answer',
links: originCtx,
input: question,
output: result,
scorers: [
{ scorer: relevanceScorer, sampling: 0.5 }
],
});
```
### Awaitable for short-lived processes
In CLI tools or serverless functions, `await` the eval to ensure spans are created before flushing telemetry:
```ts theme={null}
await onlineEval('generate-format', {
capability: 'demo',
step: 'generate',
output: result,
scorers: [formatScorer],
});
await flushTelemetry(); // Your instrumentation helper — see Quickstart
```
In long-running servers, use `void onlineEval(...)` (fire-and-forget) instead — the telemetry pipeline flushes spans in the background.
## Telemetry reference
Each call to `onlineEval` creates a parent eval span with one child span per scorer.
### Span naming
| Span | Name pattern |
| ----------------- | -------------------- |
| Parent eval span | `eval {name}` |
| Scorer child span | `score {scorerName}` |
For the full list of scorer span attributes, see [Scorers: Telemetry](/ai-engineering/evaluate/scorers#telemetry).
## Complete example
This example shows a production support agent that uses online evaluations to monitor message categorization quality:
```ts expandable theme={null}
import { createOpenAI } from '@ai-sdk/openai';
import { generateText } from 'ai';
import { withSpan, wrapAISDKModel } from 'axiom/ai';
import { Scorer } from 'axiom/ai/scorers';
import { onlineEval } from 'axiom/ai/evals/online';
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY! });
const model = wrapAISDKModel(openai('gpt-4o-mini'));
// Define valid categories
const categories = ['support', 'complaint', 'wrong_company', 'spam', 'unknown'] as const;
type Category = (typeof categories)[number];
// Scorer: checks if the output is a known category
const validCategoryScorer = Scorer(
'valid-category',
({ output }: { output: Category }) => {
const isValid = categories.includes(output);
return {
score: isValid,
metadata: { category: output, validCategories: categories },
};
},
);
// Scorer: checks if output looks like a clean classification
const formatConfidenceScorer = Scorer(
'format-confidence',
({ output }: { output: Category }) => {
if (typeof output !== 'string') {
return { score: 0, metadata: { reason: 'not a string' } };
}
const trimmed = output.trim().toLowerCase();
const isSingleWord = !trimmed.includes(' ');
const isClean = /^[a-z_]+$/.test(trimmed);
return {
score: (isSingleWord ? 0.5 : 0) + (isClean ? 0.5 : 0),
metadata: { isSingleWord, isClean },
};
},
);
// Categorize a user message with online evaluation
async function categorizeMessage(userMessage: string): Promise {
return await withSpan(
{ capability: 'support-agent', step: 'categorize-message' },
async () => {
const response = await generateText({
model,
messages: [
{
role: 'system',
content: `Classify the message as: ${categories.join(', ')}. Reply with the category name only.`,
},
{ role: 'user', content: userMessage },
],
});
const result = (response.text.trim().toLowerCase() as Category) || 'unknown';
// Monitor classification quality on 10% of production traffic
void onlineEval('categorize-message', {
capability: 'support-agent',
step: 'categorize-message',
input: userMessage,
output: result,
scorers: [
{ scorer: validCategoryScorer, sampling: 0.1 },
formatConfidenceScorer
],
});
return result;
},
);
}
```
## What's next?
* Learn about the [GenAI attributes](/ai-engineering/observe/gen-ai-attributes) that your AI spans emit.
* Set up [user feedback](/ai-engineering/observe/user-feedback) for human-in-the-loop signals.
* Write [offline evaluations](/ai-engineering/evaluate/write-evaluations) to test against known-good answers before shipping.
* Use production insights to [iterate](/ai-engineering/iterate) on your capabilities.
# Evaluation overview
Source: https://axiom.co/docs/ai-engineering/evaluate/overview
Systematically measure and improve your AI capabilities through offline and online evaluation.
Evaluation is the systematic process of measuring how well your AI capability performs.
## Why systematic evaluation matters
AI systems fail in non-deterministic ways. The same prompt can produce different results. Edge cases emerge unpredictably. As capabilities grow from simple single-turn interactions to complex multi-agent systems, manual testing becomes impossible to scale.
Systematic evaluation solves this by:
* **Establishing baselines**: Measure current performance before making changes
* **Preventing regressions**: Catch quality degradation before it reaches production
* **Enabling experimentation**: Compare different models, prompts, or architectures
* **Building confidence**: Deploy changes knowing they improve aggregate performance
## Evaluation approaches
Axiom supports two complementary approaches:
* **Offline evaluations** test your capability against a curated collection of inputs with expected outputs (ground truth). Run them before deploying to catch regressions.
* **Online evaluations** score live production traffic with reference-free scorers. Run them after deploying to monitor quality continuously.
Both approaches use the same `Scorer` API. The scorers you write for one context work in the other.
### Which evaluation approach to use
Use offline evaluations when you need to test against known-good answers before shipping. Use online evaluations when you want to continuously monitor production quality. You can use both approaches together to get the best of both worlds.
| | Offline evaluations | Online evaluations |
| ------------------- | --------------------------------- | -------------------------------------- |
| **When** | Development, before deploy | Production, on live traffic |
| **Expected values** | Requires expected output per case | No ground truth needed |
| **Scorers** | Can compare output to expected | Reference-free |
| **Execution** | CLI runner with vitest | Fire-and-forget inside your app |
| **Sampling** | Runs every case | Per-scorer sampling rate |
| **Telemetry** | OTel spans in eval dataset | OTel spans linked to production traces |
## Offline evaluation workflow
Offline evaluations test your capability against a curated dataset before you deploy. Axiom's evaluation framework follows a simple pattern:
Build a set of test cases with inputs and expected outputs (ground truth). Start small with 10-20 examples and grow over time.
Write functions that compare your capability's output against the expected result. Use custom logic or prebuilt scorers from libraries like `autoevals`.
Execute your capability against the collection and score the results. Track metrics like accuracy, pass rate, and cost.
Review results in the Axiom Console. Compare against baselines. Identify failures. Make improvements and re-evaluate.
## Online evaluation workflow
Online evaluations score live production traffic continuously after you deploy. They use the same `Scorer` API as offline evaluations, but without expected values.
Create scorers that assess output quality using only the input and output without ground truth required. Use heuristic checks for format and structure, or LLM-as-judge patterns for semantic quality.
Call `onlineEval` inside your capability code to run scorers as fire-and-forget operations that don't affect your response latency.
Set per-scorer sampling rates to balance coverage and cost. Run cheap heuristic scorers on every request and expensive LLM judges on a fraction of traffic.
Review online evaluation scores in the Axiom Console alongside your production traces. Use the insights to add targeted offline test cases and refine your capability.
## What's next?
**Shared:**
* To set up your environment and authenticate, see [Quickstart](/ai-engineering/quickstart).
* To learn how to write scoring functions that work in both offline and online evaluations, see [Scorers](/ai-engineering/evaluate/scorers).
**Offline evaluations:**
* To learn how to write evaluation functions, see [Write offline evaluations](/ai-engineering/evaluate/write-evaluations).
* To understand flags and experiments, see [Flags and experiments](/ai-engineering/evaluate/flags-experiments).
* To view results in the Console, see [Analyze results](/ai-engineering/evaluate/analyze-results).
**Online evaluations:**
* To learn how to write and run online evaluation functions, see [Write and run online evaluations](/ai-engineering/evaluate/online-evaluations/write-run-evaluations).
* To view results in the Console, see [Analyze online evaluation results](/ai-engineering/evaluate/online-evaluations/analyze-results).
# Run offline evaluations
Source: https://axiom.co/docs/ai-engineering/evaluate/run-evaluations
Learn how to run offline evaluations using the Axiom CLI and interpret the results.
This page covers running offline evaluations with the Axiom AI SDK CLI. The CLI provides commands for running offline evaluations locally or in CI/CD pipelines.
Online evaluations run inline in your app code and don't use the CLI. For more information, see [Online evaluations](/ai-engineering/evaluate/online-evaluations/write-run-evaluations).
## Run offline evaluations
The simplest way to run offline evaluations is to execute all of them in your project:
```bash theme={null}
axiom eval
```
You can also target specific evaluations by name, file path, or glob pattern:
```bash theme={null}
# By evaluation name
axiom eval spam-classification
# By file path
axiom eval src/evals/spam-classification.eval.ts
# By glob pattern
axiom eval "**/*spam*.eval.ts"
```
To see which evaluations are available without running them:
```bash theme={null}
axiom eval --list
```
## Common options
For quick local testing without sending traces to Axiom, use debug mode:
```bash theme={null}
axiom eval --debug
```
To compare results against a previous evaluation, view both runs in the Axiom Console where you can analyze differences in scores, latency, and cost.
## Run experiments with flags
Flags let you test different configurations without changing code. Override flag values directly in the command:
```bash theme={null}
# Single flag
axiom eval --flag.ticketClassification.model=gpt-4o
# Multiple flags
axiom eval \
--flag.ticketClassification.model=gpt-4o \
--flag.ticketClassification.temperature=0.3
```
For complex experiments, load flag overrides from a JSON file:
```bash theme={null}
axiom eval --flags-config=experiments/gpt4.json
```
## Understand evaluation output
When you run an evaluation, the CLI shows progress, scores, and a link to view detailed results in the Axiom Console:
```
✓ spam-classification (4/4 passed)
✓ Test case 1: spam detection
✓ Test case 2: legitimate question
Scorers:
category-match: 100% (4/4)
high-confidence: 75% (3/4)
Results:
Total: 4 test cases
Passed: 4 (100%)
Duration: 3.2s
Cost: $0.0024
View full report:
https://app.axiom.co/your-org/ai-engineering/evaluations?runId=ABC123
```
Click the link to view results in the Console, compare runs, and analyze performance.
## What's next?
To learn how to view and analyze evaluation results, see [Analyze results](/ai-engineering/evaluate/analyze-results).
# Scorers
Source: https://axiom.co/docs/ai-engineering/evaluate/scorers
Write scoring functions that measure your AI capability's output quality. The same Scorer API works in both offline and online evaluations.
Scorers are functions that measure your AI capability's output. They receive the inputs and outputs of a capability run, and return a score. The same `Scorer` API works in both [offline](/ai-engineering/evaluate/write-evaluations) and [online](/ai-engineering/evaluate/online-evaluations/write-run-evaluations) evaluations.
The key difference between the two contexts is what the scorer receives:
* **Offline scorers** receive `input`, `output`, and `expected` (ground truth from your test collection).
* **Online scorers** are reference-free. They receive `input` and `output` without an `expected` value.
Because the API is the same, you can reuse scorers across both contexts. A scorer you write for offline evaluations works in online evaluations as long as it doesn't depend on `expected`.
## Create scorers
Create scorers using the `Scorer` wrapper. A scorer takes a name and a scoring function:
```ts theme={null}
import { Scorer } from 'axiom/ai/scorers';
const MyScorer = Scorer(
'my-scorer',
({ input, output }) => {
// Return a boolean, a number (0-1), or { score, metadata }
}
);
```
## Return types
Scorers can return three types of values:
### Boolean
Return `true` or `false` for simple pass/fail checks. The SDK converts booleans to `1` (pass) or `0` (fail) and marks the score as boolean in telemetry.
```ts theme={null}
const isKnownCategory = Scorer(
'is-known-category',
({ output }: { output: string }) => {
return ['support', 'complaint', 'spam', 'unknown'].includes(output);
},
);
```
### Numeric
Return a number between `0` and `1` for graded scoring:
```ts theme={null}
const formatConfidence = Scorer(
'format-confidence',
({ output }: { output: string }) => {
const trimmed = output.trim().toLowerCase();
const isSingleWord = !trimmed.includes(' ');
const isClean = /^[a-z_]+$/.test(trimmed);
return (isSingleWord ? 0.5 : 0) + (isClean ? 0.5 : 0);
},
);
```
### Score with metadata
Return an object with `score` and `metadata` to attach additional context to the eval span:
```ts theme={null}
const validCategory = Scorer(
'valid-category',
({ output }: { output: string }) => {
const validCategories = ['support', 'complaint', 'spam', 'unknown'];
return {
score: validCategories.includes(output),
metadata: {
category: output,
validCategories,
},
};
},
);
```
## Scorer patterns
### Exact match (offline)
Compare the output directly against the expected value. This pattern only works in offline evaluations where ground truth is available.
```ts theme={null}
const ExactMatchScorer = Scorer(
'exact-match',
({ output, expected }) => {
return output.sentiment === expected.sentiment ? true : false;
}
);
```
### Heuristic checks
Validate output structure or format without ground truth. These scorers work in both offline and online evaluations.
```ts theme={null}
const formatScorer = Scorer('format', ({ output }: { output: string }) => {
const trimmed = output.trim();
return /[.!?]$/.test(trimmed) && !trimmed.includes('\n') && trimmed.length <= 200;
});
```
### LLM-as-judge
Use a second model to evaluate the output. Async scorers are useful in both contexts, especially in online evaluations where you don't have ground truth and need semantic quality assessment.
```ts theme={null}
import { generateObject } from 'ai';
import { z } from 'zod';
const relevanceScorer = Scorer(
'relevance',
async ({ input, output }: { input: string; output: string }) => {
const result = await generateObject({
model: judgeModel,
schema: z.object({
relevant: z.boolean().describe('Whether the response answers the question'),
}),
system: 'You evaluate if an AI response answers the user question.',
prompt: `Question: ${input}\n\nResponse: ${output}`,
});
return result.object.relevant;
},
);
```
LLM judge scorers add latency and cost per evaluation. In online evaluations, use [sampling](/ai-engineering/evaluate/online-evaluations/write-run-evaluations#sampling) to control how often they run.
## Use `autoevals`
The [`autoevals`](https://github.com/braintrustdata/autoevals) library provides prebuilt scorers for common tasks:
```bash theme={null}
npm install autoevals
```
```ts theme={null}
import { Scorer } from 'axiom/ai/scorers';
import { Levenshtein, FactualityScorer } from 'autoevals';
const LevenshteinScorer = Scorer(
'levenshtein',
({ output, expected }) => {
return Levenshtein({ output: output.text, expected: expected.text });
}
);
const FactualityCheck = Scorer(
'factuality',
async ({ output, expected }) => {
return await FactualityScorer({
output: output.text,
expected: expected.text,
});
}
);
```
Use multiple scorers to evaluate different aspects of your capability. For example, check both exact accuracy and semantic similarity to get a complete picture of performance.
## Telemetry
Each scorer produces an OTel span with the following attributes:
| Attribute | Description |
| ----------------------- | ---------------------------------------------------------------------------------------------------------- |
| `gen_ai.operation.name` | Always `eval.score` |
| `eval.name` | The eval name |
| `eval.score.name` | The scorer name |
| `eval.score.value` | The numeric score (`0`-`1`) |
| `eval.score.metadata` | JSON string of scorer metadata. Includes `eval.score.is_boolean: true` when the scorer returned a boolean. |
| `eval.capability.name` | The capability being evaluated |
| `eval.step.name` | The step within the capability (when set) |
| `eval.tags` | `["online"]` for online evaluations |
## What's next?
* Use scorers in [offline evaluations](/ai-engineering/evaluate/write-evaluations) to test against known-good answers before shipping.
* Use scorers in [online evaluations](/ai-engineering/evaluate/online-evaluations/write-run-evaluations) to monitor production quality continuously.
# Write evaluations
Source: https://axiom.co/docs/ai-engineering/evaluate/write-evaluations
Learn how to create offline evaluation functions with collections, tasks, and scorers.
An offline evaluation is a test suite for your AI capability. It runs your capability against a collection of test cases and scores the results using scorers. This page explains how to write offline evaluation functions using Axiom's `Eval` API.
This page covers writing offline evaluations. For online evaluations, see [Online evaluations](/ai-engineering/evaluate/online-evaluations/write-run-evaluations).
## Prerequisites
* Follow the procedure in [Quickstart](/ai-engineering/quickstart) to set up Axiom AI SDK in your TypeScript project.
* Wrap your AI model with `wrapAISDKModel` for automatic tracing. See [Instrumentation with Axiom AI SDK](/ai-engineering/observe/axiom-ai-sdk-instrumentation) for details.
Instead of using environment variables explained in the [Quickstart](/ai-engineering/quickstart), you can authenticate using OAuth instead.
The Axiom AI SDK includes a CLI for authenticating and running offline evaluations. Authenticate so that evaluation runs are recorded in Axiom and attributed to your user account.
### Login
```bash theme={null}
npx axiom auth login
```
This opens your browser and prompts you to authorize the CLI with your Axiom account. Once authorized, the CLI stores your credentials locally.
### Check authentication status
```bash theme={null}
npx axiom auth status
```
### Switch organizations
If you belong to multiple Axiom organizations:
```bash theme={null}
npx axiom auth switch
```
### Logout
```bash theme={null}
npx axiom auth logout
```
## Anatomy of an offline evaluation
The `Eval` function defines a complete test suite for your capability. Here’s the basic structure:
```ts theme={null}
import { Eval, Scorer } from 'axiom/ai/evals';
Eval('evaluation-name', {
data: [/* test cases */],
task: async ({ input }) => {/* run capability */},
scorers: [/* scoring functions */],
metadata: {/* optional metadata */},
});
```
### Key parameters
* **`data`**: An array of test cases, or a function that returns an array of test cases. Each test case has an `input` (what you send to your capability) and an `expected` output (the ground truth).
* **`task`**: An async function that executes your capability for a given input and returns the output.
* **`scorers`**: An array of scorer functions that evaluate the output against the expected result.
* **`metadata`**: Optional metadata like a description or tags.
## Create collections
The `data` parameter defines your collection of test cases. Start with a small set of examples and grow it over time as you discover edge cases.
### Inline collections
For small collections, define test cases directly in the offline evaluation:
```ts theme={null}
Eval('classify-sentiment', {
data: [
{
input: { text: 'I love this product!' },
expected: { sentiment: 'positive' },
},
{
input: { text: 'This is terrible.' },
expected: { sentiment: 'negative' },
},
{
input: { text: 'It works as expected.' },
expected: { sentiment: 'neutral' },
},
],
// ... rest of eval
});
```
### External collections
For larger collections, load test cases from external files or databases:
```ts theme={null}
import { readFile } from 'fs/promises';
Eval('classify-sentiment', {
data: async () => {
const content = await readFile('./test-cases/sentiment.json', 'utf-8');
return JSON.parse(content);
},
// ... rest of eval
});
```
We recommend storing collections in version control alongside your code. This makes it easy to track how your test suite evolves and ensures evaluations are reproducible.
## Define tasks
The `task` function executes your AI capability for each test case. It receives the `input` from the test case and should return the output your capability produces.
```ts theme={null}
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { wrapAISDKModel } from 'axiom/ai';
async function classifySentiment(text: string) {
const result = await generateText({
model: wrapAISDKModel(openai('gpt-4o-mini')),
prompt: `Classify the sentiment of this text as positive, negative, or neutral: "${text}"`,
});
return { sentiment: result.text };
}
Eval('classify-sentiment', {
data: [/* ... */],
task: async ({ input }) => {
return await classifySentiment(input.text);
},
scorers: [/* ... */],
});
```
The task function should generally be the same code you use in your actual capability. This ensures your evaluations accurately reflect real-world behavior.
## Create scorers
Scorers evaluate your capability's output. In offline evaluations, scorers receive `input`, `output`, and `expected` (ground truth), and return a score. For the full `Scorer` API reference including return types, patterns, and third-party integrations, see [Scorers](/ai-engineering/evaluate/scorers).
Here's a quick example of an offline scorer that compares output to expected values:
```ts theme={null}
import { Scorer } from 'axiom/ai/scorers';
const ExactMatchScorer = Scorer(
'exact-match',
({ output, expected }) => {
return output.sentiment === expected.sentiment ? true : false;
}
);
```
## Complete example
Here's a complete evaluation for a support ticket classification system:
```ts src/lib/capabilities/classify-ticket/evaluations/spam-classification.eval.ts expandable theme={null}
import { Eval, Scorer } from 'axiom/ai/evals';
import { generateObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { wrapAISDKModel } from 'axiom/ai';
import { z } from 'zod';
// The capability function
async function classifyTicket({
subject,
content
}: {
subject?: string;
content: string
}) {
const result = await generateObject({
model: wrapAISDKModel(openai('gpt-4o-mini')),
messages: [
{
role: 'system',
content: `You are a customer support engineer. Classify tickets as:
spam, question, feature_request, or bug_report.`,
},
{
role: 'user',
content: subject ? `Subject: ${subject}\n\n${content}` : content,
},
],
schema: z.object({
category: z.enum(['spam', 'question', 'feature_request', 'bug_report']),
confidence: z.number().min(0).max(1),
}),
});
return result.object;
}
// Custom scorer for category matching
const CategoryScorer = Scorer(
'category-match',
({ output, expected }) => {
return output.category === expected.category ? true : false;
}
);
// Custom scorer for high-confidence predictions
const ConfidenceScorer = Scorer(
'high-confidence',
({ output }) => {
return output.confidence >= 0.8 ? true : false;
}
);
// Define the evaluation
Eval('spam-classification', {
data: [
{
input: {
subject: "Congratulations! You've Won!",
content: 'Claim your $500 gift card now!',
},
expected: {
category: 'spam',
},
},
{
input: {
subject: 'How do I reset my password?',
content: 'I forgot my password and need help resetting it.',
},
expected: {
category: 'question',
},
},
{
input: {
subject: 'Feature request: Dark mode',
content: 'Would love to see a dark mode option in the app.',
},
expected: {
category: 'feature_request',
},
},
{
input: {
subject: 'App crashes on startup',
content: 'The app crashes immediately when I try to open it.',
},
expected: {
category: 'bug_report',
},
},
],
task: async ({ input }) => {
return await classifyTicket(input);
},
scorers: [CategoryScorer, ConfidenceScorer],
metadata: {
description: 'Classify support tickets into categories',
},
});
```
## File naming conventions
Name your evaluation files with the `.eval.ts` extension so they're automatically discovered by the Axiom CLI:
```
src/
└── lib/
└── capabilities/
└── classify-ticket/
└── evaluations/
├── spam-classification.eval.ts
├── category-accuracy.eval.ts
└── edge-cases.eval.ts
```
The CLI will find all files matching `**/*.eval.{ts,js,mts,mjs,cts,cjs}` based on your `axiom.config.ts` configuration.
## What's next?
* To parameterize your capabilities and run experiments, see [Flags and experiments](/ai-engineering/evaluate/flags-experiments).
* To run offline evaluations using the CLI, see [Run offline evaluations](/ai-engineering/evaluate/run-evaluations).
# Track issues
Source: https://axiom.co/docs/ai-engineering/iterate/issues
Group recurring review findings into issues, inspect supporting evidence, and track whether a failure pattern still needs follow-up
## Understand how issues are created
Issues are created from the Review workflow:
* A reviewer inspects a conversation in **Review** and submits feedback.
* When the review is negative, Axiom matches it to an existing issue, If Axiom doesn't find a match, it creates a new issue and adds the reviewed conversation as supporting evidence.
* The **Issues** page collects those recurring findings so you can manage them as a shared backlog instead of reviewing each conversation in isolation.
To learn how reviewers submit feedback and trigger these issue decisions, see [Review conversations](/ai-engineering/iterate/review).
## Open issues
To open issues:
1. Navigate to **AI engineering**.
2. Click **Issues**.
3. Select a capability from the sidebar.
Issues are scoped to the selected capability.
## Find an issue
To find an issue:
1. Use the status filter to switch between **Unresolved** and **Resolved**.
2. Use search to filter the issue list.
3. Click an issue to open its details.
New issues are created from submitting reviews.
## Review issue evidence
To inspect the conversations behind an issue:
1. Open the issue from the list.
2. Review the **Reviewed conversations** section.
3. Check the reviewer, timestamp, feedback label, and excerpt.
4. Click **View trace** to open the related trace.
This view helps confirm that multiple reviews point to the same failure pattern.
## Change issue status
To change the status of an issue:
1. Open the issue.
2. Click **Mark as resolved** or **Mark as unresolved**.
Use **Resolved** when the issue no longer needs active follow-up. Use **Unresolved** to return the issue to the active list.
## Delete an issue
To delete an issue:
1. Open the issue.
2. Click the issue actions menu.
3. Click **Delete issue**.
4. Confirm the deletion.
Deleting an issue permanently removes the record.
## View the decision log
To inspect review-to-issue decisions:
1. Open **AI engineering** > **Issues**.
2. Click the page actions menu.
3. Click **View decision log**.
4. Filter the list by decision type or issue.
The decision log includes these decision types:
* **New** for reviews that created a new issue.
* **Match** for reviews mapped to an existing issue.
* **Skip** for reviews that didn't create or match an issue.
## What's next?
* Review more conversations in [Review](/ai-engineering/iterate/review).
* See [Iterate](/ai-engineering/iterate).
# Iterate overview
Source: https://axiom.co/docs/ai-engineering/iterate/overview
Run a systematic improvement loop to continuously enhance your AI capabilities based on production data and evaluation results.
The Iterate stage closes the loop in AI engineering. By analyzing production performance, validating changes through evaluation, and deploying improvements with confidence, you create a continuous cycle of data-driven enhancement.
## The improvement loop
Successful AI engineering follows a systematic pattern:
1. **Analyze production** - Identify what needs improvement
2. **Create test cases** - Turn failures into ground truth examples
3. **Experiment with changes** - Test variations using flags
4. **Validate improvements** - Run evaluations to confirm progress
5. **Deploy with confidence** - Ship changes backed by data
6. **Repeat** - New production data feeds the next iteration
## Identify what to improve
Start by understanding how your capability performs in production. The Axiom Console provides multiple signals to help you prioritize:
### Production traces
Review conversations in [Review conversations](/ai-engineering/iterate/review) to find:
* Real-world inputs that caused failures or low-quality outputs
* High-cost or high-latency interactions that need optimization
* Unexpected tool calls or reasoning paths
* Edge cases your evaluations didn’t cover
Use the flagged, recent, negative feedback, and errored queues to surface traces that need attention, then open the related trace to examine the full interaction path, including model choices, token usage, and intermediate steps.
### User feedback
User feedback provides direct signal about which interactions matter most to your customers. Axiom AI SDK includes lightweight functions to capture both explicit and implicit feedback as timestamped event data:
* **Explicit feedback** includes direct user signals like thumbs up, thumbs down, and comments on AI-generated outputs.
* **Implicit feedback** captures behavioral signals like copying generated text, regenerating responses, or abandoning interactions.
Because user feedback is stored as timestamped events linked to specific AI runs, you can easily correlate feedback with traces to understand exactly what went wrong and prioritize high-value failures over edge cases that rarely occur.
To learn how to capture user feedback, see [User feedback](/ai-engineering/observe/user-feedback).
### Domain expert review
Axiom provides a workflow for domain experts to [review production conversations](/ai-engineering/iterate/review) and [track recurring failure patterns as issues](/ai-engineering/iterate/issues).
When a reviewer submits negative feedback, Axiom automatically matches it to an existing issue or creates a new one, helping teams prioritize fixes based on real production data.
[Track issues](/ai-engineering/iterate/issues) helps teams consolidate repeated failures instead of treating each conversation in isolation. This makes it easier to prioritize fixes, inspect supporting traces, and track whether a pattern still needs follow-up.
Reviews and issues are especially useful for categorizing failure modes such as:
* **Critical failures** - Complete breakdowns like API outages, unhandled exceptions, or timeout errors
* **Quality degradation** - Declining accuracy scores, increased hallucinations, or off-topic responses
* **Coverage gaps** - Out-of-distribution inputs the system wasn’t designed to handle, like unexpected languages or domains
* **User dissatisfaction** - Negative feedback on outputs that technically succeeded but didn’t meet user needs
This structured analysis helps teams coordinate improvement efforts, prioritize which failure modes to address first, and track patterns over time.
To learn more, see [Review conversations](/ai-engineering/iterate/review) and [Track issues](/ai-engineering/iterate/issues).
## Create test cases from production
Once you've identified high-priority failures, turn them into test cases for your evaluation collections. Organizations typically maintain multiple collections for different scenarios, failure modes, or capability variants:
```ts theme={null}
const newTestCases = [
{
input: {
// Real production input that failed
subject: 'Refund request for order #12345',
content: 'I need a refund because the product arrived damaged.'
},
expected: {
category: 'refund_request',
priority: 'high'
},
},
];
```
## Experiment with changes
Use [flags](/ai-engineering/evaluate/flags-experiments) to test different approaches without changing your code:
```bash theme={null}
# Test with a more capable model
axiom eval ticket-classification --flag.model=gpt-4o
# Try a different temperature
axiom eval ticket-classification --flag.temperature=0.3
# Experiment with prompt variations
axiom eval ticket-classification --flag.promptStrategy=detailed
```
Run multiple experiments to understand the tradeoffs between accuracy, cost, and latency.
## Validate improvements
Before deploying any change, validate it against your full test collection using baseline comparison:
```bash theme={null}
# Run baseline evaluation
axiom eval ticket-classification
# Note the run ID: run_abc123xyz
# Make your changes (update prompt, adjust config, etc.)
# Run again with baseline comparison
axiom eval ticket-classification --baseline run_abc123xyz
```
The Console shows you exactly how your changes impact:
* **Accuracy**: Did scores improve or regress?
* **Cost**: Is it more or less expensive?
* **Latency**: Is it faster or slower?
Only deploy changes that show clear improvements without unacceptable tradeoffs.
## Deploy with confidence
Once your evaluations confirm an improvement, deploy the change to production. Because you've validated against ground truth data, you can ship with confidence that the new version handles both existing cases and the new failures you discovered.
After deployment, return to the **Observe** stage to monitor performance and identify the next opportunity for improvement.
## Best practices
* **Build your collections over time.** Your evaluation collections should grow as you discover new failure modes. Each production issue that makes it through is an opportunity to strengthen your test coverage.
* **Track improvements systematically.** Use baseline comparisons for every change. This creates a clear history of how your capability has improved and prevents regressions.
* **Prioritize high-impact changes.** Focus on failures that affect many users or high-value interactions. Not every edge case deserves immediate attention.
* **Experiment before committing.** Flags let you test multiple approaches quickly. Run several experiments to understand the solution space before making code changes.
* **Close the loop.** The improvement cycle never ends. Each deployment generates new production data that reveals the next set of improvements to make.
## What's next?
To learn more about the evaluation framework that powers this improvement loop, see [Evaluate](/ai-engineering/evaluate/overview).
To understand how to capture rich telemetry from production, see [Observe](/ai-engineering/observe).
# Review conversations
Source: https://axiom.co/docs/ai-engineering/iterate/review
Review AI conversations from queues, capture experts feedback, and turn recurring failures into tracked issues.
## How Review and Issues work together
Review and Issues are part of the same workflow:
* Use **Review** to assess individual conversations and capture feedback.
* When you submit a negative review, Axiom either matches it to an existing issue or creates a new issue.
* Use **Issues** to track recurring failure patterns across many reviewed conversations.
To learn how recurring findings are grouped and managed after review, see [Track issues](/ai-engineering/iterate/issues).
## Open review
To open review:
1. Open **AI engineering**.
2. Click **Review**.
3. Select a capability from the sidebar.
## Choose a queue
Use the queue selector in the page header to change the review source.
* **Flagged** shows traces or conversations that a teammate marked for review.
* **Recent** shows the latest conversations for the selected capability.
* **Negative feedback** shows traces with negative user feedback.
* **Errored** shows traces with errors or exceptions.
* **Reviewed** shows conversations that already have a saved review.
## Flag a trace for review
Conversations can be flagged for review through the trace viewer. To flag a trace for review:
1. Open the trace in Axiom.
2. In the trace sidebar, click **Flag for review**.
To open the flagged queue:
1. Open **AI engineering** > **Review**.
2. Select the **Flagged** queue.
## Review a conversation
To review a conversation:
1. Select a conversation from the queue list.
2. Inspect the conversation in the center panel.
3. Optional: Open the **Detail** tab to inspect conversation metadata.
4. In the **Review** panel, set **Quality** to **Good** or **Bad**.
5. Enter your notes and submit.
After submission, Axiom processes the review and does one of the following:
* Matches the review to an existing issue.
* Creates a new unresolved issue.
* Skips issue categorization for positive, unrelated, or vague feedback.
If Axiom finds or creates issues, the confirmation panel links to the affected issue records.
If you don't agree with the automatic decision you can click **Edit** and select a different issue or create a new one.
## View the decision log
Once you submit a review, a decision is logged with status and reasoning. To inspect review-to-issue decisions:
1. Open **AI engineering** > **Reviews**.
2. Click the queue actions menu.
3. Click **View decision log**.
4. Filter the list by decision type or issue.
The decision log includes these decision types:
* **New** for reviews that created a new issue.
* **Match** for reviews mapped to an existing issue.
* **Skip** for reviews that didn't create or match an issue.
## Move through the queue
To continue working through a queue:
1. Click **Done & Next** to open the next queued conversation.
2. Click **Edit** to override the automatic decision and pick an exisitng issue or create a new one.
## Reopen completed reviews
To inspect saved reviews:
1. Open **AI engineering** > **Review**.
2. Select the same capability.
3. Choose the **Reviewed** queue.
4. Select a conversation from the list.
## Related documentation
Group recurring review findings into issues and manage issue status.
Use production findings to prioritize fixes and improve your capability.
# Measure
Source: https://axiom.co/docs/ai-engineering/measure
Learn how to measure the quality of your AI capabilities by running evaluations against ground truth data.
The evaluation framework described here is in active development. Axiom is working with design partners to shape what’s built. [Contact Axiom](https://www.axiom.co/contact) to get early access and join a focused group of teams shaping these tools.
The **Measure** stage is where you quantify the quality and effectiveness of your AI capability. Instead of relying on anecdotal checks, this stage uses a systematic process called an eval to score your capability’s performance against a known set of correct examples (ground truth). This provides a data-driven benchmark to ensure a capability is ready for production and to track its quality over time.
Evaluations (evals) are systematic tests that measure how well your AI features perform. Instead of manually testing AI outputs, evals automatically run your AI code against test datasets and score the results using custom metrics. This lets you catch regressions, compare different approaches, and confidently improve your AI features over time.
## Prerequisites
Follow the [Quickstart](/ai-engineering/quickstart):
* To run evals within the context of an existing AI app, follow the instrumentation setup in the [Quickstart](/ai-engineering/quickstart).
* To run evals without an existing AI app, skip the part in the Quickstart about instrumentalising your app.
## Write evalulation function
The `Eval` function provides a simple, declarative way to define a test suite for your capability directly in your codebase.
The key parameters of the `Eval` function:
* `data`: An async function that returns your collection of `{ input, expected }` pairs, which serve as your ground truth.
* `task`: The function that executes your AI capability, taking an `input` and producing an `output`.
* `scorers`: An array of scorer functions that score the `output` against the `expected` value.
* `metadata`: Optional metadata for the evaluation, such as a description.
The example below creates an evaluation for a support ticket classification system in the file `/src/evals/ticket-classification.eval.ts`.
```ts /src/evals/ticket-classification.eval.ts expandable theme={null}
import { Eval, Scorer } from 'axiom/ai/evals';
import { generateObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { wrapAISDKModel } from 'axiom/ai';
import { flag, pickFlags } from '../lib/app-scope';
import { z } from 'zod';
// The function you want to evaluate
async function classifyTicket({ subject, content }: { subject?: string; content: string }) {
const model = flag('ticketClassification.model');
const result = await generateObject({
model: wrapAISDKModel(openai(model)),
messages: [
{
role: 'system',
content: `You are a customer support engineer classifying tickets as: spam, question, feature_request, or bug_report.
If spam, return a polite auto-close message. Otherwise, say a team member will respond shortly.`,
},
{
role: 'user',
content: subject ? `Subject: ${subject}\n\n${content}` : content,
},
],
schema: z.object({
category: z.enum(['spam', 'question', 'feature_request', 'bug_report']),
response: z.string()
}),
});
return result.object;
}
// Custom exact-match scorer that returns score and metadata
const ExactMatchScorer = Scorer(
'Exact-Match',
({ output, expected }: { output: { response: string }; expected: { response: string } }) => {
const normalizedOutput = output.response.trim().toLowerCase();
const normalizedExpected = expected.response.trim().toLowerCase();
return {
score: normalizedOutput === normalizedExpected,
metadata: {
details: 'A scorer that checks for exact match',
},
};
});
}
);
// Custom spam classification scorer
const SpamClassificationScorer = Scorer(
"Spam-Classification",
({ output, expected }: {
output: { category: string };
expected: { category: string };
}) => {
const isSpam = (item: { category: string }) => item.category === "spam";
return isSpam(output) === isSpam(expected) ? 1 : 0;
}
);
// Define the evaluation
Eval('spam-classification', {
// Specify which flags this eval uses
configFlags: pickFlags('ticketClassification'),
// Test data with input/expected pairs
data: [
{
input: {
subject: "Congratulations! You've Been Selected for an Exclusive Reward",
content: 'Claim your $500 gift card now by clicking this link!',
},
expected: {
category: 'spam',
response: "We're sorry, but your message has been automatically closed.",
},
},
{
input: {
subject: 'FREE CA$H',
content: 'BUY NOW ON WWW.BEST-DEALS.COM!',
},
expected: {
category: 'spam',
response: "We're sorry, but your message has been automatically closed.",
},
},
],
// The task to run for each test case
task: async ({ input }) => {
return await classifyTicket(input);
},
// Scorers to measure performance
scorers: [SpamClassificationScorer, ExactMatchScorer],
// Optional metadata
metadata: {
description: 'Classify support tickets as spam or not spam',
},
});
```
## Set up flags
Create the file `src/lib/app-scope.ts`:
```ts /src/lib/app-scope.ts theme={null}
import { createAppScope } from 'axiom/ai';
import { z } from 'zod';
export const flagSchema = z.object({
ticketClassification: z.object({
model: z.string().default('gpt-4o-mini'),
}),
});
const { flag, pickFlags } = createAppScope({ flagSchema });
export { flag, pickFlags };
```
## Run evaluations
To run your evaluation suites from your terminal, [install the Axiom CLI](/reference/cli) and use the following commands.
| Description | Command |
| ------------------------------------ | ---------------------------------------------------- |
| Run all evals | `axiom eval` |
| Run specific eval file | `axiom eval src/evals/ticket-classification.eval.ts` |
| Run evals matching a glob pattern | `axiom eval "**/*spam*.eval.ts"` |
| Run eval by name | `axiom eval "spam-classification"` |
| List available evals without running | `axiom eval --list` |
## Analyze results in Console
When you run an eval, Axiom AI SDK captures a detailed OpenTelemetry trace for the entire run. This includes parent spans for the evaluation suite and child spans for each individual test case, task execution, and scorer result. Axiom enriches the traces with `eval.*` attributes, allowing you to deeply analyze results in the Axiom Console.
The results of evals:
* Pass/fail status for each test case
* Scores from each scorer
* Comparison to baseline (if available)
* Links to view detailed traces in Axiom
The Console features leaderboards and comparison views to track score progression across different versions of a capability, helping you verify that your changes are leading to measurable improvements.
## Additional configuration options
### Custom scorers
A scorer is a function that scores a capability’s output. Scorers receive the `input`, the generated `output`, and the `expected` value, and return a score.
The example above uses two custom scorers. Scorers can return metadata alongside the score.
You can use the [`autoevals` library](https://github.com/braintrustdata/autoevals) instead of custom scorers. `autoevals` provides prebuilt scorers for common tasks like semantic similarity, factual correctness, and text matching.
### Run experiments
Flags let you parameterize your AI behavior (like model choice or prompting strategies) and run experiments with different configurations. They’re type-safe via Zod schemas, and you can override them at runtime.
The example above uses the `ticketClassification` flag to test different language models. Flags have a default value that you can override at runtime in one of the following ways:
* Override flags directly when you run the eval:
```bash theme={null}
axiom eval --flag.ticketClassification.model=gpt-4o
```
* Alternatively, specify the flag overrides in a JSON file.
```json experiment.json theme={null}
{
"ticketClassification": {
"model": "gpt-4o"
}
}
```
And then specify the JSON file as the value of the `flags-config` parameter when you run the eval:
```bash theme={null}
axiom eval --flags-config=experiment.json
```
## What’s next?
A capability is ready to be deployed when it meets your quality benchmarks. After deployment, the next steps can be the following:
* **Baseline comparisons**: Run evals multiple times to track regression over time.
* **Experiment with flags**: Test different models or strategies using flag overrides.
* **Advanced scorers**: Build custom scorers for domain-specific metrics.
* **CI/CD integration**: Add `axiom eval` to your CI pipeline to catch regressions.
The next step is to monitor your capability’s performance with real-world traffic. To learn more about this step of the AI engineering workflow, see [Observe](/ai-engineering/observe).
# Overview of Observe stage
Source: https://axiom.co/docs/ai-engineering/observe
Learn how to observe your deployed AI capabilities in production using Axiom AI SDK to capture telemetry.
In the Observe stage of the AI engineering lifecycle, the focus is on understanding how your deployed generative AI capabilities perform in the real world. After creating and evaluating a capability, observing its production behavior is crucial for identifying unexpected issues, tracking costs, and gathering the data needed for future improvements.
## Instrument your app
Axiom offers the following approaches to capture generative AI telemetry:
| Instrumentation approach | Language support | Characteristics |
| :------------------------------------------------------------------- | :--------------- | :----------------------------------------------------------- |
| [Axiom AI SDK](/ai-engineering/observe/axiom-ai-sdk-instrumentation) | TypeScript | Quick setup. Minimal code changes. |
| [Manual](/ai-engineering/observe/manual-instrumentation) | Any | More involved setup. Full control over instrumentation. |
**Instrumentation with Axiom AI SDK** is the right choice for you if you have a TypeScript app and you want the SDK to capture and send traces with the correct semantic conventions.
**Manual instrumentation** is the right choice for you if you want to use your own tooling or if you use a language other than TypeScript. You need to instrument your app manually to emit traces compatible with Axiom’s AI engineering features.
Both approaches emit identical attributes. This means that all the telemetry analysis features work the same way.
## Review production conversations
Review and Issues let domain experts inspect production conversations, capture human feedback, and group recurring failures into a tracked backlog.
* Use [Review conversations](/ai-engineering/iterate/review) to work through flagged, recent, feedback-driven, errored, and previously reviewed conversations.
* Use [Track issues](/ai-engineering/iterate/issues) to consolidate repeated failures, inspect supporting evidence, and manage issue status over time.
## 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
1. Click the Query tab.
2. Create an APL query about your GenAI dataset. For example:
```kusto theme={null}
['gen-ai-traces']
| where ['attributes.gen_ai.operation.name'] == "chat"
```
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
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.
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?
After capturing and analyzing production telemetry, use these insights to improve your capability. Learn more in [Iterate](/ai-engineering/iterate).
# Instrumentation with Axiom AI SDK
Source: https://axiom.co/docs/ai-engineering/observe/axiom-ai-sdk-instrumentation
Learn how to instrument your TypeScript app with Axiom AI SDK for AI telemetry capture.
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](/ai-engineering/observe/manual-instrumentation). For more information on instrumentation approaches, see [Introduction to Observe](/ai-engineering/observe).
## Prerequisite
Follow the procedure in [Quickstart](/ai-engineering/quickstart) to set up Axiom AI SDK in your TypeScript project.
## 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 theme={null}
npm i ai @ai-sdk/openai
```
2. Create the file `src/shared/openai.ts` with the following content:
```ts /src/shared/openai.ts theme={null}
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 theme={null}
npm i ai @ai-sdk/anthropic
```
2. Create the file `src/shared/anthropic.ts` with the following content:
```ts /src/shared/anthropic.ts theme={null}
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 theme={null}
npm i ai @ai-sdk/google
```
2. Create the file `src/shared/gemini.ts` with the following content:
```ts /src/shared/gemini.ts theme={null}
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 theme={null}
npm i ai @ai-sdk/xai
```
2. Create the file `src/shared/grok.ts` with the following content:
```ts /src/shared/grok.ts theme={null}
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 theme={null}
npm i ai
```
2. Create the file `src/shared/openai.ts` with the following content:
```ts /src/shared/openai.ts theme={null}
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
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 theme={null}
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
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 theme={null}
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
Example of how all three instrumentation functions work together in a single, real-world example:
```ts /src/app/page.tsx expandable theme={null}
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?
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 [developing AI features](/ai-engineering/create) with confidence using Axiom
# Generative AI attributes
Source: https://axiom.co/docs/ai-engineering/observe/gen-ai-attributes
Understand the key attributes that your generative AI app sends to Axiom.
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
* `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
* `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
* `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
* `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
* `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
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?
After capturing and analyzing production telemetry:
* [Visualize traces](/query-data/traces) in Console.
* Use the new insights to [iterate](/ai-engineering/iterate) on your capability.
# Manual instrumentation
Source: https://axiom.co/docs/ai-engineering/observe/manual-instrumentation
Learn how to manually instrument your generative AI apps using OpenTelemetry tooling.
Manually instrumenting your generative AI apps using language-agnostic OpenTelemetry tooling gives you full control over your instrumentation while ensuring compatibility with Axiom’s AI engineering features.
Alternatively, instrument your app with Axiom AI SDK. For more information on instrumentation approaches, see [Introduction to Observe](/ai-engineering/observe).
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
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`
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
Axiom recommends the following attributes to get the most out of Axiom’s AI telemetry features:
### `axiom.gen_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
| 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
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
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
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 support four different roles, each with specific content formats. They follow OpenTelemetry’s structured format:
### 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 theme={null}
{
"role": "system",
"parts": [
{"type": "text", "content": "You are a helpful assistant"}
]
}
```
### 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 theme={null}
{
"role": "user",
"parts": [
{"type": "text", "content": "Weather in Paris?"}
]
}
```
### 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 theme={null}
{
"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 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 theme={null}
{
"role": "tool",
"parts": [
{"type": "tool_call_response", "id": "call_123", "response": "rainy, 57°F"}
]
}
```
### 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
### Chat completion
Example of a properly structured chat completion trace:
```typescript TypeScript expandable theme={null}
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 theme={null}
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
Example of a tool execution within an agent:
```typescript TypeScript expandable theme={null}
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 theme={null}
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?
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 [developing AI features](/ai-engineering/create) with confidence using Axiom
# User feedback
Source: https://axiom.co/docs/ai-engineering/observe/user-feedback
Capture user ratings and comments on AI capability outputs to identify quality issues and prioritize improvements.
User feedback captures direct signals from end users about your AI capability’s performance. By linking feedback events to traces, you can correlate user perception with system behavior to understand exactly what went wrong and prioritize high-impact improvements.
## How user feedback works
User feedback collection works across your server and client in the following way:
1. **Server**: Your AI capability runs inside `withSpan`, which creates a trace. Extract `traceId` and `spanId` from the span and return them to the client alongside the AI response.
2. **Client**: When users provide feedback (thumbs up/down, ratings, comments), send it to Axiom with the trace IDs. This links the feedback to the exact trace.
3. **Axiom Console**: View feedback events and click through to see the corresponding AI trace to understand what happened.
## Types of feedback
Axiom AI SDK supports the following feedback types:
| Type | Description | Example |
| -------- | ---------------------------------- | ----------------------------------------- |
| `thumb` | Thumbs up (+1) or down (-1) | Response quality rating |
| `number` | Numeric value | Similarity score (0-1), star rating (1-5) |
| `bool` | Boolean true/false | "Was this helpful?" |
| `text` | Free-form string | User comments |
| `enum` | Constrained string value | Category selection |
| `signal` | No value, indicates event occurred | "User copied response" |
## Prerequisites
* [Create an Axiom account](https://app.axiom.co/register).
* [Create a dataset in Axiom](/reference/datasets#create-dataset) dedicated for storing feedback data. Feedback events are stored separately from trace data.
* [Create an API token in Axiom](/reference/tokens) with minimal permissions because it's exposed in the frontend. Add ingest-only permissions to the feedback dataset you have created.
* Install Axiom AI SDK in your project. For more information, see [Quickstart](/ai-engineering/quickstart).
## Server-side configuration
On the server side, capture trace context with `withSpan` when you run your AI capability, and pass the trace and span IDs to the frontend using `FeedbackLinks`:
```ts theme={null}
import { withSpan } from 'axiom/ai';
import type { FeedbackLinks } from 'axiom/ai/feedback';
async function runMyCapability(input: string) {
return await withSpan({ capability: 'my-capability', step: 'generate' }, async (span) => {
const links: FeedbackLinks = {
traceId: span.spanContext().traceId,
spanId: span.spanContext().spanId,
capability: 'my-capability',
};
const result = await generateResponse(input);
return { result, links };
});
}
```
`FeedbackLinks` links feedback events to traces, and allows you to see what your AI capability did when a user provided feedback.
```ts theme={null}
type FeedbackLinks = {
traceId: string; // Required: The trace ID from your AI capability
capability: string; // Required: The name of your capability
spanId?: string; // Optional: Link to a specific span
step?: string; // Optional: Step within the capability
conversationId?: string; // Optional: Refers to `attributes.gen_ai.conversation_id`
userId?: string; // Optional: User providing feedback
};
```
## Client-side configuration
On the client side, initialize a feedback client with your Axiom credentials:
```ts theme={null}
import { createFeedbackClient, Feedback } from 'axiom/ai/feedback';
const { sendFeedback } = createFeedbackClient({
token: process.env.AXIOM_FEEDBACK_TOKEN,
dataset: process.env.AXIOM_FEEDBACK_DATASET,
url: process.env.AXIOM_URL
});
```
For browser-based feedback collection, use environment variables prefixed for your framework. For example, use `NEXT_PUBLIC_` for Next.js.
Store the following environment variables:
```bash .env theme={null}
AXIOM_FEEDBACK_TOKEN="API_TOKEN"
AXIOM_FEEDBACK_DATASET="DATASET_NAME"
AXIOM_URL="AXIOM_DOMAIN"
```
Replace `API_TOKEN` with the Axiom API token you have generated. For added security, store the API token in an environment variable.
Replace `DATASET_NAME` with the name of the Axiom dataset where you send your data.
Replace `AXIOM_DOMAIN` with the base domain of your edge deployment. For more information, see [Edge deployments](/reference/edge-deployments).
### Send feedback
Use the `Feedback` helper to create feedback objects, and send them with `sendFeedback`:
```ts theme={null}
// Thumbs up
await sendFeedback(
links,
Feedback.thumbUp({ name: 'response-quality' })
);
// Thumbs down with a comment
await sendFeedback(
links,
Feedback.thumbDown({
name: 'response-quality',
message: 'The answer was incorrect',
})
);
// Using the generic thumb function
await sendFeedback(
links,
Feedback.thumb({
name: 'response-quality',
value: 'up', // or 'down'
message: 'Very helpful!',
})
);
```
```ts theme={null}
// Star rating
await sendFeedback(
links,
Feedback.number({
name: 'star-rating',
value: 4,
})
);
// Similarity score
await sendFeedback(
links,
Feedback.number({
name: 'relevance-score',
value: 0.85,
})
);
```
```ts theme={null}
await sendFeedback(
links,
Feedback.bool({
name: 'was-helpful',
value: true,
})
);
```
```ts theme={null}
await sendFeedback(
links,
Feedback.text({
name: 'user-comment',
value: 'This response saved me hours of debugging!',
})
);
```
Use enums for constrained text values:
```ts theme={null}
await sendFeedback(
links,
Feedback.enum({
name: 'issue-category',
value: 'hallucination', // or 'off-topic', 'incomplete', etc.
})
);
```
Signals indicate an event occurred without a value. Use signals for implicit feedback like copying or regenerating:
```ts theme={null}
// User copied the response
await sendFeedback(
links,
Feedback.signal({ name: 'response-copied' })
);
// User regenerated the response
await sendFeedback(
links,
Feedback.signal({ name: 'response-regenerated' })
);
```
All feedback types support optional metadata for additional context. Metadata can contain an arbitrary set of attributes:
```ts theme={null}
await sendFeedback(
links,
Feedback.thumbUp({
name: 'response-quality',
message: 'Great answer!',
metadata: {
userId: 'user-123',
sessionId: 'session-456',
responseLength: 250,
},
})
);
```
### Error handling
The feedback client logs errors to the JavaScript console by default. To handle errors differently, pass an `onError` callback:
```ts theme={null}
const { sendFeedback } = createFeedbackClient(
{
token: process.env.AXIOM_FEEDBACK_TOKEN,
dataset: process.env.AXIOM_FEEDBACK_DATASET,
url: process.env.AXIOM_URL,
},
{
onError: (error, context) => {
// Log to your error tracking service
console.error('Feedback failed:', error, context.links);
},
}
);
```
## Example: Chat interface with feedback
This example shows a complete pattern for a chat interface with thumbs up/down feedback in Next.js.
The server-side code returns the trace and span IDs to the client-side code, which is used to link the feedback to the trace:
```ts /app/actions.ts expandable theme={null}
'use server';
import { withSpan } from 'axiom/ai';
import type { FeedbackLinks } from 'axiom/ai/feedback';
export async function chat(messages: Message[]) {
return await withSpan({ capability: 'support-agent', step: 'respond' }, async (span) => {
// Add your AI logic here: call OpenAI, Anthropic, etc.
const response = await generateResponse(messages);
// Extract trace context to pass to the client
const links: FeedbackLinks = {
traceId: span.spanContext().traceId,
spanId: span.spanContext().spanId,
capability: 'support-agent',
};
return { response, links };
});
}
```
The client-side code captures feedback and sends it to the server:
```tsx expandable /app/components/ChatMessage.tsx expandable theme={null}
'use client';
import { useState } from 'react';
import { createFeedbackClient, Feedback } from 'axiom/ai/feedback';
import type { FeedbackLinks } from 'axiom/ai/feedback';
const { sendFeedback } = createFeedbackClient({
url: process.env.NEXT_PUBLIC_AXIOM_URL,
dataset: process.env.NEXT_PUBLIC_AXIOM_FEEDBACK_DATASET,
token: process.env.NEXT_PUBLIC_AXIOM_FEEDBACK_TOKEN,
});
function ChatMessage({ message, links }: { message: string; links: FeedbackLinks }) {
const [feedback, setFeedback] = useState<'up' | 'down' | null>(null);
const handleFeedback = async (value: 'up' | 'down') => {
setFeedback(value);
await sendFeedback(
links,
Feedback.thumb({ name: 'response-quality', value })
);
};
return (
{message}
);
}
```
## View feedback in Console
After collecting feedback, analyze it in the Axiom Console.
### AI engineering tab
Using the AI engineering tab, analyze the feedback events for each capability.
1. Click the AI engineering tab.
2. Click **Feedback** in the sidebar.
3. Select the capability from the dropdown.
4. Optional: Click to filter the feedback events by name.
5. Click the feedback event to see the details.
To determine what your capability did when a user gave their feedback:
1. Click **View** in the Trace column to navigate to the corresponding AI trace.
2. Analyze the trace in the waterfall view. For more information, see [Traces](/query-data/traces#waterfall-view-of-traces).
### Query tab
Using the Query tab, query the feedback dataset as any other dataset. For example, to see the number of thumbs up and thumbs down for each capability:
```kusto theme={null}
['feedback']
| where event == 'feedback'
| summarize
thumbs_up = countif(kind == 'thumb' and value == 1),
thumbs_down = countif(kind == 'thumb' and value == -1)
by capability = ['links.capability']
```
To determine what your capability did when a user gave their feedback:
1. Click the feedback event in the list.
2. In the event details panel, click the trace ID to navigate to the corresponding AI trace.
3. Analyze the trace in the waterfall view. For more information, see [Traces](/query-data/traces#waterfall-view-of-traces).
## What’s next?
* Learn how to use feedback insights to improve your capabilities in [Iterate](/ai-engineering/iterate).
* Set up evaluations to systematically test improvements in [Evaluate](/ai-engineering/evaluate/overview).
# AI engineering overview
Source: https://axiom.co/docs/ai-engineering/overview
Build increasingly sophisticated AI capabilities with confidence using systematic evaluation and observability.
Generative AI development is fundamentally different from traditional software engineering. Its outputs are probabilistic, not deterministic. This variability makes it challenging to guarantee quality and predict failure modes without the right infrastructure.
Axiom's AI engineering capabilities build on the foundational EventDB and Console to provide systematic evaluation and observability for AI systems. Whether you're building single-turn model interactions, multi-step workflows, or complex multi-agent systems, Axiom helps you push boundaries and ship with confidence.
### Axiom AI engineering workflow
Axiom provides a structured, iterative workflow for developing AI capabilities. The workflow builds statistical confidence in systems that aren’t entirely predictable through systematic evaluation and continuous improvement, from initial prototype to production monitoring.
The core stages are:
* **Create**: Prototype your AI capability using any framework. TypeScript-based frameworks like Vercel AI SDK integrate most seamlessly with Axiom's tooling. As you build, gather reference examples to establish ground truth for evaluation.
* **Evaluate**: Systematically test your capability’s performance against reference data using custom scorers to measure accuracy, quality, and cost. Use Axiom's evaluation framework to run experiments with different configurations and track improvements over time.
* **Observe**: Deploy your capability and collect rich telemetry on every LLM call and tool execution. Use online evaluations to monitor for performance degradation and discover edge cases in production.
* **Iterate**: Use insights from production monitoring and evaluation results to refine prompts, augment reference datasets, and improve the capability. Run new evaluations to verify improvements before deploying changes.
### What’s next?
* To understand the key terms used in AI engineering, see the [Concepts](/ai-engineering/concepts) page.
* To start building, follow the [Quickstart](/ai-engineering/quickstart) page.
# Quickstart
Source: https://axiom.co/docs/ai-engineering/quickstart
Install and configure Axiom AI SDK to begin capturing telemetry from your generative AI applications.
Quickly start capturing telemetry data from your generative AI capabilities. After installation and configuration, follow the Axiom AI engineering workflow to create, evaluate, observe, and iterate.
This page explains how to set up instrumentation with Axiom AI SDK. Expand the section below to choose the right instrumentation approach for your needs.
Axiom offers the following approaches to capture generative AI telemetry:
| Instrumentation approach | Language support | Characteristics |
| :------------------------------------------------------------------- | :--------------- | :----------------------------------------------------------- |
| [Axiom AI SDK](/ai-engineering/observe/axiom-ai-sdk-instrumentation) | TypeScript | Quick setup. Minimal code changes. |
| [Manual](/ai-engineering/observe/manual-instrumentation) | Any | More involved setup. Full control over instrumentation. |
**Instrumentation with Axiom AI SDK** is the right choice for you if you have a TypeScript app and you want the SDK to capture and send traces with the correct semantic conventions.
**Manual instrumentation** is the right choice for you if you want to use your own tooling or if you use a language other than TypeScript. You need to instrument your app manually to emit traces compatible with Axiom’s AI engineering features.
Both approaches emit identical attributes. This means that all the telemetry analysis features work the same way.
## 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 API token in Axiom](/reference/tokens) with permissions to ingest data to the dataset you have created.
## Install
Install Axiom AI SDK into your TypeScript project:
```bash pnpm theme={null}
pnpm i axiom
```
```bash npm theme={null}
npm i axiom
```
```bash yarn theme={null}
yarn add axiom
```
```bash bun theme={null}
bun add axiom
```
The `axiom` package includes the `axiom` command-line interface (CLI) for running evaluations, which you'll use to systematically test and improve your AI capabilities.
## Configure tracer
To send data to Axiom, configure a tracer. For example, use a dedicated instrumentation file and load it before the rest of your app. An example configuration for a Node.js environment:
1. Install dependencies:
```bash pnpm theme={null}
pnpm i \
dotenv \
@opentelemetry/exporter-trace-otlp-http \
@opentelemetry/resources \
@opentelemetry/sdk-trace-node \
@opentelemetry/semantic-conventions \
@opentelemetry/api
```
```bash npm theme={null}
npm i \
dotenv \
@opentelemetry/exporter-trace-otlp-http \
@opentelemetry/resources \
@opentelemetry/sdk-trace-node \
@opentelemetry/semantic-conventions \
@opentelemetry/api
```
```bash yarn theme={null}
yarn add \
dotenv \
@opentelemetry/exporter-trace-otlp-http \
@opentelemetry/resources \
@opentelemetry/sdk-trace-node \
@opentelemetry/semantic-conventions \
@opentelemetry/api
```
```bash bun theme={null}
bun add \
dotenv \
@opentelemetry/exporter-trace-otlp-http \
@opentelemetry/resources \
@opentelemetry/sdk-trace-node \
@opentelemetry/semantic-conventions \
@opentelemetry/api
```
2. Create an instrumentation file:
```ts /src/instrumentation.ts expandable theme={null}
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { BatchSpanProcessor, NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
import { trace } from "@opentelemetry/api";
import { initAxiomAI, RedactionPolicy } from 'axiom/ai';
import type { AxiomEvalInstrumentationHook } from 'axiom/ai/config';
const tracer = trace.getTracer("my-tracer");
let provider: NodeTracerProvider | undefined;
// Wrap your logic in the AxiomEvalInstrumentationHook function
export const setupAppInstrumentation: AxiomEvalInstrumentationHook = async ({
dataset,
url,
token,
}) => {
if (provider) {
return { provider };
}
if (!dataset || !url || !token) {
throw new Error('Missing environment variables');
}
// Replace the environment variables with the parameters passed to the function
const exporter = new OTLPTraceExporter({
url: `${url}/v1/traces`,
headers: {
Authorization: `Bearer ${token}`,
'X-Axiom-Dataset': dataset,
},
})
// Configure the provider to export traces to your Axiom dataset
provider = new NodeTracerProvider({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'my-app', // Replace with your service name
},
{
// Use the latest schema version
// Info: https://opentelemetry.io/docs/specs/semconv/
schemaUrl: 'https://opentelemetry.io/schemas/1.37.0',
}),
spanProcessor: new BatchSpanProcessor(exporter),
});
// Register the provider
provider.register();
// Initialize Axiom AI SDK with the configured tracer
initAxiomAI({ tracer, redactionPolicy: RedactionPolicy.AxiomDefault });
return { provider };
};
```
For more information on specifying redaction policies, see [Redaction policies](/ai-engineering/redaction-policies).
### Create Axiom configuration file
The Axiom configuration file enables the evaluation framework, allowing you to run systematic tests against your AI capabilities and track improvements over time.
At the root of your project, create the Axiom configuration file `/axiom.config.ts`:
```ts /axiom.config.ts theme={null}
import { defineConfig } from 'axiom/ai/config';
import { setupAppInstrumentation } from './src/instrumentation';
export default defineConfig({
eval: {
url: process.env.AXIOM_URL,
token: process.env.AXIOM_TOKEN,
dataset: process.env.AXIOM_DATASET,
// Optional: customize which files to run
include: ['**/*.eval.{ts,js}'],
// Optional: exclude patterns
exclude: [],
// Optional: timeout for eval execution
timeoutMs: 60_000,
// Optional: instrumentation hook for OpenTelemetry
// (created this in the "Create instrumentation setup" step)
instrumentation: ({ url, token, dataset }) =>
setupAppInstrumentation({ url, token, dataset }),
},
});
```
## Store environment variables
Store environment variables in an `.env` file in the root of your project:
```bash .env theme={null}
AXIOM_URL="AXIOM_DOMAIN"
AXIOM_TOKEN="API_TOKEN"
AXIOM_DATASET="DATASET_NAME"
OPENAI_API_KEY=""
GEMINI_API_KEY=""
XAI_API_KEY=""
ANTHROPIC_API_KEY=""
```
Replace `API_TOKEN` with the Axiom API token you have generated. For added security, store the API token in an environment variable.
Replace `DATASET_NAME` with the name of the Axiom dataset where you send your data.
Replace `AXIOM_DOMAIN` with the base domain of your edge deployment. For more information, see [Edge deployments](/reference/edge-deployments).
Enter the API keys for the LLMs you want to work with.
To run offline evaluations, you can authenticate using OAuth instead of using environment variables. For more information, see [Set up and authenticate offline evaluations](/ai-engineering/evaluate/write-evaluations#prerequisites).
## What’s next?
* **Build your first capability**: Start prototyping with [Create](/ai-engineering/create).
* **Set up evaluations**: Learn how to systematically test your capabilities with [Evaluate](/ai-engineering/evaluate/overview).
* **Capture production telemetry**: Instrument your AI calls for observability with [Observe](/ai-engineering/observe).
# Redaction policies
Source: https://axiom.co/docs/ai-engineering/redaction-policies
This page explains how to use redaction policies in the Axiom AI SDK to control what data is captured in AI spans
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
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
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
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
To determine which redaction policy fits your needs, see the following examples about what gets captured with each defaultpolicy:
```json theme={null}
{
"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 theme={null}
{
"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 theme={null}
{
"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 theme={null}
{
"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
Set a default redaction policy for your entire application using `initAxiomAI`:
```ts Full data capture theme={null}
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 theme={null}
import { trace } from '@opentelemetry/api';
import { initAxiomAI, RedactionPolicy } from 'axiom/ai';
const tracer = trace.getTracer("my-tracer");
initAxiomAI({ tracer, redactionPolicy: RedactionPolicy.OpenTelemetryDefault });
```
In [Quickstart](/ai-engineering/quickstart), `initAxiomAI` is called in your instrumentation file (`/src/instrumentation.ts`).
## 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 theme={null}
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
Create custom policies by defining an `AxiomAIRedactionPolicy` object:
```ts theme={null}
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
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
This page explains how to use the arg_max aggregation in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats max(req_duration_ms) as max_duration by id, uri
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['sample-http-logs']
| summarize arg_max(req_duration_ms, id, uri)
```
## Usage
### Syntax
```kusto theme={null}
| summarize arg_max(expression, field1[, field2, ...])
```
### 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 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
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 theme={null}
['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
Find the slowest path for each HTTP method in the `['sample-http-logs']` dataset.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# arg_min
Source: https://axiom.co/docs/apl/aggregation-function/arg-min
This page explains how to use the arg_min aggregation in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats min(req_duration_ms) as minDuration by id
| where req_duration_ms=minDuration
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT id, uri
FROM sample_http_logs
WHERE req_duration_ms = (
SELECT MIN(req_duration_ms)
FROM sample_http_logs
);
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize arg_min(req_duration_ms, id, uri)
```
## Usage
### Syntax
```kusto theme={null}
| summarize arg_min(expression, field1, ..., fieldN)
```
### 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 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
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 theme={null}
['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
You can use `arg_min` to identify the path with the shortest duration and its associated details for each method.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# avg
Source: https://axiom.co/docs/apl/aggregation-function/avg
This page explains how to use the avg aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats avg(req_duration_ms) by status
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT status, AVG(req_duration_ms)
FROM sample_http_logs
GROUP BY status
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize avg(req_duration_ms) by status
```
## Usage
### Syntax
```kusto theme={null}
summarize avg(ColumnName) [by GroupingColumn]
```
### 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
* A table with the average value for the specified field, optionally grouped by another column.
## Use case examples
This example calculates the average request duration for HTTP requests, grouped by status.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# avgif
Source: https://axiom.co/docs/apl/aggregation-function/avgif
This page explains how to use the avgif aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats avg(req_duration_ms) by id where status = "200"
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT id, AVG(CASE WHEN status = '200' THEN req_duration_ms ELSE NULL END)
FROM sample_http_logs
GROUP BY id
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize avgif(req_duration_ms, status == "200") by id
```
## Usage
### Syntax
```kusto theme={null}
summarize avgif(expr, predicate) by grouping_field
```
### 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
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
In this example, you calculate the average request duration for HTTP status 200 in different cities.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# count
Source: https://axiom.co/docs/apl/aggregation-function/count
This page explains how to use the count aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, the `count` function works similarly to APL, but the syntax differs slightly.
```sql Splunk example theme={null}
| stats count by status
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT status, COUNT(*)
FROM sample_http_logs
GROUP BY status
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize count() by status
```
## Usage
### Syntax
```kusto theme={null}
summarize count() [by GroupingColumn]
```
### 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
* A table with the count of records for the entire dataset or grouped by the specified column.
## 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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# countif
Source: https://axiom.co/docs/apl/aggregation-function/countif
This page explains how to use the countif aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats count(eval(status="500")) AS error_count
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT COUNT(CASE WHEN status = '500' THEN 1 END) AS error_count
FROM sample_http_logs
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize countif(status == '500')
```
## Usage
### Syntax
```kusto theme={null}
countif(condition)
```
### Parameters
* **condition**: A boolean expression that filters the records based on a condition. Only records where the condition evaluates to `true` are counted.
### Returns
The function returns the number of records that match the specified condition.
## 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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# dcount
Source: https://axiom.co/docs/apl/aggregation-function/dcount
This page explains how to use the dcount aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats dc(user_id) AS distinct_users
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT COUNT(DISTINCT user_id) AS distinct_users
FROM sample_http_logs
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize dcount(id)
```
## Usage
### Syntax
```kusto theme={null}
dcount(column_name)
```
### Parameters
* **column\_name**: The name of the column for which you want to count distinct values.
### Returns
The function returns the count of distinct values found in the specified column.
## Use case examples
In log analysis, you can count how many distinct users accessed the service.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# dcountif
Source: https://axiom.co/docs/apl/aggregation-function/dcountif
This page explains how to use the dcountif aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats dc(eval(status="200")) AS distinct_successful_users
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT COUNT(DISTINCT CASE WHEN status = '200' THEN user_id END) AS distinct_successful_users
FROM sample_http_logs
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize dcountif(id, status == '200')
```
## Usage
### Syntax
```kusto theme={null}
dcountif(column_name, condition)
```
### 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
The function returns the count of distinct values that meet the specified condition.
## 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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# histogram
Source: https://axiom.co/docs/apl/aggregation-function/histogram
This page explains how to use the histogram aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats count by duration | timechart span=10 count
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT COUNT(*), FLOOR(req_duration_ms/10)*10 as duration_bin
FROM sample_http_logs
GROUP BY duration_bin
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize count() by histogram(req_duration_ms, 10)
```
## Usage
### Syntax
```kusto theme={null}
histogram(numeric_field, number_of_bins)
```
### 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
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
You can use the `histogram` aggregation to analyze the distribution of request durations in web server logs.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# histogramif
Source: https://axiom.co/docs/apl/aggregation-function/histogramif
This page explains how to use the histogramif aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| where status='200'
| timechart span=10 count by duration
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['sample-http-logs']
| summarize histogramif(req_duration_ms, 10, status == '200')
```
## Usage
### Syntax
```kusto theme={null}
histogramif(numeric_field, number_of_bins, condition)
```
### 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
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 `histogramif` to analyze the distribution of request durations only for successful HTTP requests.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# make_list
Source: https://axiom.co/docs/apl/aggregation-function/make-list
This page explains how to use the make_list aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
index=logs | stats values(uri) by user
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT ARRAY_AGG(uri) AS uris FROM sample_http_logs GROUP BY id;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize uris=make_list(uri) by id
```
## Usage
### Syntax
```kusto theme={null}
make_list(column)
```
### Parameters
* `column`: The name of the column to collect into a list.
### 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
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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# make_list_if
Source: https://axiom.co/docs/apl/aggregation-function/make-list-if
This page explains how to use the make_list_if aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats list(field) as field_list by condition
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT ARRAY_AGG(CASE WHEN condition THEN field END) FROM table
```
```kusto APL equivalent theme={null}
summarize make_list_if(field, condition)
```
## Usage
### Syntax
```kusto theme={null}
summarize make_list_if(expression, condition)
```
### 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
The function returns an array containing all values from `expression` that meet the specified `condition`.
## Use case examples
In this example, we will gather a list of request durations for successful HTTP requests.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# make_set
Source: https://axiom.co/docs/apl/aggregation-function/make-set
This page explains how to use the make_set aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats values(method) by id
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT id, ARRAY_AGG(DISTINCT method)
FROM sample_http_logs
GROUP BY id;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize make_set(method) by id
```
## Usage
### Syntax
```kusto theme={null}
make_set(column, [limit])
```
### 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
An array of unique values from the specified column.
## 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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# make_set_if
Source: https://axiom.co/docs/apl/aggregation-function/make-set-if
This page explains how to use the make_set_if aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats values(field) by another_field where condition
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT DISTINCT CASE WHEN condition THEN field END
FROM table
GROUP BY another_field
```
```kusto APL equivalent theme={null}
summarize make_set_if(field, condition) by another_field
```
## Usage
### Syntax
```kusto theme={null}
make_set_if(column, predicate, [max_size])
```
### 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
The `make_set_if` function returns a dynamic array of distinct values from the specified column that satisfy the given condition.
## 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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# max
Source: https://axiom.co/docs/apl/aggregation-function/max
This page explains how to use the max aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats max(req_duration_ms)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT MAX(req_duration_ms) FROM sample_http_logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize max(req_duration_ms)
```
## Usage
### Syntax
```kusto theme={null}
summarize max(ColumnName)
```
### 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
The maximum value from the specified column.
## Use case examples
In log analysis, you might want to find the longest request duration to diagnose performance issues.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# maxif
Source: https://axiom.co/docs/apl/aggregation-function/maxif
This page explains how to use the maxif aggregation function in APL.
# maxif aggregation in APL
## 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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats max(req_duration_ms) as max_duration where status="200"
```
```kusto theme={null}
['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 theme={null}
SELECT MAX(req_duration_ms)
FROM logs
WHERE status = '200';
```
```kusto theme={null}
['sample-http-logs']
| summarize maxif(req_duration_ms, status == "200")
```
## Usage
### Syntax
```kusto theme={null}
summarize maxif(column, condition)
```
### 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
The maximum value from `column` for rows that meet the `condition`. If no rows match the condition, it returns `null`.
## Use case examples
In log analysis, you might want to find the maximum request duration, but only for successful requests.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# min
Source: https://axiom.co/docs/apl/aggregation-function/min
This page explains how to use the min aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats min(duration) by id
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT MIN(duration), id FROM sample_http_logs GROUP BY id;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize min(req_duration_ms) by id
```
## Usage
### Syntax
```kusto theme={null}
summarize min(Expression)
```
### Parameters
* `Expression`: The expression from which to calculate the minimum value. Typically, this is a numeric or date/time field.
### Returns
The function returns the smallest value found in the specified column or expression.
## Use case examples
In this use case, you analyze HTTP logs to find the minimum request duration for each unique user.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# minif
Source: https://axiom.co/docs/apl/aggregation-function/minif
This page explains how to use the minif aggregation function in APL.
## 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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats min(req_duration_ms) as min_duration where status="200"
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['sample-http-logs']
| summarize minif(req_duration_ms, status == "200") by id
```
## Usage
### Syntax
```kusto theme={null}
summarize minif(Expression, Predicate)
```
### Parameters
| Parameter | Description |
| ------------ | ------------------------------------------------------------ |
| `Expression` | The numeric expression whose minimum value you want to find. |
| `Predicate` | The condition that determines which records to include. |
### Returns
The `minif` aggregation returns the minimum value of the specified `Expression` for the records that satisfy the `Predicate`.
## Use case examples
In log analysis, you might want to find the minimum request duration for successful HTTP requests.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# percentile
Source: https://axiom.co/docs/apl/aggregation-function/percentile
This page explains how to use the percentile aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats perc95(req_duration_ms)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY req_duration_ms) FROM sample_http_logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize percentile(req_duration_ms, 95)
```
## Usage
### Syntax
```kusto theme={null}
percentile(column, percentile)
```
### 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
The function returns the value from the specified column that corresponds to the given percentile.
## 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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# percentileif
Source: https://axiom.co/docs/apl/aggregation-function/percentileif
This page explains how to use the percentileif aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
stats perc95(req_duration_ms) as p95 where geo.country="US"
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY req_duration_ms)
FROM sample_http_logs
WHERE geo_country = 'US'
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize percentileif(req_duration_ms, 95, geo.country == 'US')
```
## Usage
### Syntax
```kusto theme={null}
summarize percentileif(Field, Percentile, Predicate)
```
### 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
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
You can use `percentileif` to analyze request durations for specific HTTP methods.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# percentiles_array
Source: https://axiom.co/docs/apl/aggregation-function/percentiles-array
This page explains how to use the percentiles_array function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | stats perc95(duration), perc50(duration), perc25(duration) by service
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['otel-demo-traces']
| summarize percentiles_array(duration, 25, 50, 95) by ['service.name']
```
## Usage
### Syntax
```kusto theme={null}
percentiles_array(Field, Percentile1, Percentile2, ...)
```
### 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
An array of numbers where each element is the value at the corresponding percentile.
## Use case examples
Use `percentiles_array` to understand the spread of request durations per HTTP method, highlighting performance variability.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# percentiles_arrayif
Source: https://axiom.co/docs/apl/aggregation-function/percentiles-arrayif
This page explains how to use the percentiles_array function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
index=main sourcetype=access_combined
| stats perc90(req_duration_ms) AS p90, perc99(req_duration_ms) AS p99
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['sample-http-logs']
| summarize Dist=percentiles_arrayif(req_duration_ms, dynamic([90, 99]), status == '200')
```
# Usage
## Syntax
```kusto theme={null}
percentiles_arrayif(Field, Array, Condition)
```
## 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
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
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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# phrases
Source: https://axiom.co/docs/apl/aggregation-function/phrases
This page explains how to use the phrases aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| rex field=message "(?\w+)"
| top words
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['sample-http-logs']
| summarize phrases(uri, 10)
```
## Usage
### Syntax
```kusto theme={null}
summarize phrases(column, max_phrases)
```
### 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 a dynamic array containing the most common phrases found in the specified column, ordered by frequency.
## Example
Extract common phrases from GenAI conversation text to identify trending topics and patterns.
**Query**
```kusto theme={null}
['genai-traces']
| extend conversation_text = genai_concat_contents(['attributes.gen_ai.input.messages'], ' | ')
| summarize common_phrases = phrases(conversation_text, 20)
```
**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
* [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.
# rate
Source: https://axiom.co/docs/apl/aggregation-function/rate
This page explains how to use the rate aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| timechart per_second count by resp_body_size_bytes
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT resp_body_size_bytes, COUNT(*) / TIMESTAMPDIFF(SECOND, MIN(_time), MAX(_time)) AS rate
FROM http_logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize rate(resp_body_size_bytes) by bin(_time, 1s)
```
## Usage
### Syntax
```kusto theme={null}
rate(field)
```
### Parameters
* `field`: The numeric field for which you want to calculate the rate.
### 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 theme={null}
['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
In this example, the `rate` aggregation calculates the rate of HTTP response sizes per second.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# spotlight
Source: https://axiom.co/docs/apl/aggregation-function/spotlight
This page explains how to use the spotlight function in APL to compare a selected set of events against a baseline and surface the most significant differences.
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).
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
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 theme={null}
['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 theme={null}
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 theme={null}
['sample-http-logs']
| where _time >= now(-15m)
| summarize spotlight(status in ('500','502','503'),
status, method, uri, ['geo.country'], ['geo.city'])
```
## Usage
### Syntax
```kusto theme={null}
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
| 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
* Bar charts for categorical fields (strings, Booleans)
* Boxplots for numeric fields (integers, floats, timespans) with many distinct values
## Use case examples
Find what distinguishes error responses from normal traffic in the last 15 minutes.
**Query**
```kusto theme={null}
['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 theme={null}
['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
* 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
* [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.
# Aggregation functions
Source: https://axiom.co/docs/apl/aggregation-function/statistical-functions
This section explains how to use and combine different aggregation functions in APL.
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 expressionession 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
This page explains how to use the stdev aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats stdev(duration) as duration_std
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT STDDEV(duration) AS duration_std FROM dataset;
```
```kusto APL equivalent theme={null}
['dataset']
| summarize duration_std = stdev(duration)
```
## Usage
### Syntax
```kusto theme={null}
stdev(numeric_field)
```
### Parameters
* **`numeric_field`**: The field containing numeric values for which the standard deviation is calculated.
### Returns
The `stdev` aggregation returns a single numeric value representing the standard deviation of the specified numeric field in the dataset.
## 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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# stdevif
Source: https://axiom.co/docs/apl/aggregation-function/stdevif
This page explains how to use the stdevif aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats stdev(req_duration_ms) as stdev_req where status="200"
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT STDDEV(CASE WHEN status = '200' THEN req_duration_ms END)
FROM sample_http_logs
GROUP BY geo.country;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize stdevif(req_duration_ms, status == "200") by geo.country
```
## Usage
### Syntax
```kusto theme={null}
summarize stdevif(column, condition)
```
### 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
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
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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# sum
Source: https://axiom.co/docs/apl/aggregation-function/sum
This page explains how to use the sum aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats sum(req_duration_ms) as total_duration
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT SUM(req_duration_ms) AS total_duration
FROM sample_http_logs
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize total_duration = sum(req_duration_ms)
```
## Usage
### Syntax
```kusto theme={null}
summarize [ =] sum()
```
### 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
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
The `sum` aggregation can be used to calculate the total request duration in an HTTP log dataset.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# sumif
Source: https://axiom.co/docs/apl/aggregation-function/sumif
This page explains how to use the sumif aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats sum(duration) as total_duration where status="200"
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT SUM(CASE WHEN status = '200' THEN duration ELSE 0 END) AS total_duration
FROM http_logs
```
```kusto APL equivalent theme={null}
summarize total_duration = sumif(duration, status == '200')
```
## Usage
### Syntax
```kusto theme={null}
sumif(numeric_expression, condition)
```
### 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
`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
In this use case, we calculate the total request duration for HTTP requests that returned a `200` status code.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# topk
Source: https://axiom.co/docs/apl/aggregation-function/topk
This page explains how to use the topk aggregation function in APL.
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).
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| top limit=5 status by method
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT status, COUNT(*)
FROM sample_http_logs
GROUP BY status
ORDER BY COUNT(*) DESC
LIMIT 5;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize topk(status, 5)
```
## Usage
### Syntax
```kusto theme={null}
topk(Field, k)
```
### Parameters
* `Field`: The field or expression to rank the results by.
* `k`: The number of top results to return.
### Returns
A subset of the original dataset with the top `k` values based on the specified field.
## 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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# topkif
Source: https://axiom.co/docs/apl/aggregation-function/topkif
This page explains how to use the topkif aggregation in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| where method="GET" | top limit=5 status
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT status, COUNT(*)
FROM sample_http_logs
WHERE method = 'GET'
GROUP BY status
ORDER BY COUNT(*) DESC
LIMIT 5;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize topkif(status, 5, method == 'GET')
```
# Usage
## Syntax
```kusto theme={null}
topkif(Field, k, Condition)
```
## 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
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 `topkif` when analyzing HTTP logs to find the top 5 most frequent HTTP status codes for GET requests.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.\`
# variance
Source: https://axiom.co/docs/apl/aggregation-function/variance
This page explains how to use the variance aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats var(req_duration_ms) as variance
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT VAR_POP(req_duration_ms) FROM sample_http_logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize variance(req_duration_ms)
```
## Usage
### Syntax
```kusto theme={null}
summarize variance(Expression)
```
### 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
The function returns the variance (a numeric value) of the specified expression across the records.
## 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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# varianceif
Source: https://axiom.co/docs/apl/aggregation-function/varianceif
This page explains how to use the varianceif aggregation function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval filtered_var=if(status=="200",req_duration_ms,null())
| stats var(filtered_var)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT VARIANCE(CASE WHEN status = '200' THEN req_duration_ms END)
FROM sample_http_logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize varianceif(req_duration_ms, status == '200')
```
## Usage
### Syntax
```kusto theme={null}
summarize varianceif(Expr, Predicate)
```
### 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 the variance of `Expr` for the records where the `Predicate` is true. If no records match the condition, it returns `null`.
## 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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [**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.
# All features of Axiom Processing Language (APL)
Source: https://axiom.co/docs/apl/apl-features
This page gives an overview about all the features of Axiom Processing Language (APL).
| 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 | [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. |
# Map fields
Source: https://axiom.co/docs/apl/data-types/map-fields
This page explains what map fields are and how to query them.
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
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
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
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 theme={null}
['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
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
To create a map field using the Axiom API, send a request to the [Create map field](/restapi/endpoints/createMapField) endpoint. For example:
```bash theme={null}
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 `API_TOKEN` with the Axiom API token you have generated. For added security, store the API token in an environment variable.
Replace `DATASET_NAME` with the name of the Axiom dataset where you send your data.
Replace `MAP_FIELD` with the name of the field that you want to change to a map field.
## 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
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
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 theme={null}
{
"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 theme={null}
{
"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
This page explains how APL represents missing 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
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 theme={null}
print bool(null), datetime(null), dynamic(null), int(null), long(null), real(null), double(null), time(null)
```
## 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 (`==`): 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
This page explains the data types in APL.
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 (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
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
The `bool` data type supports the following operators: equality (`==`), inequality (`!=`), logical-and (`and`), and logical-or (`or`).
## 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
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
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** 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
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 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 represents a signed, 64-bit wide, integer.
### 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 represents a 64-bit wide, double-precision, floating-point number.
## The string data type
The **string** data type represents a sequence of zero or more [Unicode](https://home.unicode.org/) characters.
### 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 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** `(time)` data type represents a time interval.
## 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
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
This page explains how to use entity names in your APL query.
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
* 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
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 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
This guide helps you migrate SQL to APL, helping you understand key differences and providing you with query examples.
## 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)
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
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:
* **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
A basic APL query follows this structure:
```kusto theme={null}
|
|
|
|
```
## Query Examples
Let’s see some examples of how to convert SQL queries to APL.
## SELECT with a simple filter
**SQL:**
```sql theme={null}
SELECT *
FROM [Sample-http-logs]
WHERE method = 'GET';
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT Country, COUNT(*)
FROM [Sample-http-logs]
GROUP BY method;
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT TOP 10 Status, Method
FROM [Sample-http-logs]
ORDER BY Method DESC;
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT method, status, geo.country
FROM [Sample-http-logs]
WHERE resp_header_size_bytes >= 18;
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT geo.country
FROM [Sample-http-logs]
GROUP BY geo.country
HAVING COUNT(*) > 100;
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
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 theme={null}
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
**SQL:**
```sql theme={null}
SELECT SUM(resp_body_size_bytes) AS TotalBytes
FROM [Sample-http-logs];
```
**APL:**
```kusto theme={null}
[‘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
**SQL:**
```sql theme={null}
SELECT AVG(req_duration_ms) AS AverageRequest
FROM [Sample-http-logs];
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT MIN(req_duration_ms) AS MinRequest, MAX(req_duration_ms) AS MaxRequest
FROM [Sample-http-logs];
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT COUNT(DISTINCT method) AS UniqueMethods
FROM [Sample-http-logs];
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT STDDEV(req_duration_ms) AS StdDevRequest
FROM [Sample-http-logs];
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT VAR(req_duration_ms) AS VarRequest
FROM [Sample-http-logs];
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT COUNT(*) AS TotalDuration, SUM(req_duration_ms) AS TotalDuration, AVG(Price) AS AverageDuration
FROM [Sample-http-logs];
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
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 theme={null}
['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
**SQL:**
```sql theme={null}
SELECT COUNT(*) AS HighContentStatus
FROM [Sample-http-logs];
WHERE resp_header_size_bytes > 1;
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT Status
FROM [Sample-http-logs];
GROUP BY Status
HAVING COUNT(*) > 10;
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT content_type, COUNT(*) AS RequestCount
FROM [Sample-http-logs];
WHERE content_type = ‘text/csv’;
```
**APL:**
```kusto theme={null}
['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:
## Length of a string
**SQL:**
```sql theme={null}
SELECT LEN(Status) AS NameLength
FROM [Sample-http-logs];
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT CONCAT(content_type, ' ', method) AS FullLength
FROM [Sample-http-logs];
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT SUBSTRING(content_type, 1, 10) AS ShortDescription
FROM [Sample-http-logs];
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT LEFT(content_type, 3) AS LeftTitle, RIGHT(content_type, 3) AS RightTitle
FROM [Sample-http-logs];
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT REPLACE(StaTUS, 'old', 'new') AS UpdatedStatus
FROM [Sample-http-logs];
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT UPPER(FirstName) AS UpperFirstName, LOWER(LastName) AS LowerLastName
FROM [Sample-http-logs];
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT LTRIM(content_type) AS LeftTrimmedFirstName, RTRIM(content_type) AS RightTrimmedLastName
FROM [Sample-http-logs];
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT TRIM(content_type) AS TrimmedFirstName
FROM [Sample-http-logs];
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT REVERSE(Method) AS ReversedFirstName
FROM [Sample-http-logs];
```
**APL:**
```kusto theme={null}
['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
**SQL:**
```sql theme={null}
SELECT Status, Method
FROM “Sample-http-logs”
WHERE LOWER(Method) LIKE 'get’';
```
**APL:**
```kusto theme={null}
['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
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
This guide dives into why APL could be a superior choice for your data needs, and the differences between Sumo Logic and APL.
## 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
* **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
Let’s see some examples of how to convert SumoLogic queries to APL.
## 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 theme={null}
* | parse "From: * To: *" as (from, to)
```
**APL:**
```kusto theme={null}
['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
In this section, we will utilize a regular expression to identify the four octets of an IP address. This will help us efficiently extract the source IP addresses from the data.
**Sumo Logic:**
```bash theme={null}
*| parse regex "(\\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"
```
**APL:**
```kusto theme={null}
['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
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 theme={null}
_sourceCategory=apache
| parse "GET * " as url
```
**APL:**
```kusto theme={null}
['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
This section aims to identify and analyze traffic originating from the Source Category. We will extract 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 theme={null}
_sourceCategory=apache
| parse "* " as src_IP
| parse " 200 * " as size
| parse "GET * " as url
```
**APL:**
```kusto theme={null}
['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
In this part, we will compute the total number of bytes transferred to each source IP address. This will allow us to gauge the data volume associated with each source using APL.
**Sumo Logic:**
```bash theme={null}
_sourceCategory=apache
| parse "* " as src_IP
| parse " 200 * " as size
| count, sum(size) by src_IP
```
**APL:**
```kusto theme={null}
['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
In this section, we will calculate the average size of all successful HTTP responses. This metric helps us to understand the typical data load associated with successful server responses.
**Sumo Logic:**
```bash theme={null}
_sourceCategory=apache
| parse " 200 * " as size
| avg(size)
```
**APL:**
Get the average value from a string:
```kusto theme={null}
['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)
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 theme={null}
_sourceCategory=apache
| parse "* " as src_IP
| parse " 200 * " as size nodrop
| parse "GET * " as url
```
**APL:**
```kusto theme={null}
['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
This section is dedicated to identifying the frequency of visits to a specific URL. By counting these occurrences, we can gain insights into website popularity and user behavior.
**Sumo Logic:**
```bash theme={null}
_sourceCategory=apache
| parse "GET * " as url
| count by url
```
**APL:**
```kusto theme={null}
['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
In this section, we will identify the total number of pages associated with each source IP address. This analysis will allow us to understand the volume of content generated or hosted by each source.
**Sumo Logic:**
```bash theme={null}
_sourceCategory=apache
| parse "* -" as src_ip
| count by src_ip
```
**APL:**
```kusto theme={null}
['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
We aim to identify the total number of pages per source IP address in this section. 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 theme={null}
_sourceCategory=apache
| parse "* " as src_ip
| parse "GET * " as url
| count by url
| sort by _count
```
**APL:**
```kusto theme={null}
['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.
**Sumo Logic:**
```bash theme={null}
* | parse "GET * " as url
| count by url
| top 10 url by _count
```
**APL:**
```kusto theme={null}
['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
In this section, we aim to identify the top 10 source IP addresses based on their bandwidth consumption.
**Sumo Logic:**
```bash theme={null}
_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 theme={null}
['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
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 theme={null}
_sourceCategory=apache
| parse "* -" as src_ip
| count by src_ip
| top 100 src_ip by _count
```
**APL:**
```kusto theme={null}
['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
For the Source Category "apache", count by status\_code and timeslice of 1 hour.
**Sumo Logic:**
```bash theme={null}
_sourceCategory=apache*
| parse "HTTP/1.1\" * * \"" as (status_code, size)
| timeslice 1h
| count by _timeslice, status_code
```
**APL:**
```kusto theme={null}
['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
In this section, We aim to count instances by `status_code`, grouped into one-hour timeslices, and then transpose `status_code` to column format. This will help us understand the frequency and timing of different status codes.
**Sumo Logic:**
```bash theme={null}
_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
In this example, we will perform 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 theme={null}
_sourceCategory=apache*
| parse "HTTP/1.1\" * * \"" as (status_code, size)
| timeslice 5 buckets
| count by _timeslice, status_code
```
**APL:**
```kusto theme={null}
['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
In this example, we will count messages by status code categories. We will group all messages with status codes in the `200s`, `300s`, `400s`, and `500s` together, we are also groupint the method requests with the `GET`, `POST`, `PUT`, `DELETE` attributes. This will provide an overview of the response status distribution.
**Sumo Logic:**
```bash theme={null}
_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 theme={null}
['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
For the Source Category "apache", find all messages with a client error status code (40\*):
**Sumo Logic:**
```bash theme={null}
_sourceCategory=apache*
| parse "HTTP/1.1\" * * \"" as (status_code, size)
| where status_code matches "40*"
```
**APL:**
```kusto theme={null}
['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
In this query example, we aim to count the number of hits by browser. This analysis will provide insights into the different browsers used to access the source and their respective frequencies.
**Sumo Logic:**
```bash theme={null}
_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 theme={null}
['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.
**Sumo Logic:**
```bash theme={null}
* | parse "day=*:" as day_of_week
| where day_of_week in ("Saturday","Sunday")
```
**APL:**
```kusto theme={null}
['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
In this section, we will identify version numbers that match numeric values 2, 3, or 1. We will utilize the `num` operator to convert these strings into numerical format, facilitating easier analysis and comparison.
**Sumo Logic:**
```bash theme={null}
* | parse "Version=*." as number | num(number)
| where number in (2,3,6)
```
**APL:**
```kusto theme={null}
['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
As we’ve navigated through the process of migrating from Sumo Logic to APL, we hope you’ve 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 our APL [learning resources](/apl/guides/migrating-from-sql-to-apl) and [tutorials](/apl/tutorial) to become proficient in APL, and join our [community forums](http://axiom.co/discord) to engage with other APL users. Together, we 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
This step-by-step guide provides a high-level mapping from Splunk SPL to APL.
Splunk and Axiom are powerful tools for log analysis and data exploration. The data explorer interface 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
Splunk uses a `search` command for basic searching, while in APL, simply specify the dataset name followed by a filter.
**Splunk:**
```bash theme={null}
search index="myIndex" error
```
**APL:**
```kusto theme={null}
['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
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 theme={null}
Search index=”myIndex” error
| stats count
```
**APL:**
```kusto theme={null}
['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
In Splunk, the `stats` command is used for aggregation. In APL, perform aggregation using the `summarize` operator.
**Splunk:**
```bash theme={null}
search index="myIndex"
| stats count by status
```
**APL:**
```kusto theme={null}
['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
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 theme={null}
search index="myIndex" earliest=-1d@d latest=now
```
**APL:**
```kusto theme={null}
['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
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 theme={null}
search index="myIndex"
| sort - content_type
```
**APL:**
```kusto theme={null}
['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
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 theme={null}
index=main sourcetype=mySourceType
| fields status, responseTime
```
**APL:**
```kusto theme={null}
['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
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 theme={null}
index="myIndex" sourcetype="mySourceType"
| rename oldFieldName AS newFieldName
```
**APL:**
```kusto theme={null}
['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
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 theme={null}
search index="myIndex"
| eval newField=field1+field2
```
**APL:**
```kusto theme={null}
['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
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 are 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
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
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 we don’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
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
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
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
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
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
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
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.
# Axiom Processing Language (APL)
Source: https://axiom.co/docs/apl/introduction
This section explains how to use the Axiom Processing Language to get deeper insights from your data.
The 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.
## Prerequisites
* [Create an Axiom account](https://app.axiom.co/register).
* [Create a dataset in Axiom](/reference/datasets#create-dataset) where you send your data.
## 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 theme={null}
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
```kusto theme={null}
['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
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
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).
## What’s next
Check out the [list of example queries](/apl/tutorial) or explore the supported operators and functions:
* [Scalar functions](/apl/scalar-functions/)
* [Aggregation functions](/apl/aggregation-function/)
* [Tabular operators](/apl/tabular-operators/)
* [Scalar operators](/apl/scalar-operators/)
# Set statement
Source: https://axiom.co/docs/apl/query-statement/set-statement
The set statement is used to set a query option in your APL query.
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
```kusto theme={null}
set OptionName=OptionValue
```
## 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
```kusto theme={null}
set stricttypes;
['Dataset']
| where number == 5
```
# Special field attributes
Source: https://axiom.co/docs/apl/reference/special-field-attributes
This page explains how to implement special fields within APL queries to enhance the functionality and interactivity of datasets. Use these fields in APL queries to add unique behaviors to the Axiom user interface.
## 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
* 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
* 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
* 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
* 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
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 theme={null}
['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
This section explains how to use array functions in APL.
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
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.
# array_concat
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-concat
This page explains how to use the array_concat function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval combined_array = mvappend(array1, array2, array3)
```
```kusto APL equivalent theme={null}
| 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 theme={null}
SELECT array1 UNION ALL array2 UNION ALL array3
```
```kusto APL equivalent theme={null}
| extend combined_array = array_concat(array1, array2, array3)
```
## Usage
### Syntax
```kusto theme={null}
array_concat(array1, array2, ...)
```
### Parameters
* `array1`: The first array to concatenate.
* `array2`: The second array to concatenate.
* `...`: Additional arrays to concatenate.
### Returns
An array containing all elements from the input arrays in the order they are provided.
## 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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# array_extract
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-extract
This page explains how to use the array_extract function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval arr=mvappend("{\"id\":1,\"value\":true}", "{\"id\":2,\"value\":false}")
| spath input=arr path="{}.value" output=extracted_value
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT JSON_EXTRACT(data, '$[*].value') AS extracted_value
FROM my_table;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend extracted_value = array_extract(dynamic([{'id': 1, 'value': true}, {'id': 2, 'value': false}]), @'$[*].value')
| project _time, extracted_value
```
## Usage
### Syntax
```kusto theme={null}
array_extract(sourceArray, jsonPath)
```
### 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
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 `array_extract` to retrieve specific fields from structured arrays, such as arrays of request metadata.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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`.
# array_iff
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-iff
This page explains how to use the array_iff function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
eval selected_array=if(condition, array1, array2)
```
```kusto APL equivalent theme={null}
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 theme={null}
CASE WHEN condition THEN array1 ELSE array2 END
```
```kusto APL equivalent theme={null}
array_iff(condition_array, array1, array2)
```
## Usage
### Syntax
```kusto theme={null}
array_iff(condition_array, array1, array2)
```
### 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
An array where each element is selected from `array1` if the corresponding `condition_array` element is `true`, and from `array2` otherwise.
## 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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# array_index_of
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-index-of
This page explains how to use the array_index_of function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval index=mvfind(array, "value")
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT POSITION('value' IN ARRAY[...])
```
```kusto APL equivalent theme={null}
let index = array_index_of(array, 'value')
```
## Usage
### Syntax
```kusto theme={null}
array_index_of(array, lookup_value, [start], [length], [occurrence])
```
### 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
`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
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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# array_length
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-length
This page explains how to use the array_length function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval array_size = mvcount(array_field)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT CARDINALITY(array_field) AS array_size
FROM sample_table
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend array_size = array_length(array_field)
```
## Usage
### Syntax
```kusto theme={null}
array_length(array_expression)
```
### Parameters
* array\_expression: An expression representing the array to measure.
### Returns
The function returns an integer representing the number of elements in the specified array.
## Use case example
In OpenTelemetry traces, `array_length` can reveal the number of events associated with a span.
**Query**
```kusto theme={null}
['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
* [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.
# array_reverse
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-reverse
This page explains how to use the array_reverse function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
# SPL doesn’t have a direct array_reverse equivalent.
```
```kusto APL equivalent theme={null}
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 theme={null}
-- ANSI SQL lacks a built-in array reverse function.
```
```kusto APL equivalent theme={null}
let arr = dynamic([1, 2, 3, 4, 5]);
print reversed_arr = array_reverse(arr)
```
## Usage
### Syntax
```kusto theme={null}
array_reverse(array_expression)
```
### Parameters
* `array_expression`: The array you want to reverse. This array must be of a dynamic type.
### Returns
Returns the input array with its elements in reverse order.
## 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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# array_rotate_left
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-rotate-left
This page explains how to use the array_rotate_left function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval rotated_array = mvindex(array, 1) . "," . mvindex(array, 0)
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT array_column[2], array_column[3], array_column[0], array_column[1] FROM table
```
```kusto APL equivalent theme={null}
print rotated_array = array_rotate_left(dynamic([1,2,3,4]), 2)
```
## Usage
### Syntax
```kusto theme={null}
array_rotate_left(array, positions)
```
### 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
A new array where the elements have been rotated to the left by the specified number of positions.
## Use case example
Analyze traces by rotating the field order for visualization or pattern matching.
**Query**
```kusto theme={null}
['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 theme={null}
[
{
"name": "Enqueued",
"timestamp": 1733997117722909000
},
{
"timestamp": 1733997117722911700,
"name": "Sent"
},
{
"name": "ResponseReceived",
"timestamp": 1733997117723591400
}
]
```
```json rotated_sequence theme={null}
[
{
"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
* [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.
# array_rotate_right
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-rotate-right
This page explains how to use the array_rotate_right function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval rotated_array=mvindex(array, -3)
```
```kusto APL equivalent theme={null}
| 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 theme={null}
-- No direct ANSI SQL equivalent for array rotation
```
```kusto APL equivalent theme={null}
| extend rotated_array = array_rotate_right(array_column, 3)
```
## Usage
### Syntax
```kusto theme={null}
array_rotate_right(array, count)
```
### Parameters
* `array`: An array to rotate.
* `count`: An integer specifying the number of positions to rotate the array to the right.
### Returns
An array where the elements are rotated to the right by the specified `count`.
## 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 theme={null}
['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 theme={null}
[
{
"attributes": null,
"name": "Enqueued",
"timestamp": 1733997421220380700
},
{
"name": "Sent",
"timestamp": 1733997421220390400,
"attributes": null
},
{
"attributes": null,
"name": "ResponseReceived",
"timestamp": 1733997421221118500
}
]
```
```json rotated_sequence theme={null}
[
{
"attributes": null,
"name": "ResponseReceived",
"timestamp": 1733997421221118500
},
{
"attributes": null,
"name": "Enqueued",
"timestamp": 1733997421220380700
},
{
"name": "Sent",
"timestamp": 1733997421220390400,
"attributes": null
}
]
```
## 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.
# array_select_dict
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-select-dict
This page explains how to use the array_select_dict function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval selected = mvfilter(array, 'key' == 5)
```
```kusto APL equivalent theme={null}
| 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 theme={null}
SELECT *
FROM my_table
WHERE JSON_CONTAINS(array_column, '{"key": 5}')
```
```kusto APL equivalent theme={null}
| project selected = array_select_dict(array_column, "key", 5)
```
## Usage
### Syntax
```kusto theme={null}
array_select_dict(array, key, value)
```
### 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
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
This example demonstrates how to use `array_select_dict` to extract a dictionary where the key `service.name` has the value `frontend`.
**Query**
```kusto theme={null}
['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
* [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.
# array_shift_left
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-shift-left
This page explains how to use the array_shift_left function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval rotated_array = mvindex(array, 1) . mvindex(array, 0)
```
```kusto APL equivalent theme={null}
['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 theme={null}
-- Pseudo code in SQL
SELECT ARRAY_SHIFT_LEFT(array_column, shift_amount)
```
```kusto APL equivalent theme={null}
['array_shift_left'](array_column, shift_amount)
```
## Usage
### Syntax
```kusto theme={null}
['array_shift_left'](array, shift_amount)
```
### Parameters
| Parameter | Type | Description |
| -------------- | ------- | ------------------------------------------------------ |
| `array` | Array | The array to shift. |
| `shift_amount` | Integer | The number of positions to shift elements to the left. |
### 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
Reorganize span events to analyze dependencies in a different sequence.
**Query**
```kusto theme={null}
['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 theme={null}
[
{
"name": "Enqueued",
"timestamp": 1734001111273917000,
"attributes": null
},
{
"attributes": null,
"name": "Sent",
"timestamp": 1734001111273925400
},
{
"name": "ResponseReceived",
"timestamp": 1734001111274167300,
"attributes": null
}
]
```
```json shifted_events theme={null}
[
{
"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
* [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.
# array_shift_right
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-shift-right
This page explains how to use the array_shift_right function in APL.
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
* To manage and rotate data within arrays.
* To implement cyclic operations or transformations.
* To manipulate array data structures in log analysis or telemetry contexts.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval shifted_array=mvappend(mvindex(array,-1),mvindex(array,0,len(array)-1))
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['dataset.name']
| extend shifted_array = array_shift_right(array)
```
## Usage
### Syntax
```kusto theme={null}
array_shift_right(array: array) : array
```
### Parameters
| Parameter | Type | Description |
| --------- | ----- | ------------------------------------------ |
| `array` | array | The input array whose elements are shifted |
### 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
Reorganize span events in telemetry data for visualization or debugging.
**Query**
```kusto theme={null}
['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 theme={null}
[
{
"name": "Enqueued",
"timestamp": 1734001215487927300,
"attributes": null
},
{
"attributes": null,
"name": "Sent",
"timestamp": 1734001215487937000
},
{
"timestamp": 1734001215488191000,
"attributes": null,
"name": "ResponseReceived"
}
]
```
```json shifted_events theme={null}
[
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
* [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.
# array_slice
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-slice
This page explains how to use the array_slice function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval sliced_array=mvindex(my_array, 1, 3)
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT JSON_EXTRACT(my_array, '$[1:3]') AS sliced_array FROM my_table
```
```kusto APL equivalent theme={null}
T | extend sliced_array = array_slice(my_array, 1, 3)
```
## Usage
### Syntax
```kusto theme={null}
array_slice(array, start, end)
```
### 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
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
Filter spans from trace data to analyze a specific range of events.
**Query**
```kusto theme={null}
['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 theme={null}
[
{
"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 theme={null}
[
{
"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
* [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.
# array_sort_asc
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-sort-asc
This page explains how to use the array_sort_asc function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval sorted_values = mvsort(multivalue_field)
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT val
FROM UNNEST([4, 2, 5]) AS val
ORDER BY val ASC
```
```kusto APL equivalent theme={null}
print sorted_arr = array_sort_asc(dynamic([4, 2, 5]))
```
## Usage
### Syntax
```kusto theme={null}
array_sort_asc(array)
```
### Parameters
| Name | Type | Required | Description |
| ----- | ------- | -------- | ------------------------------------------------------------------------- |
| array | dynamic | ✓ | An array of values to sort. Can be numbers, strings, or other primitives. |
### Returns
A new array that contains the same elements as the input array, sorted in ascending order. If the input is not an array, the function returns an empty array.
## Example
**Query**
```kusto theme={null}
['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 theme={null}
[
[
"a",
"i",
"m",
"o",
"x"
]
]
```
## 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.
# array_sort_desc
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-sort-desc
This page explains how to use the array_sort_desc function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | stats list(duration) as durations by id
... | eval durations=reverse(mvsort(durations))
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT trace_id, ARRAY_AGG(duration ORDER BY duration DESC) AS durations
FROM traces
GROUP BY trace_id;
```
```kusto APL equivalent theme={null}
['otel-demo-traces']
| summarize durations=make_list(duration) by trace_id
| extend durations=array_sort_desc(durations)
```
## Usage
### Syntax
```kusto theme={null}
array_sort_desc(array)
```
### Parameters
| Name | Type | Required | Description |
| ----- | ----- | -------- | ----------------------------------------------------- |
| array | array | ✓ | The input array whose elements are sorted descending. |
### 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
**Query**
```kusto theme={null}
['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 theme={null}
[
[
"x",
"o",
"m",
"i",
"a"
]
]
```
## 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.
# array_split
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-split
This page explains how to use the array_split function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval split_array = mvzip(array_field, "2")
```
```kusto APL equivalent theme={null}
['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 theme={null}
-- SQL typically requires custom functions or JSON manipulation.
SELECT * FROM dataset WHERE JSON_ARRAY_LENGTH(array_field) > 0;
```
```kusto APL equivalent theme={null}
['otel-demo-traces']
| extend split_array = array_split(events, 2)
```
## Usage
### Syntax
```kusto theme={null}
array_split(array, index)
```
### 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 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
### Single split index
Split large event arrays into manageable chunks for analysis.
```kusto theme={null}
['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 theme={null}
[
{
"timestamp": 1734033733465219300,
"name": "Enqueued"
},
{
"name": "Sent",
"timestamp": 1734033733465228500
},
{
"timestamp": 1734033733465455900,
"name": "ResponseReceived"
}
]
```
```json split_events theme={null}
[
[
{
"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
Divide traces into fixed-size segments for better debugging.
**Query**
```kusto theme={null}
['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 theme={null}
[
{
"attributes": null,
"name": "Enqueued",
"timestamp": 1734034755085206000
},
{
"name": "Sent",
"timestamp": 1734034755085215500,
"attributes": null
},
{
"attributes": null,
"name": "ResponseReceived",
"timestamp": 1734034755085424000
}
]
```
```json split_events theme={null}
[
[
{
"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
* [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.
# array_sum
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/array-sum
This page explains how to use the array_sum function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval total_duration = mvsum(duration_array)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT SUM(value) AS total_duration
FROM UNNEST(duration_array) AS value;
```
```kusto APL equivalent theme={null}
['dataset.name']
| extend total_duration = array_sum(duration_array)
```
## Usage
### Syntax
```kusto theme={null}
array_sum(array_expression)
```
### Parameters
| Parameter | Type | Description |
| ------------------ | ----- | ------------------------------------------ |
| `array_expression` | array | An array of numerical values to be summed. |
### 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
Summing the duration of all events in an array field.
**Query**
```kusto theme={null}
['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
* [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.
# bag_has_key
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/bag-has-key
This page explains how to use the bag_has_key function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval hasKey=if(isnull(spath(data, "keyName")), false, true)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT *
FROM logs
WHERE JSON_EXTRACT(json_column, '$.keyName') IS NOT NULL
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| where bag_has_key(dynamic_field, 'keyName')
```
## Usage
### Syntax
```kusto theme={null}
bag_has_key(bag: dynamic, key: string)
```
### Parameters
| Name | Type | Description |
| ----- | --------- | ---------------------------------------------------------------- |
| `bag` | `dynamic` | A dynamic value representing a property bag (e.g., JSON object). |
| `key` | `string` | The key to check for within the property bag. |
### Returns
Returns a `bool` value:
* `true` if the specified key exists in the property bag
* `false` otherwise
## Use case examples
Use `bag_has_key` to filter log entries that include a specific metadata key embedded in a dynamic object.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# bag_keys
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/bag-keys
This page explains how to use the bag_keys function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval key_list=keys(data_field)
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT JSON_OBJECT_KEYS(data) FROM logs;
```
```kusto APL equivalent theme={null}
datatable(data: dynamic)
[
dynamic({ "ip": "127.0.0.1", "status": "200", "method": "GET" })
]
| extend keys = bag_keys(data)
```
## Usage
### Syntax
```kusto theme={null}
bag_keys(bag)
```
### Parameters
| Name | Type | Description |
| ----- | --------- | -------------------------------------------------- |
| `bag` | `dynamic` | The dynamic object whose keys you want to extract. |
### 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 `bag_keys` to audit dynamic metadata fields in HTTP logs where each record contains a nested object representing additional request attributes.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# bag_pack
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/bag-pack
This page explains how to use the bag_pack and pack functions in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval metadata=tojson({"status": status, "duration": req_duration_ms})
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT JSON_OBJECT('status' VALUE status, 'duration' VALUE req_duration_ms) AS metadata FROM logs;
```
```kusto APL equivalent theme={null}
project metadata = bag_pack('status', status, 'duration', req_duration_ms)
```
## Usage
### Syntax
```kusto theme={null}
bag_pack(key1, value1, key2, value2, ...)
```
### 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
A `dynamic` value representing a property bag (dictionary) where keys are strings and values are the corresponding values.
## Use case examples
Use `bag_pack` to create a structured object that captures key request attributes for easier inspection or export.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# bag_zip
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/bag-zip
This page explains how to use the bag_zip function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval zipped=mvzip(keys, values, '=')
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT JSON_OBJECT('key1', value1, 'key2', value2)
FROM table_name
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend keys = dynamic(['key1', 'key2'])
| extend values = dynamic([value1, value2])
| extend result = bag_zip(keys, values)
```
## Usage
### Syntax
```kusto theme={null}
bag_zip(keys, values)
```
### 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
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 `bag_zip` to combine metadata keys and values extracted from HTTP logs into structured objects for easier analysis.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# isarray
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/isarray
This page explains how to use the isarray function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval is_array=if(isnotnull(mvcount(field)), "true", "false")
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT CASE
WHEN JSON_TYPE(field) = 'ARRAY' THEN TRUE
ELSE FALSE
END AS is_array
FROM dataset_name;
```
```kusto APL equivalent theme={null}
['dataset.name']
| extend is_array=isarray(field)
```
## Usage
### Syntax
```kusto theme={null}
isarray(value)
```
### Parameters
| Parameter | Description |
| --------- | ------------------------------------ |
| `value` | The value to check if it’s an array. |
### Returns
A boolean value:
* `true` if the specified value is an array.
* `false` otherwise.
## Use case example
Filter for records where the `events` field contains an array.
**Query**
```kusto theme={null}
['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
* [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.
# len
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/len
This page explains how to use the len function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval uri_length=len(uri)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT LENGTH(uri) AS uri_length FROM http_logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend uri_length = len(uri)
```
## Usage
### Syntax
```kusto theme={null}
len(value)
```
### Parameters
| Name | Type | Description |
| ----- | --------------- | ------------------------------------------------- |
| value | string or array | The input to measure—either a string or an array. |
### 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 `len` to find requests with long URIs, which might indicate poorly designed endpoints or potential abuse.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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`.
# pack_array
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/pack-array
This page explains how to use the pack_array function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval array_field = mvappend(value1, value2, value3)
```
```kusto APL equivalent theme={null}
| 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 theme={null}
SELECT ARRAY[value1, value2, value3] AS array_field;
```
```kusto APL equivalent theme={null}
| extend array_field = pack_array(value1, value2, value3)
```
## Usage
### Syntax
```kusto theme={null}
pack_array(value1, value2, ..., valueN)
```
### 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
An array containing the specified values in the order they are provided.
## Use case example
Use `pack_array` to consolidate span data into an array for a trace summary.
**Query**
```kusto theme={null}
['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
* [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.
# pack_dictionary
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/pack-dictionary
This page explains how to use the pack_dictionary function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval dict=mvmap("key1", value1, "key2", value2)
```
```kusto APL equivalent theme={null}
| 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 theme={null}
SELECT '{"key1":' || value1 || ',"key2":' || value2 || '}' AS dict FROM my_table;
```
```kusto APL equivalent theme={null}
| extend dict = pack_dictionary('key1', value1, 'key2', value2)
```
## Usage
### Syntax
```kusto theme={null}
pack_dictionary(key1, value1, key2, value2, ...)
```
### 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
A dynamic object that represents a dictionary where each key maps to its associated value.
## Use case examples
Use `pack_dictionary` to store request metadata in a compact format for structured inspection or export.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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`.
# strcat_array
Source: https://axiom.co/docs/apl/scalar-functions/array-functions/strcat-array
This page explains how to use the strcat_array function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval concatenated=mvjoin(array_field, ", ")
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT STRING_AGG(column_name, ', ') AS concatenated FROM table;
```
```kusto APL equivalent theme={null}
dataset
| summarize concatenated = strcat_array(column_name, ', ')
```
## Usage
### Syntax
```kusto theme={null}
strcat_array(array, delimiter)
```
### 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
A single concatenated string with the array’s elements separated by the specified delimiter.
## Use case example
You can use `strcat_array` to combine HTTP methods and URLs for a quick summary of unique request paths.
**Query**
```kusto theme={null}
['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
* [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.
# Conditional functions
Source: https://axiom.co/docs/apl/scalar-functions/conditional-function
Learn how to use and combine different conditional functions in APL
## Conditional functions
| **Function Name** | **Description** |
| ----------------- | ----------------------------------------------------------------------------------------------------------- |
| [case](#case) | Evaluates a list of conditions and returns the first result expression whose condition is satisfied. |
| [iff](#iff) | Evaluates the first argument (the predicate), and returns the value of either the second or third arguments |
## case
Evaluates a list of conditions and returns the first result whose condition is satisfied.
### Arguments
* condition: An expression that evaluates to a Boolean.
* result: An expression that Axiom evaluates and returns the value if its condition is the first that evaluates to true.
* nothingMatchedResult: An expression that Axiom evaluates and returns the value if none of the conditional expressions evaluates to true.
### Returns
Axiom returns the value of the first result whose condition evaluates to true. If none of the conditions is satisfied, Axiom returns the value of `nothingMatchedResult`.
### Example
```kusto theme={null}
case(condition1, result1, condition2, result2, condition3, result3, ..., nothingMatchedResult)
```
```kusto theme={null}
['sample-http-logs'] |
extend status_human_readable = case(
status_int == 200,
'OK',
status_int == 201,
'Created',
status_int == 301,
'Moved Permanently',
status_int == 500,
'Internal Server Error',
'Other'
)
```
[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%20status_code%20%3D%20case\(status_int%20%3D%3D%20200%2C%20'OK'%2C%20status_int%20%3D%3D%20201%2C%20'Created'%2C%20status_int%20%3D%3D%20301%2C%20'Moved%20Permanently'%2C%20status_int%20%3D%3D%20500%2C%20'Internal%20Server%20Error'%2C%20'Other'\)%22%7D)
## iff
Evaluates the first argument (the predicate), and returns the value of either the second or third arguments. The second and third arguments must be of the same type.
The `iif` function is equivalent to the `iff` function.
### Arguments
* predicate: An expression that evaluates to a boolean value.
* ifTrue: An expression that gets evaluated and its value returned from the function if predicate evaluates to `true`.
* ifFalse: An expression that gets evaluated and its value returned from the function if predicate evaluates to `false`.
### Returns
This function returns the value of ifTrue if predicate evaluates to true, or the value of ifFalse otherwise.
### Examples
```kusto theme={null}
iff(predicate, ifTrue, ifFalse)
```
```kusto theme={null}
['sample-http-logs']
| project Status = iff(req_duration_ms == 1, "numeric", "Inactive")
```
[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%20Status%20%3D%20iff%28req_duration_ms%20%3D%3D%201%2C%20%5C%22numeric%5C%22%2C%20%5C%22Inactive%5C%22%29%22%7D)
To return a null value from `iff`, use `dynamic(null)`.
```kusto theme={null}
iff(condition, dynamic(null), value)
```
# Conversion functions
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions
Learn how to use and combine different conversion functions in APL
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. |
# dynamic_to_json
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/dynamic-to-json
This page explains how to use the dynamic_to_json function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval json_output = tojson({"key": "value"})
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT JSON_OBJECT('key' VALUE 'value') AS json_output FROM dual;
```
```kusto APL equivalent theme={null}
print dynamic_value = bag_pack("key", "value")
| extend json_output = dynamic_to_json(dynamic_value)
```
## Usage
### Syntax
```kusto theme={null}
dynamic_to_json(dynamic)
```
### Parameters
| Name | Type | Description |
| ------- | ------- | ---------------------------------------------- |
| dynamic | dynamic | The dynamic value to convert to a JSON string. |
### 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
Convert a dynamic object containing request metadata to a JSON string for logging or export purposes.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# ensure_field
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/ensure-field
This page explains how to use the ensure_field function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval status = if(isnull(status_code), null(), status_code)
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT COALESCE(optional_field, NULL) AS field_value FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend field_value = ensure_field('optional_field', typeof(string))
```
## Usage
### Syntax
```kusto theme={null}
ensure_field(field_name, field_type)
```
### 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
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
Handle missing fields gracefully when analyzing HTTP logs where some fields might not be present in all records.
**Query**
```kusto theme={null}
['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 theme={null}
['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
* [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 is not 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.
# isbool
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/isbool
This page explains how to use the isbool function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval is_boolean = if(isbool(field), 1, 0)
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT CASE WHEN field IN (0, 1, TRUE, FALSE) THEN 1 ELSE 0 END AS is_boolean FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend is_boolean = isbool(field)
```
## Usage
### Syntax
```kusto theme={null}
isbool(expression)
```
### Parameters
| Name | Type | Description |
| ---------- | ------- | ----------------------------------------- |
| expression | dynamic | The expression to check for boolean type. |
### Returns
Returns `true` if the expression value is a boolean, `false` otherwise.
## Use case examples
Validate that a field contains boolean values before using it in boolean operations or filters.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# toarray
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/toarray
This page explains how to use the toarray function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval methods=split("GET,POST,PUT", ",") | mvcount(methods)
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT JSON_ARRAY_LENGTH('["GET","POST","PUT"]')
```
```kusto APL equivalent theme={null}
print methods = dynamic(["GET", "POST", "PUT"])
| extend method_count = array_length(toarray(methods))
```
## Usage
### Syntax
```kusto theme={null}
toarray(value)
```
### Parameters
| Name | Type | Description |
| ----- | ------- | ---------------------------------------- |
| value | dynamic | A JSON array, property bag, or bag value |
### 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
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 theme={null}
['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
* [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`.
# tobool
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/tobool
This page explains how to use the tobool function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval is_active = if(field == "true" OR field == 1, 1, 0)
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT CASE WHEN status = 'active' THEN TRUE ELSE FALSE END AS is_active FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend is_active = tobool(is_active)
```
## Usage
### Syntax
```kusto theme={null}
tobool(value)
```
### Parameters
| Name | Type | Description |
| ----- | ------- | -------------------------------- |
| value | dynamic | The value to convert to boolean. |
### Returns
If conversion is successful, the result is a boolean. If conversion isn’t successful, the result is `false`.
### Conversion behavior
The `tobool` function converts values based on their type:
* **Boolean**: Returns the value unchanged.
* **Integer/Float/Duration**: Returns `true` if the value is not 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
Convert string representations of Boolean values to actual Boolean types for filtering and analysis.
**Query**
```kusto theme={null}
['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
* [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.
# todatetime
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/todatetime
This page explains how to use the todatetime function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval timestamp = strptime(date_field, "%Y-%m-%d %H:%M:%S")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT CAST('2022-11-13' AS DATETIME) AS date_value FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend date_value = todatetime('2022-11-13')
```
## Usage
### Syntax
```kusto theme={null}
todatetime(value)
```
### Parameters
| Name | Type | Description |
| ----- | ------- | --------------------------------- |
| value | dynamic | The value to convert to datetime. |
### Returns
If the conversion is successful, the result is a datetime value. If the conversion isn't successful, the result is `null`.
### 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
Convert date strings from log fields to datetime values for time-based filtering and analysis.
**Query**
```kusto theme={null}
['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
* [totimespan](/apl/scalar-functions/conversion-functions/totimespan): Converts input to timespan. Use `totimespan` for duration values, and `todatetime` for absolute time points.å
# todouble, toreal
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/todouble
This page explains how to use the todouble function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval price = tonumber(price_string)
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT CAST('1567.89' AS DOUBLE) AS price FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend price = todouble('1567.89')
```
## Usage
### Syntax
```kusto theme={null}
todouble(value)
toreal(value)
```
### Parameters
| Name | Type | Description |
| ----- | ------- | ----------------------------- |
| value | dynamic | The value to convert to real. |
### Returns
If conversion is successful, the result is a value of type `real`. If conversion isn't successful, the result is `null`.
### 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
Convert string representations of numeric values to real numbers for mathematical calculations and aggregations.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# todynamic
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/todynamic
This page explains how to use the todynamic function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval json_field = json_extract(raw_field, "$.key")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT JSON_VALUE(raw_column, '$.key') AS value FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend value = todynamic(raw_column).key
```
## Usage
### Syntax
```kusto theme={null}
todynamic(value)
```
### Parameters
| Name | Type | Description |
| ----- | ------ | ----------------------------------------------------- |
| value | string | A string representing a JSON-encoded object or array. |
### Returns
A dynamic value. If the input isn’t a valid JSON string, the function returns `null`.
## 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 theme={null}
['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
* [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`.
# tohex
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/tohex
This page explains how to use the tohex function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval hex_value = printf("%x", numeric_field)
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT TO_HEX(546) AS hex_value FROM dual;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend hex_value = tohex(546)
```
## Usage
### Syntax
```kusto theme={null}
tohex(value [, minLength])
```
### 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
If conversion is successful, the result is a string value representing the hexadecimal representation. If conversion isn't successful, the result is `null`.
## Example
Convert numeric identifiers to hexadecimal format for display or integration with systems that use hex identifiers.
**Query**
```kusto theme={null}
['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
* [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.
# toint, tolong
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/toint
This page explains how to use the toint function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval status_code = tonumber(status_string)
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT CAST('456' AS INT) AS status_code FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend status_code = toint('456')
```
## Usage
### Syntax
```kusto theme={null}
toint(value)
```
### Parameters
| Name | Type | Description |
| ----- | ------- | -------------------------------- |
| value | dynamic | The value to convert to integer. |
### Returns
If the conversion is successful, the result is an integer. If the conversion isn't successful, the result is `null`.
### 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
Convert string representations of HTTP status codes to integers for numeric comparisons and filtering.
**Query**
```kusto theme={null}
['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
* [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.
# tostring
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/tostring
This page explains how to use the tostring function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval status_str = tostring(status_code)
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT CAST(123 AS VARCHAR) AS status_str FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend status_str = tostring(123)
```
## Usage
### Syntax
```kusto theme={null}
tostring(value)
```
### Parameters
| Name | Type | Description |
| ----- | ------- | ------------------------------- |
| value | dynamic | The value to convert to string. |
### 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
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
Convert trace attributes to strings for string-based analysis and reporting.
**Query**
```kusto theme={null}
['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
* [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.
# totimespan
Source: https://axiom.co/docs/apl/scalar-functions/conversion-functions/totimespan
This page explains how to use the totimespan function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval duration = duration_field
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT INTERVAL '1' DAY AS duration FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend duration = totimespan('24h')
```
## Usage
### Syntax
```kusto theme={null}
totimespan(value)
```
### Parameters
| Name | Type | Description |
| ----- | ------- | --------------------------------- |
| value | dynamic | The value to convert to timespan. |
### Returns
If conversion is successful, the result is a timespan value. If conversion isn't successful, the result is `null`.
### 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
Convert numeric duration values to timespan format for duration-based analysis and filtering.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [todatetime](/apl/scalar-functions/conversion-functions/todatetime): Converts input to datetime. Use `todatetime` for absolute time points, and `totimespan` for duration values.
# Datetime functions
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions
Learn how to use and combine different datetime functions in APL
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).
# ago
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/ago
This page explains how to use the ago function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | where _time > relative_time(now(), "-6h@h")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT * FROM events WHERE timestamp_column > CURRENT_TIMESTAMP - INTERVAL '6' HOUR;
```
```kusto APL equivalent theme={null}
['dataset']
| where _time > ago(6h)
```
## Usage
### Syntax
```kusto theme={null}
ago(timespan)
```
### Parameters
| Name | Type | Description |
| -------- | ---------- | --------------------------------------------------- |
| timespan | `timespan` | The timespan to subtract from the current UTC time. |
### Returns
A `datetime` value equal to `now() - timespan`.
## Use case examples
Filter HTTP logs from the last 6 hours and count requests by status code.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# datetime_add
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/datetime-add
This page explains how to use the datetime_add function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval new_time=relative_time(_time, "+1mon")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT DATEADD(month, 1, timestamp_column) AS new_time FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend new_time = datetime_add('month', 1, _time)
```
## Usage
### Syntax
```kusto theme={null}
datetime_add(part, value, datetime)
```
### 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
A `datetime` value after adding the specified interval to the base datetime.
## Use case examples
Project what the time is 1 hour after each request to estimate cache expiration windows.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# datetime_diff
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/datetime-diff
This page explains how to use the datetime_diff function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval hours_diff=round((_time - relative_time(now(), "-1d@d")) / 3600)
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT DATEDIFF(hour, start_time, end_time) AS hours_diff FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend hours_diff = datetime_diff('hour', end_time, start_time)
```
## Usage
### Syntax
```kusto theme={null}
datetime_diff(part, datetime1, datetime2)
```
### 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
A `long` representing the number of periods of the specified unit in the result of `datetime1 - datetime2`.
## Use case examples
Calculate how many hours ago each request occurred.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# datetime_part
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/datetime-part
This page explains how to use the datetime_part function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval hour=strftime(_time, "%H")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT EXTRACT(HOUR FROM timestamp_column) AS hour FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend hour = datetime_part('hour', _time)
```
## Usage
### Syntax
```kusto theme={null}
datetime_part(part, datetime)
```
### 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
An `int` representing the value of the extracted date part.
## Use case examples
Analyze request volume by hour of day to find peak traffic periods.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# dayofmonth
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/dayofmonth
This page explains how to use the dayofmonth function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval day=strftime(_time, "%d")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT EXTRACT(DAY FROM timestamp_column) AS day FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend day = dayofmonth(_time)
```
## Usage
### Syntax
```kusto theme={null}
dayofmonth(datetime)
```
### Parameters
| Name | Type | Description |
| -------- | ---------- | ------------------------- |
| datetime | `datetime` | The input datetime value. |
### Returns
An `int` from 1 to 31 representing the day number of the month.
## Use case examples
Count requests by day of month to find recurring traffic patterns.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# dayofweek
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/dayofweek
This page explains how to use the dayofweek function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval day=strftime(_time, "%w")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT EXTRACT(DOW FROM timestamp_column) AS day FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend day = dayofweek(_time)
```
## Usage
### Syntax
```kusto theme={null}
dayofweek(datetime)
```
### Parameters
| Name | Type | Description |
| -------- | ---------- | ------------------------- |
| datetime | `datetime` | The input datetime value. |
### 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
Analyze request volume by day of the week to identify peak traffic days.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# dayofyear
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/dayofyear
This page explains how to use the dayofyear function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval doy=strftime(_time, "%j")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT EXTRACT(DOY FROM timestamp_column) AS day_of_year FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend day_of_year = dayofyear(_time)
```
## Usage
### Syntax
```kusto theme={null}
dayofyear(datetime)
```
### Parameters
| Name | Type | Description |
| -------- | ---------- | ------------------------- |
| datetime | `datetime` | The input datetime value. |
### Returns
An `int` from 1 to 366 representing the day number within the year.
## Use case examples
Track daily request counts across the year to spot high-traffic and low-traffic periods.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# endofday
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/endofday
This page explains how to use the endofday function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval end=relative_time(_time, "@d+1d-1s")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT DATE_TRUNC('day', timestamp_column) + INTERVAL '1 day' - INTERVAL '1 second' AS end_of_day FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend end_of_day = endofday(_time)
```
## Usage
### Syntax
```kusto theme={null}
endofday(datetime [, offset])
```
### 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
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
Calculate how far each request is from the end of the day to understand the distribution of traffic within daily windows.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# endofmonth
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/endofmonth
This page explains how to use the endofmonth function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval month_end=relative_time(now(), "@mon+1mon-1d@d+86399")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT DATE_TRUNC('month', timestamp_column) + INTERVAL '1 month' - INTERVAL '1 second' AS month_end FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend month_end = endofmonth(_time)
```
## Usage
### Syntax
```kusto theme={null}
endofmonth(datetime [, offset])
```
### 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
A `datetime` representing the last moment of the month for the given date, shifted by the offset if specified.
## Use case examples
Count requests by month boundary to track monthly traffic volume.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# endofweek
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/endofweek
This page explains how to use the endofweek function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval week_end=relative_time(now(), "@w7+6d@d+86399")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT DATE_TRUNC('week', timestamp_column) + INTERVAL '7 days' - INTERVAL '1 second' AS week_end FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend week_end = endofweek(_time)
```
## Usage
### Syntax
```kusto theme={null}
endofweek(datetime [, offset])
```
### 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
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
Summarize total requests per week to track weekly traffic volume.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# endofyear
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/endofyear
This page explains how to use the endofyear function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval year=strftime(_time, "%Y") | eval year_end=year."-12-31T23:59:59"
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT DATE_TRUNC('year', timestamp_column) + INTERVAL '1 year' - INTERVAL '1 second' AS year_end FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend year_end = endofyear(_time)
```
## Usage
### Syntax
```kusto theme={null}
endofyear(datetime [, offset])
```
### 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
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
Count total HTTP requests per year to understand annual traffic volume.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# getmonth
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/getmonth
This page explains how to use the getmonth function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval month=strftime(_time, "%m")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT EXTRACT(MONTH FROM timestamp_column) AS month FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend month = getmonth(_time)
```
## Usage
### Syntax
```kusto theme={null}
getmonth(datetime)
```
### Parameters
| Name | Type | Description |
| -------- | ---------- | ------------------------- |
| datetime | `datetime` | The input datetime value. |
### Returns
An `int` from 1 to 12 representing the month number.
## Use case examples
Analyze HTTP request volume by month to identify traffic patterns throughout the year.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# getyear
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/getyear
This page explains how to use the getyear function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval year=strftime(_time, "%Y")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT EXTRACT(YEAR FROM timestamp_column) AS year FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend year = getyear(_time)
```
## Usage
### Syntax
```kusto theme={null}
getyear(datetime)
```
### Parameters
| Name | Type | Description |
| -------- | ---------- | ------------------------- |
| datetime | `datetime` | The input datetime value. |
### Returns
An `int` representing the year.
## Use case examples
Count HTTP requests by year to understand long-term traffic trends.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# hourofday
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/hourofday
This page explains how to use the hourofday function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval hour=strftime(_time, "%H")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT EXTRACT(HOUR FROM timestamp_column) AS hour FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend hour = hourofday(_time)
```
## Usage
### Syntax
```kusto theme={null}
hourofday(datetime)
```
### Parameters
| Name | Type | Description |
| -------- | ---------- | ------------------------- |
| datetime | `datetime` | The input datetime value. |
### Returns
An `int` from 0 to 23 representing the hour of the day.
## Use case examples
Analyze HTTP request volume by hour to identify peak traffic periods.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# monthofyear
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/monthofyear
This page explains how to use the monthofyear function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval month=strftime(_time, "%m")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT EXTRACT(MONTH FROM timestamp_column) AS month FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend month = monthofyear(_time)
```
## Usage
### Syntax
```kusto theme={null}
monthofyear(datetime)
```
### Parameters
| Name | Type | Description |
| -------- | ---------- | ------------------------- |
| datetime | `datetime` | The input datetime value. |
### Returns
An `int` from 1 to 12 representing the month number of the year.
## Use case examples
Analyze average request duration by month to identify seasonal performance variations.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# now
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/now
This page explains how to use the now function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval current_time=now()
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT CURRENT_TIMESTAMP AS current_time;
```
```kusto APL equivalent theme={null}
['dataset']
| extend current_time = now()
```
## Usage
### Syntax
```kusto theme={null}
now([offset])
```
### Parameters
| Name | Type | Description |
| ------ | ---------- | ------------------------------------------------------------------------- |
| offset | `timespan` | Optional: A timespan added to the current UTC clock time. Default is `0`. |
### Returns
The current UTC clock time as a `datetime`. All references to `now()` within a single statement return the same value.
## Use case examples
Calculate the age of each request in hours to understand how recent events are.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# startofday
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/startofday
This page explains how to use the startofday function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval day_start=relative_time(_time, "@d")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT DATE_TRUNC('day', timestamp_column) AS day_start FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend day_start = startofday(_time)
```
## Usage
### Syntax
```kusto theme={null}
startofday(datetime [, offset])
```
### 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
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
Count requests per day to identify daily traffic patterns.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# startofmonth
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/startofmonth
This page explains how to use the startofmonth function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval month_start=relative_time(_time, "@mon")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT DATE_TRUNC('month', timestamp_column) AS month_start FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend month_start = startofmonth(_time)
```
## Usage
### Syntax
```kusto theme={null}
startofmonth(datetime [, offset])
```
### 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
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
Count requests per month to identify monthly traffic trends.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# startofweek
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/startofweek
This page explains how to use the startofweek function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval week_start=relative_time(_time, "@w0")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT DATE_TRUNC('week', timestamp_column) AS week_start FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend week_start = startofweek(_time)
```
## Usage
### Syntax
```kusto theme={null}
startofweek(datetime [, offset])
```
### 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
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
Count requests per week to identify weekly traffic patterns.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# startofyear
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/startofyear
This page explains how to use the startofyear function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval year_start=relative_time(_time, "@y")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT DATE_TRUNC('year', timestamp_column) AS year_start FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend year_start = startofyear(_time)
```
## Usage
### Syntax
```kusto theme={null}
startofyear(datetime [, offset])
```
### 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
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
Count requests per year to understand long-term traffic volume.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# unixtime_microseconds_todatetime
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/unixtime-microseconds-todatetime
This page explains how to use the unixtime_microseconds_todatetime function in APL.
`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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval eventTime=strftime( micro_ts/1000000 , "%Y-%m-%dT%H:%M:%S.%6N")
```
```kusto APL equivalent theme={null}
| 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 theme={null}
SELECT TIMESTAMP '1970-01-01 00:00:00' + micro_ts / 1000000 * INTERVAL '1 second' FROM events;
```
```kusto APL equivalent theme={null}
['events']
| extend eventTime = unixtime_microseconds_todatetime(micro_ts)
```
## Usage
### Syntax
```kusto theme={null}
unixtime_microseconds_todatetime(microseconds)
```
### Parameters
| Name | Type | Description |
| -------------- | --------------- | ----------------------------------------------------------------------- |
| `microseconds` | `int` or `long` | Whole microseconds since the Unix epoch. Fractional input is truncated. |
### Returns
A `datetime` value that represents the given epoch microseconds at UTC precision (1 microsecond).
## Use case example
The HTTP access logs keep the timestamp as epoch microseconds and you want to convert the values to datetime.
**Query**
```kusto theme={null}
['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
* [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.
# unixtime_milliseconds_todatetime
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/unixtime-milliseconds-todatetime
This page explains how to use the unixtime_milliseconds_todatetime function in APL.
`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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
`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 theme={null}
| eval timestamp=strftime(epoch_ms/1000,"%Y-%m-%dT%H:%M:%SZ")
```
```kusto APL equivalent theme={null}
| 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 theme={null}
SELECT FROM_UNIXTIME(epoch_ms/1000) AS timestamp FROM requests;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend timestamp=unixtime_milliseconds_todatetime(epoch_ms)
```
## Usage
### Syntax
```kusto theme={null}
unixtime_milliseconds_todatetime(milliseconds)
```
### Parameters
| Name | Type | Description |
| -------------- | --------------- | ----------------------------------------------------------------------- |
| `milliseconds` | `int` or `long` | Whole milliseconds since the Unix epoch. Fractional input is truncated. |
### Returns
A `datetime` value that represents the given epoch milliseconds at UTC precision (1 millisecond).
## Use case example
The HTTP access logs keep the timestamp as epoch milliseconds and you want to convert the values to datetime.
**Query**
```kusto theme={null}
['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
* [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.
# unixtime_nanoseconds_todatetime
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/unixtime-nanoseconds-todatetime
This page explains how to use the unixtime_nanoseconds_todatetime function in APL.
`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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval event_time = strftime(epoch_ns/1000000000, "%Y-%m-%dT%H:%M:%S.%N%z")
```
```kusto APL equivalent theme={null}
| 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 theme={null}
SELECT TO_TIMESTAMP_LTZ(epoch_ns/1e9) AS event_time
FROM events;
```
```kusto APL equivalent theme={null}
events
| extend event_time = unixtime_nanoseconds_todatetime(epoch_ns)
```
## Usage
### Syntax
```kusto theme={null}
unixtime_nanoseconds_todatetime(nanoseconds)
```
### Parameters
| Name | Type | Description |
| ------------- | --------------- | ---------------------------------------------------------------------- |
| `nanoseconds` | `int` or `long` | Whole nanoseconds since the Unix epoch. Fractional input is truncated. |
### Returns
A `datetime` value that represents the given epoch nanoseconds at UTC precision (1 nanosecond).
## Use case example
The HTTP access logs keep the timestamp as epoch nanoseconds and you want to convert the values to datetime.
**Query**
```kusto theme={null}
['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
* [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.
# unixtime_seconds_todatetime
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/unixtime-seconds-todatetime
This page explains how to use the unixtime_seconds_todatetime function in APL.
`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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
`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 theme={null}
eval event_time = strftime(epoch, "%Y-%m-%dT%H:%M:%S")
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT TO_TIMESTAMP(epoch_seconds) AS event_time FROM events;
```
```kusto APL equivalent theme={null}
['events']
| extend event_time = unixtime_seconds_todatetime(epoch_seconds)
```
## Usage
### Syntax
```kusto theme={null}
unixtime_seconds_todatetime(seconds)
```
### Parameters
| Name | Type | Description |
| --------- | --------------- | ------------------------------------------------------------------ |
| `seconds` | `int` or `long` | Whole seconds since the Unix epoch. Fractional input is truncated. |
### Returns
A `datetime` value that represents the given epoch seconds at UTC precision (1 second).
## Use case example
The HTTP access logs keep the timestamp as epoch seconds and you want to convert the values to datetime.
**Query**
```kusto theme={null}
['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
* [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.
# week_of_year
Source: https://axiom.co/docs/apl/scalar-functions/datetime-functions/week-of-year
This page explains how to use the week_of_year function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval week=strftime(_time, "%V")
```
```kusto APL equivalent theme={null}
... | 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 theme={null}
SELECT EXTRACT(WEEK FROM timestamp_column) AS week FROM events;
```
```kusto APL equivalent theme={null}
['dataset']
| extend week = week_of_year(_time)
```
## Usage
### Syntax
```kusto theme={null}
week_of_year(datetime)
```
### Parameters
| Name | Type | Description |
| -------- | ---------- | ------------------------- |
| datetime | `datetime` | The input datetime value. |
### Returns
A `long` representing the ISO 8601 week number (from 1 to 53).
## Use case examples
Group HTTP log events by week to understand traffic trends and average request duration.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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.
# GenAI functions
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions
This page provides an overview of GenAI functions in APL for analyzing and processing GenAI conversation data.
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
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
| 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 |
# genai_concat_contents
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-concat-contents
This page explains how to use the genai_concat_contents function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval all_content=mvjoin(messages, " ")
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT
conversation_id,
STRING_AGG(content, ' ') as all_content
FROM conversations
CROSS JOIN UNNEST(messages) as msg
GROUP BY conversation_id
```
```kusto APL equivalent theme={null}
['ai-logs']
| extend all_content = genai_concat_contents(messages, ' ')
```
## Usage
### Syntax
```kusto theme={null}
genai_concat_contents(messages, separator)
```
### 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 a string containing all message contents concatenated together with the specified separator.
## Example
Create a searchable conversation transcript from a GenAI conversation array.
**Query**
```kusto theme={null}
['genai-traces']
| extend conversation_text = genai_concat_contents(['attributes.gen_ai.input.messages'], ' / ')
```
**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. /" |
This query concatenates the message contents of a GenAI conversation array with a separator of `/`.
## 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.
# genai_conversation_turns
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-conversation-turns
This page explains how to use the genai_conversation_turns function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval turn_count=mvcount(messages)/2
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT
conversation_id,
COUNT(*) / 2 as turn_count
FROM conversations
CROSS JOIN UNNEST(messages)
GROUP BY conversation_id
```
```kusto APL equivalent theme={null}
['ai-logs']
| extend turn_count = genai_conversation_turns(messages)
```
## Usage
### Syntax
```kusto theme={null}
genai_conversation_turns(messages)
```
### 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 a long integer representing the number of conversation turns. A turn is typically counted as a user-assistant exchange pair.
## Example
Count the number of conversation turns in a GenAI chat operation.
**Query**
```kusto theme={null}
['genai-traces']
| extend turns = genai_conversation_turns(['attributes.gen_ai.input.messages'])
| summarize avg_turns = avg(turns), max_turns = max(turns)
```
**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
* [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.
# genai_cost
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-cost
This page explains how to use the genai_cost function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you would need to manually calculate costs using eval and lookup tables.
```sql Splunk example theme={null}
| 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 theme={null}
['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 theme={null}
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 theme={null}
['ai-logs']
| extend total_cost = genai_cost(model, input_tokens, output_tokens)
```
## Usage
### Syntax
```kusto theme={null}
genai_cost(model, input_tokens, output_tokens)
```
### 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 a real number representing the total cost in dollars (USD) for the API call based on the model's pricing.
## Example
Calculate the total cost of a GenAI API call based on model and token usage.
**Query**
```kusto theme={null}
['genai-traces']
| 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)
```
**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
* [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.
# genai_estimate_tokens
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-estimate-tokens
This page explains how to use the genai_estimate_tokens function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval estimated_tokens=len(text)/4
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT
text,
LENGTH(text) / 4 as estimated_tokens
FROM prompts
```
```kusto APL equivalent theme={null}
['ai-logs']
| extend estimated_tokens = genai_estimate_tokens(text)
```
## Usage
### Syntax
```kusto theme={null}
genai_estimate_tokens(text)
```
### Parameters
| Name | Type | Required | Description |
| ---- | ------ | -------- | --------------------------------------------------------------- |
| text | string | Yes | The text string for which you want to estimate the token count. |
### Returns
Returns a long integer representing the estimated number of tokens in the input text.
## Example
Estimate the number of tokens in a GenAI conversation prompt.
**Query**
```kusto theme={null}
['genai-traces']
| 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)
```
**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
* [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.
# genai_extract_assistant_response
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-extract-assistant-response
This page explains how to use the genai_extract_assistant_response function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you would need to use complex eval statements with mvfilter and mvindex to extract assistant messages.
```sql Splunk example theme={null}
| eval assistant_msgs=mvfilter(match(role, "assistant"))
| eval response=mvindex(assistant_msgs, -1)
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['ai-logs']
| extend assistant_response = genai_extract_assistant_response(messages)
```
## Usage
### Syntax
```kusto theme={null}
genai_extract_assistant_response(messages)
```
### 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 a string containing the content of the last assistant message in the conversation, or an empty string if no assistant message is found.
## Example
Extract the assistant's response from a GenAI conversation.
**Query**
```kusto theme={null}
['genai-traces']
| extend ai_response = genai_extract_assistant_response(['attributes.gen_ai.input.messages'])
| where strlen(ai_response) > 0
| project _time, ai_response
| limit 3
```
**Output**
| \_time | ai\_response |
| -------------------- | ------------------------------------------------------------------------------ |
| 2024-01-15T10:30:00Z | To reset your password, click on the 'Forgot Password' link on the login page. |
| 2024-01-15T10:31:00Z | Our 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
* [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.
# genai_extract_function_results
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-extract-function-results
This page explains how to use the genai_extract_function_results function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you would need to use complex filtering and extraction logic to isolate function results from nested message structures.
```sql Splunk example theme={null}
| eval function_results=mvfilter(match(role, "function") OR match(role, "tool"))
| eval results=mvindex(function_results, 0)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT
conversation_id,
JSON_EXTRACT(content, '$.result') as function_results
FROM conversations
CROSS JOIN UNNEST(messages)
WHERE role IN ('function', 'tool')
```
```kusto APL equivalent theme={null}
['ai-logs']
| extend function_results = genai_extract_function_results(messages)
```
## Usage
### Syntax
```kusto theme={null}
genai_extract_function_results(messages)
```
### 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 a dynamic object containing the function call results from the conversation, or null if no function results are found.
## Example
Extract function call results from a GenAI conversation to analyze tool execution outcomes.
**Query**
```kusto theme={null}
['genai-traces']
| extend function_results = genai_extract_function_results(['attributes.gen_ai.input.messages'])
| where isnotnull(function_results)
| project _time, function_results
| limit 3
```
**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
* [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.
# genai_extract_system_prompt
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-extract-system-prompt
This page explains how to use the genai_extract_system_prompt function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you would need to filter messages by role and extract the first system message.
```sql Splunk example theme={null}
| eval system_msgs=mvfilter(match(role, "system"))
| eval system_prompt=mvindex(system_msgs, 0)
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['ai-logs']
| extend system_prompt = genai_extract_system_prompt(messages)
```
## Usage
### Syntax
```kusto theme={null}
genai_extract_system_prompt(messages)
```
### 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 a string containing the content of the system message, or an empty string if no system message is found.
## Example
Extract the system prompt from a GenAI conversation to verify AI configuration.
**Query**
```kusto theme={null}
['genai-traces']
| 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
```
**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
* [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.
# genai_extract_tool_calls
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-extract-tool-calls
This page explains how to use the genai_extract_tool_calls function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you would need to filter and extract tool call information from nested message structures manually.
```sql Splunk example theme={null}
| eval tool_calls=mvfilter(match(role, "assistant") AND isnotnull(tool_calls))
| eval tools=spath(tool_calls, "tool_calls")
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['ai-logs']
| extend tool_calls = genai_extract_tool_calls(messages)
```
## Usage
### Syntax
```kusto theme={null}
genai_extract_tool_calls(messages)
```
### 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 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
Extract tool calls from a GenAI conversation to analyze which functions are being invoked.
**Query**
```kusto theme={null}
['genai-traces']
| 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
```
**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
* [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.
# genai_extract_user_prompt
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-extract-user-prompt
This page explains how to use the genai_extract_user_prompt function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you would need to filter messages by user role and extract the last one.
```sql Splunk example theme={null}
| eval user_msgs=mvfilter(match(role, "user"))
| eval user_prompt=mvindex(user_msgs, -1)
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['ai-logs']
| extend user_prompt = genai_extract_user_prompt(messages)
```
## Usage
### Syntax
```kusto theme={null}
genai_extract_user_prompt(messages)
```
### 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 a string containing the content of the last user message in the conversation, or an empty string if no user message is found.
## Example
Extract the user's prompt from a GenAI conversation to analyze common questions.
**Query**
```kusto theme={null}
['genai-traces']
| 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
```
**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 |
This query identifies the most common user questions, helping you understand user needs and improve responses.
## 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.
# genai_get_content_by_index
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-get-content-by-index
This page explains how to use the genai_get_content_by_index function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you would use `mvindex` to access array elements by position.
```sql Splunk example theme={null}
| eval message_content=mvindex(messages, 2)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT
conversation_id,
content as message_content
FROM conversations
CROSS JOIN UNNEST(messages) WITH OFFSET AS pos
WHERE pos = 2
```
```kusto APL equivalent theme={null}
['ai-logs']
| extend message_content = genai_get_content_by_index(messages, 2)
```
## Usage
### Syntax
```kusto theme={null}
genai_get_content_by_index(messages, index)
```
### 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 a string containing the content of the message at the specified index, or an empty string if the index is out of bounds.
## Example
Get the content of the first user message in a GenAI conversation.
**Query**
```kusto theme={null}
['genai-traces']
| extend first_message = genai_get_content_by_index(['attributes.gen_ai.input.messages'], 1)
| where isnotempty(first_message)
| project _time, first_message
| limit 3
```
**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? |
This query helps you understand how users typically start conversations, which can inform greeting messages and initial prompts.
## 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.
# genai_get_content_by_role
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-get-content-by-role
This page explains how to use the genai_get_content_by_role function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you would use `mvfilter` to filter by role and then extract the content.
```sql Splunk example theme={null}
| eval filtered_msgs=mvfilter(match(role, "system"))
| eval content=mvindex(filtered_msgs, 0)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT
conversation_id,
content
FROM conversations
CROSS JOIN UNNEST(messages)
WHERE role = 'system'
LIMIT 1
```
```kusto APL equivalent theme={null}
['ai-logs']
| extend content = genai_get_content_by_role(messages, 'system')
```
## Usage
### Syntax
```kusto theme={null}
genai_get_content_by_role(messages, role)
```
### 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 a string containing the content of the first message with the specified role, or an empty string if no matching message is found.
## Example
Extract the content of the system message from a GenAI conversation.
**Query**
```kusto theme={null}
['genai-traces']
| 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
```
**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
* [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.
# genai_get_pricing
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-get-pricing
This page explains how to use the genai_get_pricing function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you would typically use a lookup table to retrieve pricing information by model name.
```sql Splunk example theme={null}
| lookup model_pricing model OUTPUT input_price output_price
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['ai-logs']
| extend pricing = genai_get_pricing(model)
```
## Usage
### Syntax
```kusto theme={null}
genai_get_pricing(model)
```
### 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 a dynamic object containing pricing information with the following structure:
```json theme={null}
{
"input_price_per_million": ,
"output_price_per_million":
}
```
Returns null if the model is not recognized.
## Example
Get pricing information for a GenAI model to understand cost structure.
**Query**
```kusto theme={null}
['genai-traces']
| 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
```
**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
* [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.
# genai_get_role
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-get-role
This page explains how to use the genai_get_role function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you would use `mvindex` to access the role field at a specific position.
```sql Splunk example theme={null}
| eval message_role=mvindex(role, 2)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT
conversation_id,
role as message_role
FROM conversations
CROSS JOIN UNNEST(messages) WITH OFFSET AS pos
WHERE pos = 2
```
```kusto APL equivalent theme={null}
['ai-logs']
| extend message_role = genai_get_role(messages, 2)
```
## Usage
### Syntax
```kusto theme={null}
genai_get_role(messages, index)
```
### 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 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
Get the role of the first message in a GenAI conversation.
**Query**
```kusto theme={null}
['genai-traces']
| extend first_role = genai_get_role(['attributes.gen_ai.input.messages'], 0)
| summarize conversations_with_system = countif(first_role == 'system'), total_conversations = count()
```
**Output**
| conversations\_with\_system | total\_conversations |
| --------------------------- | -------------------- |
| 1250 | 1450 |
This query verifies that most conversations are properly initialized with system prompts.
## 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.
# genai_has_tool_calls
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-has-tool-calls
This page explains how to use the genai_has_tool_calls function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you would check if tool-related fields exist in the messages.
```sql Splunk example theme={null}
| eval has_tools=if(isnotnull(tool_calls), "true", "false")
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['ai-logs']
| extend has_tools = genai_has_tool_calls(messages)
```
## Usage
### Syntax
```kusto theme={null}
genai_has_tool_calls(messages)
```
### 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 a boolean value: `true` if the messages contain tool calls, `false` otherwise.
## Example
Check if a GenAI conversation contains any tool calls or function calls.
**Query**
```kusto theme={null}
['genai-traces']
| 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)
```
**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
* [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.
# genai_input_cost
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-input-cost
This page explains how to use the genai_input_cost function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you would need to lookup pricing and calculate costs manually.
```sql Splunk example theme={null}
| lookup model_pricing model OUTPUT input_price
| eval input_cost=(input_tokens * input_price / 1000000)
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['ai-logs']
| extend input_cost = genai_input_cost(model, input_tokens)
```
## Usage
### Syntax
```kusto theme={null}
genai_input_cost(model, input_tokens)
```
### 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 a real number representing the cost in dollars (USD) for the input tokens based on the model's pricing.
## Example
Calculate the cost of input tokens for a GenAI chat operation.
**Query**
```kusto theme={null}
['genai-traces']
| 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)
```
**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
* [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.
# genai_is_truncated
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-is-truncated
This page explains how to use the genai_is_truncated function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you would check the finish\_reason field manually.
```sql Splunk example theme={null}
| eval is_truncated=if(finish_reason="length", "true", "false")
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT
conversation_id,
CASE WHEN finish_reason = 'length' THEN true ELSE false END as is_truncated
FROM ai_logs
```
```kusto APL equivalent theme={null}
['ai-logs']
| extend is_truncated = genai_is_truncated(messages, finish_reason)
```
## Usage
### Syntax
```kusto theme={null}
genai_is_truncated(messages, finish_reason)
```
### 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 a boolean value: `true` if the response was truncated (typically when finish\_reason is 'length'), `false` otherwise.
## Example
Check if a GenAI response was truncated due to token limits.
**Query**
```kusto theme={null}
['genai-traces']
| 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)
```
**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
* [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.
# genai_message_roles
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-message-roles
This page explains how to use the genai_message_roles function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you would extract the role field from all messages in an array.
```sql Splunk example theme={null}
| eval roles=mvindex(role, 0, mvcount(role))
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['ai-logs']
| extend roles = genai_message_roles(messages)
```
## Usage
### Syntax
```kusto theme={null}
genai_message_roles(messages)
```
### 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 a dynamic array containing all the roles in the conversation in their original order (for example, `['system', 'user', 'assistant', 'user', 'assistant']`).
## Example
Extract all message roles from a GenAI conversation to understand the conversation structure.
**Query**
```kusto theme={null}
['genai-traces']
| 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
```
**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
* [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.
# genai_output_cost
Source: https://axiom.co/docs/apl/scalar-functions/genai-functions/genai-output-cost
This page explains how to use the genai_output_cost function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you would need to lookup pricing and calculate costs manually.
```sql Splunk example theme={null}
| lookup model_pricing model OUTPUT output_price
| eval output_cost=(output_tokens * output_price / 1000000)
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['ai-logs']
| extend output_cost = genai_output_cost(model, output_tokens)
```
## Usage
### Syntax
```kusto theme={null}
genai_output_cost(model, output_tokens)
```
### 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 a real number representing the cost in dollars (USD) for the output tokens based on the model's pricing.
## Example
Calculate the cost of output tokens for a GenAI chat operation.
**Query**
```kusto theme={null}
['genai-traces']
| 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)
```
**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
* [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.
# Hash functions
Source: https://axiom.co/docs/apl/scalar-functions/hash-functions
Learn how to use and combine various hash functions in APL
The table summarizes the hash functions available in APL.
| Name | Description |
| ------------------------------------------------- | -------------------------------------------------- |
| [hash](/apl/scalar-functions/hash-functions/hash) | Returns a signed integer hash for the input value. |
| [hash\_md5](#hash-md5) | Returns a MD5 hash value for the input value. |
| [hash\_sha1](#hash-sha1) | Returns a sha1 hash value for the input value. |
| [hash\_sha256](#hash-sha256) | Returns a SHA256 hash value for the input value. |
| [hash\_sha512](#hash-sha512) | Returns a SHA512 hash value for the input value. |
## hash\_md5
Returns an MD5 hash value for the input value.
### Arguments
* source: The value to be hashed.
### Returns
The MD5 hash value of the given scalar, encoded as a hex string (a string of characters, each two of which represent a single Hex number between 0 and 255).
### Examples
```kusto theme={null}
hash_md5(source)
```
```kusto theme={null}
['sample-http-logs']
| project md5_hash_value = hash_md5(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%20md5_hash_value%20%3D%20hash_md5%28content_type%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
* Result
```json theme={null}
{
"md5_hash_value": "b980a9c041dbd33d5893fad65d33284b"
}
```
## hash\_sha1
Returns a SHA1 hash value for the input value.
### Arguments
* source: The value to be hashed.
### Returns
The sha1 hash value of the given scalar, encoded as a hex string
### Examples
```kusto theme={null}
hash_sha1(source)
```
```kusto theme={null}
['sample-http-logs']
| project sha1_hash_value = hash_sha1(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%20sha1_hash_value%20%3D%20hash_sha1%28content_type%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
* Result
```json theme={null}
{
"sha1_hash_value": "9f9af029585ba014e07cd3910ca976cf56160616"
}
```
## hash\_sha256
Returns a SHA256 hash value for the input value.
### Arguments
* source: The value to be hashed.
### Returns
The sha256 hash value of the given scalar, encoded as a hex string (a string of characters, each two of which represent a single Hex number between 0 and 255).
### Examples
```kusto theme={null}
hash_sha256(source)
```
```kusto theme={null}
['sample-http-logs']
| project sha256_hash_value = hash_sha256(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%20sha256_hash_value%20%3D%20hash_sha256%28content_type%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
* Result
```json theme={null}
{
"sha256_hash_value": "bb4770ff4ac5b7d2be41a088cb27d8bcaad53b574b6f27941e8e48e9e10fc25a"
}
```
## hash\_sha512
Returns a SHA512 hash value for the input value.
### Arguments
* source: The value to be hashed.
### Returns
The sha512 hash value of the given scalar, encoded as a hex string (a string of characters, each two of which represent a single Hex number between 0 and 511).
### Examples
```kusto theme={null}
hash_sha512(source)
```
```kusto theme={null}
['sample-http-logs']
| project sha512_hash_value = hash_sha512(status)
```
[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%20sha512_hash_value%20%3D%20hash_sha512%28status%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
* Result
```json theme={null}
{
"sha512_hash_value": "0878a61b503dd5a9fe9ea3545d6d3bd41c3b50a47f3594cb8bbab3e47558d68fc8fcc409cd0831e91afc4e609ef9da84e0696c50354ad86b25f2609efef6a834"
}
```
***
```kusto theme={null}
['sample-http-logs']
| project sha512_hash_value = hash_sha512(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%20sha512_hash_value%20%3D%20hash_sha512%28content_type%29%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2230d%22%7D%7D)
* Result
```json theme={null}
{
"sha512_hash_value": "95c6eacdd41170b129c3c287cfe088d4fafea34e371422b94eb78b9653a89d4132af33ef39dd6b3d80e18c33b21ae167ec9e9c2d820860689c647ffb725498c4"
}
```
# hash
Source: https://axiom.co/docs/apl/scalar-functions/hash-functions/hash
This page explains how to use the hash function in APL.
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`.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval anon_id = md5(id) | stats count by anon_id
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT HASH(id) % 10 AS bucket, COUNT(*) AS requests
FROM sample_http_logs
GROUP BY bucket
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend bucket = abs(hash(id) % 10)
| summarize requests = count() by bucket
```
## Usage
### Syntax
```kusto theme={null}
hash(source [, salt])
```
### 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
The signed integer hash of `source` (and `salt` if supplied).
## Use case examples
Hash requesters to see your busiest anonymous users.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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.
# IP functions
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions
This section explains how to use IP functions in APL.
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
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.
# format_ipv4
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/format-ipv4
This page explains how to use the format_ipv4 function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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.
## Usage
### Syntax
```kusto theme={null}
format_ipv4(ipv4address)
```
### Parameters
* `ipv4address`: A `long` numeric representation of the IPv4 address in network byte order.
### Returns
* Returns a string representing the IPv4 address in dotted-decimal format.
* Returns an empty string if the conversion fails.
## 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 theme={null}
['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
* [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.
# format_ipv4_mask
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/format-ipv4-mask
This page explains how to use the format_ipv4_mask function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval cidr=ip."/24"
```
```kusto APL equivalent theme={null}
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 theme={null}
-- Requires custom UDF or external processing
SELECT format_ip_with_mask(ip, 24) FROM connections
```
```kusto APL equivalent theme={null}
format_ipv4_mask(ip, 24)
```
## Usage
### Syntax
```kusto theme={null}
format_ipv4_mask(ip, prefix)
```
### Parameters
| Name | Type | Required | Description |
| ------ | ------ | -------- | ------------------------------------------------------------------------------------------------------- |
| ip | string | ✓ | The IPv4 address in CIDR notation. You can use a string (e.g., `'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
A string representing the IPv4 address in CIDR notation if the conversion succeeds. If the conversion fails, the function returns an empty string.
## Example
**Query**
```kusto theme={null}
['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
* [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.
# geo_info_from_ip_address
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/geo-info-from-ip-address
This page explains how to use the geo_info_from_ip_address function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval geo_info = iplocation(client_ip)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT ip_to_location(client_ip) AS geo_info
FROM sample_http_logs
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend geo_info = geo_info_from_ip_address(client_ip)
```
## Usage
### Syntax
```kusto theme={null}
geo_info_from_ip_address(ip_address)
```
### Parameters
| Parameter | Type | Description |
| ------------ | ------ | ------------------------------------------------------------ |
| `ip_address` | string | The IP address for which to retrieve geographic information. |
### 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 geographic data to analyze web log traffic.
**Query**
```kusto theme={null}
['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 theme={null}
{
"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
* [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
### Extract geolocation information from IPv4 address
```kusto theme={null}
['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
```kusto theme={null}
['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
```kusto theme={null}
['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
```kusto theme={null}
['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
### Extract geolocation information from IPv6 address
```kusto theme={null}
['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
```kusto theme={null}
['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
```kusto theme={null}
['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
```kusto theme={null}
['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)
# has_any_ipv4
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/has-any-ipv4
This page explains how to use the has_any_ipv4 function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| where cidrmatch("192.168.1.0/24", ip_field)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT * FROM logs WHERE ip_field = '192.168.1.1' OR ip_field = '192.168.1.2';
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| where has_any_ipv4('ip_field', dynamic(['192.168.1.1', '192.168.1.2']))
```
## Usage
### Syntax
```kusto theme={null}
has_any_ipv4(column, ip_list)
```
### Parameters
| Parameter | Description | Type |
| --------- | ---------------------------------------- | --------- |
| `column` | The column to evaluate. | `string` |
| `ip_list` | A list of IPv4 addresses or CIDR ranges. | `dynamic` |
### 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
When analyzing logs, you can use `has_any_ipv4` to filter requests from specific IPv4 addresses or subnets.
**Query**
```kusto theme={null}
['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
* [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.
# has_any_ipv4_prefix
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/has-any-ipv4-prefix
This page explains how to use the has_any_ipv4_prefix function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval is_in_range=if(match(ip, "10.*") OR match(ip, "192.168.*"), 1, 0)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT * FROM logs
WHERE ip LIKE '10.%' OR ip LIKE '192.168.%';
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| where has_any_ipv4_prefix(uri, dynamic(['10.', '192.168.']))
```
## Usage
### Syntax
```kusto theme={null}
has_any_ipv4_prefix(ip_column, prefixes)
```
### Parameters
| Parameter | Type | Description |
| ----------- | --------- | ----------------------------------------- |
| `ip_column` | `string` | The column containing the IPv4 address. |
| `prefixes` | `dynamic` | A list of IPv4 prefixes to check against. |
### Returns
* `true` if the IPv4 address matches any of the specified prefixes.
* `false` otherwise.
## Use case example
Detect requests from specific IP ranges.
**Query**
```kusto theme={null}
['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
* [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.
# has_ipv4
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/has-ipv4
This page explains how to use the has_ipv4 function in APL.
## 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`.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
search sourcetype=access_combined | eval isPresent=match(_raw, "192\.168\.1\.1")
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT CASE WHEN column_text LIKE '%192.168.1.1%' THEN TRUE ELSE FALSE END AS result
FROM log_table;
```
```kusto APL equivalent theme={null}
print result=has_ipv4('05:04:54 192.168.1.1 GET /favicon.ico 404', '192.168.1.1')
```
## Usage
### Syntax
```kusto theme={null}
has_ipv4(source, ip_address)
```
### 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
* `true` if `ip_address` is a valid IP address and is found in `source`.
* `false` otherwise.
## Use case example
Identify requests coming from a specific IP address in HTTP logs.
**Query**
```kusto theme={null}
['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
* [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_prefix
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/has-ipv4-prefix
This page explains how to use the has_ipv4_prefix function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval is_match = if(cidrmatch("192.168.0.0/24", ip), true, false)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT *
FROM sample_http_logs
WHERE ip LIKE '192.168.0%'
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| where has_ipv4_prefix(uri, "192.168.0")
```
## Usage
### Syntax
```kusto theme={null}
has_ipv4_prefix(column_name, prefix)
```
### 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 (e.g., "192.0"). |
### Returns
* Returns a Boolean (`true` or `false`) indicating whether the IPv4 address starts with the specified prefix.
## Use case example
Use `has_ipv4_prefix` to filter logs for requests originating from a specific IP range.
**Query**
```kusto theme={null}
['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
* [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
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv4-compare
This page explains how to use the ipv4_compare function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval comparison = if(ip1 < ip2, -1, if(ip1 == ip2, 0, 1))
```
```kusto APL equivalent theme={null}
| 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 theme={null}
SELECT CASE
WHEN ip1 < ip2 THEN -1
WHEN ip1 = ip2 THEN 0
ELSE 1
END AS comparison
FROM ips;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend comparison = ipv4_compare(ip1, ip2)
```
## Usage
### Syntax
```kusto theme={null}
ipv4_compare(ip1, ip2)
```
### Parameters
| Parameter | Type | Description |
| --------- | ------ | ----------------------------------- |
| `ip1` | string | The first IPv4 address to compare. |
| `ip2` | string | The second IPv4 address to compare. |
### 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
You can use `ipv4_compare` to sort logs based on IP addresses or to identify connections between specific IPs.
**Query**
```kusto theme={null}
['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
* [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.
# ipv4_is_in_any_range
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv4-is-in-any-range
This page explains how to use the ipv4_is_in_any_range function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval is_in_range = cidrmatch("192.168.0.0/24", ip_address)
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['dataset']
| extend is_in_range = ipv4_is_in_any_range(ip_address, dynamic(['192.168.0.0/24', '10.0.0.0/8']))
```
## Usage
### Syntax
```kusto theme={null}
ipv4_is_in_any_range(ip_address: string, ranges: dynamic)
```
### 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
* `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
Identify log entries from specific subnets, such as local office IP ranges.
**Query**
```kusto theme={null}
['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
* [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.
# ipv4_is_in_range
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv4-is-in-range
This page explains how to use the ipv4_is_in_range function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval in_range = cidrmatch("192.168.0.0/24", ip_address)
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['sample-http-logs']
| extend in_range = ipv4_is_in_range(ip_address, '192.168.0.0/24')
```
## Usage
### Syntax
```kusto theme={null}
ipv4_is_in_range(ip: string, range: string)
```
### Parameters
| Parameter | Type | Description |
| --------- | ------ | --------------------------------------------------------- |
| `ip` | string | The IPv4 address to evaluate. |
| `range` | string | The IPv4 range in CIDR notation (e.g., `192.168.1.0/24`). |
### 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
You can use `ipv4_is_in_range` to identify traffic from specific geographic regions or service provider IP blocks.
**Query**
```kusto theme={null}
['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
* [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.
# ipv4_is_match
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv4-is-match
This page explains how to use the ipv4_is_match function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
cidrmatch("192.168.1.0/24", ip)
```
```kusto APL equivalent theme={null}
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 theme={null}
ip LIKE '192.168.1.0'
```
```kusto APL equivalent theme={null}
ipv4_is_match(ip, "192.168.1.0")
```
## Usage
### Syntax
```kusto theme={null}
ipv4_is_match(ipaddress1, ipaddress2, prefix)
```
### 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
* `true` if the IPv4 addresses match.
* `false` otherwise.
* `null` if the conversion of an IPv4 string wasn’t successful.
## 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 theme={null}
['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
* [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_private
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv4-is-private
This page explains how to use the ipv4_is_private function in APL.
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) |
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
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 theme={null}
['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 theme={null}
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 theme={null}
['sample-http-logs']
| extend is_private=ipv4_is_private(client_ip)
```
## Usage
### Syntax
```kusto theme={null}
ipv4_is_private(ip: string)
```
### Parameters
| Parameter | Type | Description |
| --------- | ------ | ------------------------------------------------------ |
| `ip` | string | The IPv4 address to evaluate for private range status. |
### Returns
* `true`: The input IP address is private.
* `false`: The input IP address isn’t private.
## Use case example
You can use `ipv4_is_private` to filter logs and focus on public traffic for external analysis.
**Query**
```kusto theme={null}
['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
* [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.
# ipv4_netmask_suffix
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv4-netmask-suffix
This page explains how to use the ipv4_netmask_suffix function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
eval netmask = replace(ip, "^.*?/", "")
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT SUBSTRING(ip, CHARINDEX('/', ip) + 1, LEN(ip)) AS netmask FROM logs;
```
```kusto APL equivalent theme={null}
extend netmask = ipv4_netmask_suffix(ip)
```
## Usage
### Syntax
```kusto theme={null}
ipv4_netmask_suffix(ipv4address)
```
### Parameters
| Parameter | Type | Description |
| ------------- | ------ | ----------------------------------------------------------- |
| `ipv4address` | string | The IPv4 address in CIDR notation (e.g., `192.168.1.1/24`). |
### 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
When analyzing network traffic logs, you can extract the netmask suffix to group or filter traffic by subnets.
**Query**
```kusto theme={null}
['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
* [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.
# ipv6_compare
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv6-compare
This page explains how to use the ipv6_compare function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval ip1 = "2001:db8::1", ip2 = "2001:db8::2"
| eval comparison = if(ip1 == ip2, 0, if(ip1 < ip2, -1, 1))
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT CASE
WHEN ip1 = ip2 THEN 0
WHEN ip1 < ip2 THEN -1
ELSE 1
END AS comparison
FROM my_table
```
```kusto APL equivalent theme={null}
print comparison = ipv6_compare('2001:db8::1', '2001:db8::2')
```
## Usage
### Syntax
```kusto theme={null}
ipv6_compare(ipv6_1, ipv6_2)
```
### Parameters
| Name | Type | Description |
| -------- | ------ | ----------------------------------- |
| `ipv6_1` | string | The first IPv6 address to compare. |
| `ipv6_2` | string | The second IPv6 address to compare. |
### 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
Use `ipv6_compare` to identify whether requests from certain IPv6 addresses fall into specific ranges or appear out of expected order.
**Query**
```kusto theme={null}
['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
* [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.
# ipv6_is_in_any_range
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv6-is-in-any-range
This page explains how to use the ipv6_is_in_any_range function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval is_internal = if(cidrmatch("2001:db8::/32", ip), "true", "false")
```
```kusto APL equivalent theme={null}
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 theme={null}
-- 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 theme={null}
ipv6_is_in_any_range('2001:db8::1', dynamic(['2001:db8::/32']))
```
## Usage
### Syntax
```kusto theme={null}
ipv6_is_in_any_range(ipv6_address, ipv6_ranges)
```
### Parameters
| Name | Type | Description |
| -------------- | --------------- | --------------------------------------------------------- |
| `ipv6_address` | `string` | An IPv6 address in standard format (e.g., `2001:db8::1`). |
| `ipv6_ranges` | `dynamic array` | A JSON array of IPv6 CIDR strings to compare against. |
### Returns
A `bool` value:
* `true` if the given IPv6 address is within any of the provided CIDR ranges.
* `false` otherwise.
## Example
You want to detect HTTP requests from a specific internal IPv6 block.
**Query**
```kusto theme={null}
['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
* [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.
# ipv6_is_in_range
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv6-is-in-range
This page explains how to use the ipv6_is_in_range function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval inRange=if(cidrmatch("2001:db8::/32", src_ip), "yes", "no")
```
```kusto APL equivalent theme={null}
['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 theme={null}
-- Using a hypothetical UDF
SELECT ipv6_in_range(ip_address, '2001:db8::/32') AS in_range FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend inRange = ipv6_is_in_range(src_ip, '2001:db8::/32')
```
## Usage
### Syntax
```kusto theme={null}
ipv6_is_in_range(ipv6: string, cidr_range: string)
```
### Parameters
| Name | Type | Description |
| ------------ | ------ | --------------------------------------------- |
| `ipv6` | string | The IPv6 address to check. |
| `cidr_range` | string | The IPv6 CIDR block (e.g. `'2001:db8::/32'`). |
### Returns
A `bool` value:
* `true` if the IPv6 address is within the specified CIDR range.
* `false` otherwise.
## Example
Use this function to isolate internal service calls originating from a designated IPv6 block.
**Query**
```kusto theme={null}
['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
* [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.
# ipv6_is_match
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/ipv6-is-match
This page explains how to use the ipv6_is_match function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval is_in_subnet=if(match(ipv6_field, "^2001:db8::/32"), "true", "false")
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT CASE
WHEN ip_address LIKE '2001:db8:%' THEN TRUE
ELSE FALSE
END AS is_in_subnet
FROM logs
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend is_in_subnet = ipv6_is_match('2001:db8:abcd:0012::0', '2001:db8::/32')
```
## Usage
### Syntax
```kusto theme={null}
ipv6_is_match(ipv6_address, ipv6_subnet)
```
### Parameters
| Name | Type | Description |
| -------------- | ------ | ---------------------------------------------------------- |
| `ipv6_address` | string | The full IPv6 address you want to check. |
| `ipv6_subnet` | string | The target subnet in CIDR notation, e.g., `2001:db8::/32`. |
### Returns
A boolean value:
* `true` if the `ipv6_address` belongs to the specified `ipv6_subnet`.
* `false` otherwise.
## Example
Identify requests that originate from a known IPv6 subnet.
**Query**
```kusto theme={null}
['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
* [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.
# parse_ipv4
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/parse-ipv4
This page explains how to use the parse_ipv4 function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval ip_int = tonumber(replace(ip, "\\.", ""))
```
```kusto APL equivalent theme={null}
['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 theme={null}
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 theme={null}
['sample-http-logs']
| extend ip_long = parse_ipv4(uri)
```
## Usage
### Syntax
```kusto theme={null}
parse_ipv4(ipv4_address)
```
### Parameters
| Parameter | Type | Description |
| -------------- | ------ | --------------------------------------------- |
| `ipv4_address` | string | The IPv4 address to parse into a long number. |
### 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
You can use the `parse_ipv4` function to analyze web traffic by representing IP addresses as long numbers.
**Query**
```kusto theme={null}
['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
* [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.
# parse_ipv4_mask
Source: https://axiom.co/docs/apl/scalar-functions/ip-functions/parse-ipv4-mask
This page explains how to use the parse_ipv4_mask function in APL.
## 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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval converted_ip = cidrmatch("192.168.1.0/24", ip)
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT inet_aton('192.168.1.0') & (0xFFFFFFFF << (32 - 24)) AS converted_ip
```
```kusto APL equivalent theme={null}
print converted_ip = parse_ipv4_mask("192.168.1.0", 24)
```
## Usage
### Syntax
```kusto theme={null}
parse_ipv4_mask(ip, prefix)
```
### 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
* A signed, 64-bit long number in big-endian order if the conversion is successful.
* `null` if the conversion is unsuccessful.
### Example
```kusto theme={null}
print parse_ipv4_mask("127.0.0.1", 24)
```
## Use case example
Use `parse_ipv4_mask` to analyze logs and filter entries based on IP ranges.
**Query**
```kusto theme={null}
['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
* [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.
# Mathematical functions
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions
Learn how to use and combine different mathematical functions in APL.
The table summarizes the mathematical functions available in APL.
| Name | Description |
| --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| [abs](#abs) | Calculates the absolute value of the input. |
| [acos](#acos) | Returns the angle whose cosine is the specified number (the inverse operation of cos()). |
| [asin](#asin) | Returns the angle whose sine is the specified number (the inverse operation of sin()). |
| [atan](#atan) | Returns the angle whose tangent is the specified number (the inverse operation of tan()). |
| [atan2](#atan2) | Calculates the angle, in radians, between the positive x-axis and the ray from the origin to the point (y, x). |
| [cos](#cos) | Returns the cosine function. |
| [cot](#cot) | Returns the cotangent function. |
| [degrees](#degrees) | Converts angle value in radians into value in degrees, using formula degrees = (180 / PI) \* angle-in-radians. |
| [exp](#exp) | The base-e exponential function of x, which is e raised to the power x: e^x. |
| [exp10](#exp10) | The base-10 exponential function of x, which is 10 raised to the power x: 10^x. |
| [exp2](#exp2) | The base-2 exponential function of x, which is 2 raised to the power x: 2^x. |
| [gamma](#gamma) | Computes gamma function. |
| [isfinite](#isfinite) | Returns whether input is a finite value (neither infinite nor NaN). |
| [isinf](#isinf) | Returns whether input is an infinite (positive or negative) value. |
| [isint](#isint) | Returns whether input is an integer (positive or negative) value |
| [isnan](#isnan) | Returns whether input is a Not a Number (NaN) value. |
| [log](#log) | Returns the natural logarithm function. |
| [log10](#log10) | Returns the common (base-10) logarithm function. |
| [log2](#log2) | Returns the base-2 logarithm function. |
| [loggamma](#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](#not) | Reverses the value of its bool argument. |
| [pi](#pi) | Returns the constant value of Pi (π). |
| [pow](#pow) | Returns a result of raising to power. |
| [radians](#radians) | Converts angle value in degrees into value in radians, using formula radians = (PI / 180) \* angle-in-degrees. |
| [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](#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](#sign) | Sign of a numeric expression. |
| [sin](#sin) | Returns the sine function. |
| [sqrt](#sqrt) | Returns the square root function. |
| [tan](#tan) | Returns the tangent function. |
## abs
Calculates the absolute value of the input.
### Arguments
| **Name** | **Type** | **Required or Optional** | **Description** |
| -------- | --------------------- | ------------------------ | -------------------------- |
| x | int, real or timespan | Required | The value to make absolute |
### Returns
* Absolute value of x.
### Examples
```kusto theme={null}
abs(x)
```
```kusto theme={null}
abs(80.5) == 80.5
```
```kusto theme={null}
['sample-http-logs']
| project absolute_value = abs(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%20project%20absolute_value%20%3D%20abs%28req_duration_ms%29%22%7D)
## acos
Returns the angle whose cosine is the specified number (the inverse operation of cos()) .
### Arguments
| **Name** | **Type** | **Required or Optional** | **Description** |
| -------- | -------- | ------------------------ | -------------------------------- |
| x | real | Required | A real number in range \[-1,. 1] |
### Returns
* The value of the arc cosine of x
* `null` if `x` \< -1 or `x` > 1
### Examples
```kusto theme={null}
acos(x)
```
```kusto theme={null}
acos(-1) == 3.141592653589793
```
[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%20cosine_angle%20%3D%20acos%28-1%29%22%7D)
## asin
Returns the angle whose sine is the specified number (the inverse operation of sin()) .
### Arguments
| **Name** | **Type** | **Required or Optional** | **Description** |
| -------- | -------- | ------------------------ | -------------------------------- |
| x | real | Required | A real number in range \[-1,. 1] |
* x: A real number in range \[-1, 1].
### Returns
* The value of the arc sine of x
* null if x \< -1 or x > 1
### Examples
```kusto theme={null}
asin(x)
```
```kusto theme={null}
['sample-http-logs']
| project inverse_sin_angle = asin(-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%20project%20inverse_sin_angle%20%3D%20asin%28-1%29%22%7D)
## atan
Returns the angle whose tangent is the specified number (the inverse operation of tan()) .
### Arguments
x: A real number.
### Returns
The value of the arc tangent of x
### Examples
```kusto theme={null}
atan(x)
```
```kusto theme={null}
atan(-1) == -0.7853981633974483
```
[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%20inverse_tan_angle%20%3D%20atan%28-1%29%22%7D)
## atan2
Calculates the angle, in radians, between the positive x-axis and the ray from the origin to the point (y, x).
### Arguments
x: X coordinate (a real number).
y: Y coordinate (a real number).
### Returns
The angle, in radians, between the positive x-axis and the ray from the origin to the point (y, x).
### Examples
```kusto theme={null}
atan2(y,x)
```
```kusto theme={null}
atan2(-1, 1) == -0.7853981633974483
```
[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%20angle_in_rads%20%3D%20atan2%28-1%2C%201%29%22%7D)
## cos
Returns the cosine function.
### Arguments
x: A real number.
### Returns
The result of cosine of x.
### Examples
```kusto theme={null}
cos(x)
```
```kusto theme={null}
cos(-1) == 0.5403023058681398
```
[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%20cosine_function%20%3D%20cos%28-1%29%22%7D)
## cot
Returns the cotangent function.
### Arguments
x: A real number.
### Returns
The result of cotangent of x.
### Examples
```kusto theme={null}
cot(x)
```
```kusto theme={null}
cot(-1) == -0.6420926159343308
```
[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%20cotangent_function%20%3D%20cot%28-1%29%22%7D)
## degrees
Converts angle value in radians into value in degrees, using formula degrees = (180 / PI ) \* angle\_in\_radians
### Arguments
| **Name** | **Type** | **Required or Optional** | **Description** |
| -------- | -------- | ------------------------ | ----------------- |
| a | real | Required | Angle in radians. |
### Returns
The corresponding angle in degrees for an angle specified in radians.
### Examples
```kusto theme={null}
degrees(a)
```
```kusto theme={null}
degrees(3.14) == 179.9087476710785
```
[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%20degree_rads%20%3D%20degrees%283.14%29%22%7D)
## exp
The base-e exponential function of x, which is e raised to the power x: e^x.
### Arguments
| **Name** | **Type** | **Required or Optional** | **Description** |
| -------- | ----------- | ------------------------ | ---------------------- |
| x | real number | Required | Value of the exponent. |
### Returns
* Exponential value of x.
* For natural (base-e) logarithms, see [log](/apl/scalar-functions/mathematical-functions#log\(\)).
* For exponential functions of base-2 and base-10 logarithms, see [exp2](/apl/scalar-functions/mathematical-functions#exp2\(\)), [exp10](/apl/scalar-functions/mathematical-functions#exp10\(\))
### Examples
```kusto theme={null}
exp(x)
```
```kusto theme={null}
exp(1) == 2.718281828459045
```
[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%20exponential_value%20%3D%20exp%281%29%22%7D)
## exp2
The base-2 exponential function of x, which is 2 raised to the power x: 2^x.
### Arguments
| **Name** | **Type** | **Required or Optional** | **Description** |
| -------- | ----------- | ------------------------ | ---------------------- |
| x | real number | Required | Value of the exponent. |
### Returns
* Exponential value of x.
* For natural (base-2) logarithms, see [log2](/apl/scalar-functions/mathematical-functions#log2\(\)).
* For exponential functions of base-e and base-10 logarithms, see [exp](/apl/scalar-functions/mathematical-functions#exp\(\)), [exp10](/apl/scalar-functions/mathematical-functions#exp10\(\))
### Examples
```kusto theme={null}
exp2(x)
```
```kusto theme={null}
| project base_2_exponential_value = exp2(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%20project%20base_2_exponential_value%20%3D%20exp2%28req_duration_ms%29%22%7D)
## gamma
Computes [gamma function](https://en.wikipedia.org/wiki/Gamma_function)
### Arguments
* x: Parameter for the gamma function
### Returns
* Gamma function of x.
* For computing log-gamma function, see loggamma().
### Examples
```kusto theme={null}
gamma(x)
```
```kusto theme={null}
gamma(4) == 6
```
[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%20gamma_function%20%3D%20gamma%284%29%22%7D)
## isinf
Returns whether input is an infinite (positive or negative) value.
### Example
```kusto theme={null}
isinf(x)
```
```kusto theme={null}
isinf(45.56) == false
```
[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%20infinite_value%20%3D%20isinf%2845.56%29%22%7D)
### Arguments
x: A real number.
### Returns
A non-zero value (true) if x is a positive or negative infinite; and zero (false) otherwise.
## isnan
Returns whether input is Not-a-Number (NaN) value.
### Arguments
x: A real number.
### Returns
A non-zero value (true) if x is NaN; and zero (false) otherwise.
### Examples
```kusto theme={null}
isnan(x)
```
```kusto theme={null}
isnan(45.56) == false
```
[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%20nan%20%3D%20isnan%2845.56%29%22%7D)
## log
log() returns the natural logarithm function.
### Arguments
| **Name** | **Type** | **Required or Optional** | **Description** |
| -------- | -------- | ------------------------ | ------------------ |
| x | real | Required | A real number > 0. |
### Returns
The natural logarithm is the base-e logarithm: the inverse of the natural exponential function (exp).
null if the argument is negative or null or can’t be converted to a real value.
### Examples
```kusto theme={null}
log(x)
```
```kusto theme={null}
log(1) == 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%20project%20natural_log%20%3D%20log%281%29%22%7D)
## log10
log10() returns the common (base-10) logarithm function.
### Arguments
| **Name** | **Type** | **Required or Optional** | **Description** |
| -------- | -------- | ------------------------ | ------------------ |
| x | real | Required | A real number > 0. |
### Returns
The common logarithm is the base-10 logarithm: the inverse of the exponential function (exp) with base 10.
null if the argument is negative or null or can’t be converted to a real value.
### Examples
```kusto theme={null}
log10(x)
```
```kusto theme={null}
log10(4) == 0.6020599913279624
```
[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%20base10%20%3D%20log10%284%29%22%7D)
## log2
log2() returns the base-2 logarithm function.
### Arguments
| **Name** | **Type** | **Required or Optional** | **Description** |
| -------- | -------- | ------------------------ | ------------------ |
| x | real | Required | A real number > 0. |
### Returns
The logarithm is the base-2 logarithm: the inverse of the exponential function (exp) with base 2.
null if the argument is negative or null or can’t be converted to a real value.
### Examples
```kusto theme={null}
log2(x)
```
```kusto theme={null}
log2(6) == 2.584962500721156
```
[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%20base2_log%20%3D%20log2%286%29%22%7D)
## loggamma
Computes log of absolute value of the [gamma function](https://en.wikipedia.org/wiki/Gamma_function)
### Arguments
x: Parameter for the gamma function
### Returns
* Returns the natural logarithm of the absolute value of the gamma function of x.
### Examples
````kusto theme={null}
loggamma(x)
```kusto
loggamma(16) == 27.89927138384089
````
[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%20gamma_function%20%3D%20loggamma%2816%29%22%7D)
## not
Reverses the value of its bool argument.
### Examples
```kusto theme={null}
not(expr)
```
[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%20reverse%20%3D%20not%28false%29%22%7D)
### Arguments
| **Name** | **Type** | **Required or Optional** | **Description** |
| -------- | -------- | ------------------------ | ----------------------------------- |
| Expr | bool | Required | A `bool` expression to be reversed. |
### Returns
Returns the reversed logical value of its bool argument.
## pi
Returns the constant value of Pi.
### Returns
* The double value of Pi (3.1415926...)
### Examples
```kusto theme={null}
pi()
```
```kusto theme={null}
['sample-http-logs']
| project pie = pi()
```
[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%20pie%20%3D%20pi%28%29%22%7D)
## pow
Returns a result of raising to power
### Examples
```kusto theme={null}
pow(base, exponent )
```
```kusto theme={null}
pow(2, 6) == 64
```
[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%20power%20%3D%20pow%282%2C%206%29%22%7D)
### Arguments
* *base:* Base value.
* *exponent:* Exponent value.
### Returns
Returns base raised to the power exponent: base ^ exponent.
## radians
Converts angle value in degrees into value in radians, using formula `radians = (PI / 180 ) * angle_in_degrees`
### Arguments
| **Name** | **Type** | **Required or Optional** | **Description** |
| -------- | -------- | ------------------------ | --------------------------------- |
| a | real | Required | Angle in degrees (a real number). |
### Returns
The corresponding angle in radians for an angle specified in degrees.
### Examples
```kusto theme={null}
radians(a)
```
```kusto theme={null}
radians(60) == 1.0471975511965976
```
[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%20radians%20%3D%20radians%2860%29%22%7D)
## round
Returns the rounded source to the specified precision.
### Arguments
* source: The source scalar the round is calculated on.
* Precision: Number of digits the source will be rounded to.(default value is 0)
### Returns
The rounded source to the specified precision.
### Examples
```kusto theme={null}
round(source [, Precision])
```
```kusto theme={null}
round(25.563663) == 26
```
[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%20rounded_value%20%3D%20round%2825.563663%29%22%7D)
## sign
Sign of a numeric expression
### Examples
```kusto theme={null}
sign(x)
```
```kusto theme={null}
sign(25.563663) == 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%20project%20numeric_expression%20%3D%20sign%2825.563663%29%22%7D)
### Arguments
* x: A real number.
### Returns
* The positive (+1), zero (0), or negative (-1) sign of the specified expression.
## sin
Returns the sine function.
### Examples
```kusto theme={null}
sin(x)
```
```kusto theme={null}
sin(25.563663) == 0.41770848373492825
```
[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%20sine_function%20%3D%20sin%2825.563663%29%22%7D)
### Arguments
| **Name** | **Type** | **Required or Optional** | **Description** |
| -------- | -------- | ------------------------ | --------------- |
| x | real | Required | A real number. |
### Returns
The result of sin(x)
## sqrt
Returns the square root function.
### Examples
```kusto theme={null}
sqrt(x)
```
```kusto theme={null}
sqrt(25.563663) == 5.0560521160288685
```
[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%20squareroot%20%3D%20sqrt%2825.563663%29%22%7D)
### Arguments
| **Name** | **Type** | **Required or Optional** | **Description** |
| -------- | -------- | ------------------------ | ------------------- |
| x | real | Required | A real number >= 0. |
### Returns
* A positive number such that \_sqrt(x) \_ sqrt(x) == x\*
* null if the argument is negative or can’t be converted to a real value.
## tan
Returns the tangent function.
### Examples
```kusto theme={null}
tan(x)
```
```kusto theme={null}
tan(25.563663) == 0.4597371460602336
```
[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%20tangent_function%20%3D%20tan%2825.563663%29%22%7D)
### Argument
| **Name** | **Type** | **Required or Optional** | **Description** |
| -------- | -------- | ------------------------ | --------------- |
| x | real | Required | A real number. |
### Returns
* The result of `tan(x)`
## exp10
The base-10 exponential function of x, which is 10 raised to the power x: 10^x.
### Examples
```kusto theme={null}
exp10(x)
```
```kusto theme={null}
exp10(25.563663) == 36,615,333,994,520,800,000,000,000
```
[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%20base_10_exponential%20%3D%20pow%2810%2C%2025.563663%29%22%7D)
### Arguments
| **Name** | **Type** | **Required or Optional** | **Description** |
| -------- | -------- | ------------------------ | ------------------------------------- |
| x | real | Required | A real number, value of the exponent. |
### Returns
* Exponential value of x.
* For natural (base-10) logarithms, see [log10](/apl/scalar-functions/mathematical-functions#log10\(\)).
* For exponential functions of base-e and base-2 logarithms, see [exp](/apl/scalar-functions/mathematical-functions#exp\(\)), [exp2](/apl/scalar-functions/mathematical-functions#exp2\(\))
## isint
Returns whether input is an integer (positive or negative) value.
### Arguments
* Expr: expression value which can be a real number
### Returns
A non-zero value (true) if expression is a positive or negative integer; and zero (false) otherwise.
### Examples
```kusto theme={null}
isint(expression)
```
```kusto theme={null}
isint(resp_body_size_bytes) == true
```
```kusto theme={null}
isint(25.563663) == false
```
[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%20integer_value%20%3D%20isint%2825.563663%29%22%7D)
## isfinite
Returns whether input is a finite value (is neither infinite nor NaN).
### Arguments
* number: A real number.
### Returns
A non-zero value (true) if x is finite; and zero (false) otherwise.
### Examples
```kusto theme={null}
isfinite(number)
```
```kusto theme={null}
isfinite(25.563663) == true
```
[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%20isfinite_value%20%3D%20isfinite%2825.563663%29%22%7D)
# max_of
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/max-of
This page explains how to use the max_of function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
eval max_value=if(a > b and a > c, a, if(b > c, b, c))
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT GREATEST(a, b, c) AS max_value FROM table
```
```kusto APL equivalent theme={null}
extend max_value = max_of(a, b, c)
```
## Usage
### Syntax
```kusto theme={null}
max_of(Expr1, Expr2, ..., ExprN)
```
### Parameters
The function takes a comma-separated list of expressions to compare. All values must be of the same type.
### 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
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 theme={null}
['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 |
# min_of
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/min-of
This page explains how to use the min_of function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
eval smallest=min(field1, field2)
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT LEAST(col1, col2, col3) AS min_val FROM table;
```
```kusto APL equivalent theme={null}
extend min_val = min_of(col1, col2, col3)
```
## Usage
### Syntax
```kusto theme={null}
min_of(Expr1, Expr2, ..., ExprN)
```
### Parameters
The function takes a comma-separated list of expressions to compare. All values must be of the same type.
### 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
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 theme={null}
['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 |
# rand
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/rand
This page explains how to use the rand function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval r=random()/2147483647
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT RAND() as r;
```
```kusto APL equivalent theme={null}
print r = rand()
```
## Usage
### Syntax
```kusto theme={null}
rand()
rand(range)
```
### 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
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
**Query**
```kusto theme={null}
['sample-http-logs']
| extend random = rand()
```
[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%20random%20%3D%20rand\(\)%22%7D)
**Output**
| \_time | random |
| -------------------- | ------------------ |
| 2024-07-10T14:32:00Z | 0.6324890121902683 |
# range
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/range
This page explains how to use the range function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| makeresults count=10
| streamstats count as x
```
```kusto APL equivalent theme={null}
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 theme={null}
WITH RECURSIVE seq AS (
SELECT 1 AS x
UNION ALL
SELECT x + 1 FROM seq WHERE x < 10
)
SELECT * FROM seq;
```
```kusto APL equivalent theme={null}
print r = range(1, 10, 1)
```
## Usage
### Syntax
```kusto theme={null}
range(start, stop [, step])
```
### 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
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
Generate an array of durations to help classify HTTP request latencies.
**Query**
```kusto theme={null}
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 theme={null}
[
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 theme={null}
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 theme={null}
[
"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 theme={null}
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 theme={null}
[
"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.
# set_difference
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/set-difference
This page explains how to use the set_difference function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval a=mvappend("a", "b", "c"), b=mvappend("b", "c")
| eval diff=mvfilter(NOT match(a, b))
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT value FROM array1
EXCEPT
SELECT value FROM array2;
```
```kusto APL equivalent theme={null}
print a=dynamic(['a', 'b', 'c']), b=dynamic(['b', 'c'])
| extend diff=set_difference(a, b)
```
## Usage
### Syntax
```kusto theme={null}
set_difference(Array1, Array2)
```
### Parameters
| Name | Type | Description |
| -------- | ----- | ---------------------------------------------------- |
| `Array1` | array | The array to subtract from. |
| `Array2` | array | The array containing values to remove from `Array1`. |
### Returns
An array that includes all values from `Array1` that aren’t present in `Array2`. The result doesn’t include duplicates.
## Example
Use `set_difference` to return the difference between two arrays.
**Query**
```kusto theme={null}
['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
* [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.
# set_has_element
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/set-has-element
This page explains how to use the set_has_element function in APL.
`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`.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
index=web
| stats values(uri) AS uris BY id
| where "/checkout" in uris
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT id
FROM sample_http_logs
GROUP BY id
HAVING 'US' = ANY(ARRAY_AGG(country));
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize countries=make_set(['geo.country']) by id
| where set_has_element(countries, 'US')
```
## Usage
### Syntax
```kusto theme={null}
set_has_element(set, value)
```
### 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
A `bool` that’s true when `value` exists in `set` and false otherwise.
## Example
Use `set_has_element` to determine if a set contains a specific value.
**Query**
```kusto theme={null}
['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
* [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.
# set_intersect
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/set-intersect
This page explains how to use the set_intersect function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval A=split("apple,banana,cherry", ",")
| eval B=split("banana,cherry,dragonfruit", ",")
| eval C=mvfilter(match(A, B))
```
```kusto APL equivalent theme={null}
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 theme={null}
-- Using PostgreSQL syntax
SELECT ARRAY(
SELECT unnest(array['apple','banana','cherry'])
INTERSECT
SELECT unnest(array['banana','cherry','dragonfruit'])
);
```
```kusto APL equivalent theme={null}
print A=dynamic(['apple', 'banana', 'cherry']), B=dynamic(['banana', 'cherry', 'dragonfruit'])
| extend C = set_intersect(A, B)
```
## Usage
### Syntax
```kusto theme={null}
set_intersect(Array1, Array2)
```
### Parameters
| Name | Type | Description |
| -------- | ------- | ---------------------------- |
| `Array1` | dynamic | The first array to compare. |
| `Array2` | dynamic | The second array to compare. |
### Returns
A dynamic array containing elements that exist in both `Array1` and `Array2`, in the order they appear in `Array1`, with duplicates removed.
## Example
Use `set_intersect` to return the intersection of two arrays.
**Query**
```kusto theme={null}
['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
* [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.
# set_union
Source: https://axiom.co/docs/apl/scalar-functions/mathematical-functions/set-union
This page explains how to use the set_union function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval result=mvappend(array1, array2)
| eval result=mvdedup(result)
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT value FROM (
SELECT value FROM table1
UNION
SELECT value FROM table2
)
```
```kusto APL equivalent theme={null}
extend result = set_union(array1, array2)
```
## Usage
### Syntax
```kusto theme={null}
set_union(Array1, Array2)
```
### Parameters
| Name | Type | Description |
| ------ | ------- | -------------------------- |
| Array1 | dynamic | The first array to merge. |
| Array2 | dynamic | The second array to merge. |
### Returns
A dynamic array that contains the distinct elements of both input arrays.
## Example
Use `set_union` to return the union of two arrays.
**Query**
```kusto theme={null}
['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
* [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.
# column_ifexists
Source: https://axiom.co/docs/apl/scalar-functions/metadata-functions/column-ifexists
This page explains how to use the column_ifexists function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval field=if(isnull(Capital), State, Capital)
```
```kusto APL equivalent theme={null}
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 theme={null}
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 theme={null}
StormEvents | project column_ifexists('Capital', State)
```
## Usage
### Syntax
```kusto theme={null}
column_ifexists(FieldName, DefaultValue)
```
### 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 the field specified by `FieldName` if it exists in the table schema. Otherwise, returns the result of `DefaultValue`.
## 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 theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# cursor_current
Source: https://axiom.co/docs/apl/scalar-functions/metadata-functions/cursor-current
This page explains how to use the cursor_current function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| stats max(_indextime) as last_processed
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT MAX(timestamp) as last_processed_time
FROM logs
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend cursor = cursor_current()
| summarize max(_time), processing_cursor = make_list(cursor)[0]
```
## Usage
### Syntax
```kusto theme={null}
cursor_current()
```
### Parameters
This function takes no parameters.
### 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 `cursor_current` to track the last processed position when implementing incremental log processing pipelines.
**Query**
```kusto theme={null}
['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 theme={null}
['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
* [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.
# ingestion_time
Source: https://axiom.co/docs/apl/scalar-functions/metadata-functions/ingestion-time
This page explains how to use the ingestion_time function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval ingest_time=_indextime
```
```kusto APL equivalent theme={null}
...
| 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 theme={null}
SELECT event_time, CURRENT_TIMESTAMP AS ingest_time FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend ingest_time = ingestion_time()
```
## Usage
### Syntax
```kusto theme={null}
ingestion_time()
```
### Parameters
This function doesn’t take any parameters.
### Returns
A `datetime` value that represents when each record was ingested into Axiom.
## Use case examples
Use `ingestion_time` to identify delays between when an HTTP request occurred and when it was ingested into Axiom.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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.
# Pair functions
Source: https://axiom.co/docs/apl/scalar-functions/pair-functions
Learn how to use and combine different pair functions in APL
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
| 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. |
# find_pair
Source: https://axiom.co/docs/apl/scalar-functions/pair-functions/find-pair
This page explains how to use the find_pair function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval found_tag=mvfind(tags, 'host=server.*')
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT *
FROM logs
WHERE JSON_EXTRACT(tags, '$[*].key') LIKE 'host%'
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend tags = dynamic(['host:server1', 'env:prod'])
| extend found = find_pair(tags, 'host*', '*')
```
## Usage
### Syntax
```kusto theme={null}
find_pair(array, key_pattern, value_pattern)
find_pair(array, key_pattern, value_pattern, separator)
```
### 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
A `dynamic` object representing the first matched pair, with `key`, `value` and `separator` properties. Returns `null` if no matching pair is found.
## Example
Use `find_pair` to extract specific metadata from HTTP logs stored as tag arrays.
**Query**
```kusto theme={null}
['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
* [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.
# pair
Source: https://axiom.co/docs/apl/scalar-functions/pair-functions/pair
This page explains how to use the pair function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval tag = host . ":" . value
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT JSON_OBJECT('key', key_col, 'value', value_col) AS tag FROM logs
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend tag = pair('host', 'server1')
```
## Usage
### Syntax
```kusto theme={null}
pair(key, value, [separator])
```
### 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
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
Create pair objects to represent request metadata.
**Query**
```kusto theme={null}
['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
* [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.
# parse_pair
Source: https://axiom.co/docs/apl/scalar-functions/pair-functions/parse-pair
This page explains how to use the parse_pair function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| rex field=tag "(?[^:]+):(?.*)"
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT
SPLIT_PART(tag, ':', 1) AS key,
SPLIT_PART(tag, ':', 2) AS value
FROM logs
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend parsed = parse_pair('host:server1')
| extend key = parsed.key, value = parsed.value
```
## Usage
### Syntax
```kusto theme={null}
parse_pair(pair_string, [separator])
```
### 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
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 is not found in the input string, the function returns a pair with the entire input as the `value` and an empty `key`.
## Example
Extract and analyze tag components from HTTP request metadata.
**Query**
```kusto theme={null}
['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
* [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.
# Rounding functions
Source: https://axiom.co/docs/apl/scalar-functions/rounding-functions
Learn how to use and combine different rounding functions in APL
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
| 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. |
# bin
Source: https://axiom.co/docs/apl/scalar-functions/rounding-functions/bin
This page explains how to use the bin function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| bin span=5m _time
| stats count by _time
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT FLOOR(UNIX_TIMESTAMP(timestamp) / 300) * 300 AS time_bucket, COUNT(*)
FROM logs
GROUP BY time_bucket
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| summarize count() by bin(_time, 5m)
```
## Usage
### Syntax
```kusto theme={null}
bin(value, bin_size)
```
### 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
The nearest multiple of `bin_size` that is less than or equal to `value`. The return type matches the input type.
## Use case examples
Aggregate HTTP requests into 5-minute intervals to analyze traffic patterns.
**Query**
```kusto theme={null}
['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 theme={null}
['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
* [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.
# bin_auto
Source: https://axiom.co/docs/apl/scalar-functions/rounding-functions/bin-auto
This page explains how to use the bin_auto function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| timechart count
```
```kusto APL equivalent theme={null}
['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 theme={null}
-- 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 theme={null}
['sample-http-logs']
| summarize count() by bin_auto(_time)
```
## Usage
### Syntax
```kusto theme={null}
bin_auto(expression)
```
### Parameters
| Name | Type | Description |
| ------------ | ---------- | ------------------------------------------------------------- |
| `expression` | `datetime` | A datetime expression to round. Typically the `_time` column. |
### 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
Create a time-series view of HTTP traffic with automatic time granularity.
**Query**
```kusto theme={null}
['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 theme={null}
['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
* [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.
# ceiling
Source: https://axiom.co/docs/apl/scalar-functions/rounding-functions/ceiling
This page explains how to use the ceiling function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval rounded_up = ceil(request_time)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT CEILING(request_duration) AS rounded_up FROM logs
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend rounded_up = ceiling(req_duration_ms)
```
## Usage
### Syntax
```kusto theme={null}
ceiling(number)
```
### Parameters
| Name | Type | Description |
| -------- | ------ | ------------------------------ |
| `number` | `real` | The numeric value to round up. |
### Returns
An integer representing the smallest whole number greater than or equal to the input value.
## Use case examples
Round request durations up to whole milliseconds for consistent bucketing.
**Query**
```kusto theme={null}
['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 theme={null}
['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
* [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.
# floor
Source: https://axiom.co/docs/apl/scalar-functions/rounding-functions/floor
This page explains how to use the floor function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you use the `floor` function to round down values. APL's `floor` function works identically.
```sql Splunk example theme={null}
| eval completed_seconds = floor(duration_ms / 1000)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT FLOOR(request_duration / 1000) AS completed_seconds FROM logs
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend completed_seconds = floor(req_duration_ms / 1000)
```
## Usage
### Syntax
```kusto theme={null}
floor(number)
```
### Parameters
| Name | Type | Description |
| -------- | ------ | -------------------------------- |
| `number` | `real` | The numeric value to round down. |
### Returns
An integer representing the largest whole number less than or equal to the input value.
## Use case examples
Calculate completed seconds from millisecond durations.
**Query**
```kusto theme={null}
['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 theme={null}
['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
* [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.
# SQL functions
Source: https://axiom.co/docs/apl/scalar-functions/sql-functions
Learn how to use SQL functions in APL
## SQL functions
| **Function Name** | **Description** |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| [parse\_sql()](#parse-sql) | Interprets and analyzes SQL queries, making it easier to extract and understand SQL statements within datasets. |
| [format\_sql()](#format-sql) | Converts the data model produced by `parse_sql()` back into a SQL statement for validation or formatting purposes. |
## parse\_sql()
Analyzes an SQL statement and constructs a data model, enabling insights into the SQL content within a dataset.
### Limitations
* It’s mainly used for simple SQL queries. SQL statements like stored procedures, Windows functions, common table expressions (CTEs), recursive queries, advanced statistical functions, and special joins aren’t supported.
### Arguments
| **Name** | **Type** | **Required or Optional** | **Description** |
| -------------- | -------- | ------------------------ | ----------------------------- |
| sql\_statement | string | Required | The SQL statement to analyze. |
### Returns
A dictionary representing the structured data model of the provided SQL statement. This model includes maps or slices that detail the various components of the SQL statement, such as tables, fields, conditions, etc.
### Examples
### Basic data retrieval
The SQL statement **`SELECT * FROM db`** retrieves all columns and rows from the table named **`db`**.
```kusto theme={null}
hn
| project parse_sql("select * from db")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22hn%20%7C%20project%20parse_sql\('select%20*%20from%20db'\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
### WHERE Clause
This example parses a **`SELECT`** statement with a **`WHERE`** clause, filtering **`customers`** by **`subscription_status`**.
```kusto theme={null}
hn
| project parse_sql("SELECT id, email FROM customers WHERE subscription_status = 'active'")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22hn%20%7C%20project%20parse_sql\(%5C%22SELECT%20id%2C%20email%20FROM%20customers%20WHERE%20subscription_status%20%3D%20'active'%5C%22\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
### JOIN operation
This example shows parsing an SQL statement that performs a **`JOIN`** operation between **`orders`** and **`customers`** tables to match orders with customer names.
```kusto theme={null}
hn
| project parse_sql("SELECT orders.id, customers.name FROM orders JOIN customers ON orders.customer_id = customers.id")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22hn%20%7C%20project%20parse_sql\(%5C%22SELECT%20orders.id%2C%20customers.name%20FROM%20orders%20JOIN%20customers%20ON%20orders.customer_id%20%3D%20customers.id%5C%22\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
### GROUP BY Clause
In this example, the **`parse_sql()`** function is used to parse an SQL statement that aggregates order counts by **`product_id`** using the **`GROUP BY`** clause.
```kusto theme={null}
hn
| project parse_sql("SELECT product_id, COUNT(*) as order_count FROM orders GROUP BY product_id")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22hn%20%7C%20project%20parse_sql\(%5C%22SELECT%20product_id%2C%20COUNT\(*\)%20as%20order_count%20FROM%20orders%20GROUP%20BY%20product_id%5C%22\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
### Nested Queries
This example demonstrates parsing a nested SQL query, where the inner query selects **`user_id`** from **`orders`** based on **`purchase_date`**, and the outer query selects names from **`users`** based on those IDs.
```kusto theme={null}
hn
| project parse_sql("SELECT name FROM users WHERE id IN (SELECT user_id FROM orders WHERE purchase_date > '2022-01-01')")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22hn%20%7C%20project%20parse_sql\(%5C%22SELECT%20name%20FROM%20users%20WHERE%20id%20IN%20\(SELECT%20user_id%20FROM%20orders%20WHERE%20purchase_date%20%3E%20'2022-01-01'\)%5C%22\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
### ORDER BY Clause
Here, the example shows how to parse an SQL statement that orders **`users`** by **`registration_date`** in descending order.
```kusto theme={null}
hn
| project parse_sql("SELECT name, registration_date FROM users ORDER BY registration_date DESC")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22hn%20%7C%20project%20parse_sql\(%5C%22SELECT%20name%2C%20registration_date%20FROM%20users%20ORDER%20BY%20registration_date%20DESC%5C%22\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
### Sorting users by registration data
This example demonstrates parsing an SQL statement that retrieves the **`name`** and **`registration_date`** of users from the **`users`** table, and orders the results by **`registration_date`** in descending order, showing how to sort data based on a specific column.
```kusto theme={null}
hn | extend parse_sql("SELECT name, registration_date FROM users ORDER BY registration_date DESC")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22hn%20%7C%20extend%20parse_sql\(%5C%22SELECT%20name%2C%20registration_date%20FROM%20users%20ORDER%20BY%20registration_date%20DESC%5C%22\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
### Querying with index hints to use a specific index
This query hints at MySQL to use a specific index named **`index_name`** when executing the SELECT statement on the **`users`** table.
```kusto theme={null}
hn
| project parse_sql("SELECT * FROM users USE INDEX (index_name) WHERE user_id = 101")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22hn%20%7C%20project%20parse_sql\(%5C%22SELECT%20*%20FROM%20users%20USE%20INDEX%20\(index_name\)%20WHERE%20user_id%20%3D%20101%5C%22\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
### Inserting data with ON DUPLICATE KEY UPDATE
This example showcases MySQL’s ability to handle duplicate key entries elegantly by updating the existing record if the insert operation encounters a duplicate key.
```kusto theme={null}
hn
| project parse_sql("INSERT INTO settings (user_id, setting, value) VALUES (1, 'theme', 'dark') ON DUPLICATE KEY UPDATE value='dark'")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22hn%20%7C%20project%20parse_sql\(%5C%22INSERT%20INTO%20settings%20\(user_id%2C%20setting%2C%20value\)%20VALUES%20\(1%2C%20'theme'%2C%20'dark'\)%20ON%20DUPLICATE%20KEY%20UPDATE%20value%3D'dark'%5C%22\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
### Using JSON functions
This query demonstrates MySQL’s support for JSON data types and functions, extracting the age from a JSON object stored in the **`user_info`** column.
```kusto theme={null}
hn
| project parse_sql("SELECT JSON_EXTRACT(user_info, '$.age') AS age FROM users WHERE user_id = 101")
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22hn%20%7C%20project%20parse_sql\(%5C%22SELECT%20JSON_EXTRACT\(user_info%2C%20%27%24.age%27\)%20AS%20age%20FROM%20users%20WHERE%20user_id%20%3D%20101%5C%22\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
## format\_sql()
Transforms the data model output by `parse_sql()` back into a SQL statement. Useful for testing and ensuring that the parsing accurately retains the original structure and intent of the SQL statement.
### Arguments
| **Name** | **Type** | **Required or Optional** | **Description** |
| ------------------ | ---------- | ------------------------ | -------------------------------------------------- |
| parsed\_sql\_model | dictionary | Required | The structured data model output by `parse_sql()`. |
### Returns
A string that represents the SQL statement reconstructed from the provided data model.
### Examples
### Reformatting a basic SELECT Query
After parsing a SQL statement, you can reformat it back to its original or a standard SQL format.
```kusto theme={null}
hn
| extend parsed = parse_sql("SELECT * FROM db")
| project formatted_sql = format_sql(parsed)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22hn%20%7C%20extend%20parsed%20%3D%20parse_sql\(%5C%22SELECT%20*%20FROM%20db%5C%22\)%20%7C%20project%20formatted_sql%20%3D%20format_sql\(parsed\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
### Formatting SQL Queries
This example first parses a SQL statement to analyze its structure and then formats the parsed structure back into a SQL string using `format_sql`.
```kusto theme={null}
hn
| extend parsed = parse_sql("SELECT name, registration_date FROM users ORDER BY registration_date DESC")
| project format_sql(parsed)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22hn%20%7C%20extend%20parsed%20%3D%20parse_sql\(%5C%22SELECT%20name%2C%20registration_date%20FROM%20users%20ORDER%20BY%20registration_date%20DESC%5C%22\)%20%7C%20project%20formatted_sql%20%3D%20format_sql\(parsed\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
### Formatting a simple SELECT Statement
This example demonstrates parsing a straightforward `SELECT` statement that retrieves user IDs and usernames from an `user_accounts` table where the `active` status is `1`. After parsing, it uses `format_sql` to convert the parsed data back into a SQL string.
```kusto theme={null}
hn
| extend parsed = parse_sql("SELECT user_id, username FROM user_accounts WHERE active = 1")
| project formatted_sql = format_sql(parsed)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22hn%20%7C%20extend%20parsed%20%3D%20parse_sql\(%5C%22SELECT%20user_id%2C%20username%20FROM%20user_accounts%20WHERE%20active%20%3D%201%5C%22\)%20%7C%20project%20formatted_sql%20%3D%20format_sql\(parsed\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
### Reformatting a complex query with JOINS
In this example, a more complex SQL statement involving an `INNER JOIN` between `orders` and `customers` tables is parsed. The query selects orders and customer names for orders placed after January 1, 2023. `format_sql` is then used to reformat the parsed structure into a SQL string.
```kusto theme={null}
hn
| extend parsed = parse_sql("SELECT orders.order_id, customers.name FROM orders INNER JOIN customers ON orders.customer_id = customers.id WHERE orders.order_date > '2023-01-01'")
| project formatted_sql = format_sql(parsed)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22hn%20%7C%20extend%20parsed%20%3D%20parse_sql\(%5C%22SELECT%20orders.order_id%2C%20customers.name%20FROM%20orders%20INNER%20JOIN%20customers%20ON%20orders.customer_id%20%3D%20customers.id%20WHERE%20orders.order_date%20%3E%20'2023-01-01'%5C%22\)%20%7C%20project%20formatted_sql%20%3D%20format_sql\(parsed\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
### Using format\_sql with aggregation functions
This example focuses on parsing an SQL statement that performs aggregation. It selects product IDs and counts of total sales from a `sales` table, grouping by `product_id` and having a condition on the count. After parsing, `format_sql` reformats the output into an SQL string.
```kusto theme={null}
hn
| extend parsed = parse_sql("SELECT product_id, COUNT(*) as total_sales FROM sales GROUP BY product_id HAVING COUNT(*) > 100")
| project formatted_sql = format_sql(parsed)
```
[Run in Playground](https://play.axiom.co/axiom-play-qf1k/query?initForm=%7B%22apl%22%3A%22hn%20%7C%20extend%20parsed%20%3D%20parse_sql\(%5C%22SELECT%20product_id%2C%20COUNT\(*\)%20as%20total_sales%20FROM%20sales%20GROUP%20BY%20product_id%20HAVING%20COUNT\(*\)%20%3E%20100%5C%22\)%20%7C%20project%20formatted_sql%20%3D%20format_sql\(parsed\)%22%2C%22queryOptions%22%3A%7B%22quickRange%22%3A%2290d%22%7D%7D)
# String functions
Source: https://axiom.co/docs/apl/scalar-functions/string-functions
Learn how to use and combine different string functions in APL
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. |
# base64_decode_toarray
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/base64-decode-toarray
This page explains how to use the base64_decode_toarray function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval decoded=base64decode(encodedField)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT CAST(FROM_BASE64(encodedField) AS BINARY) FROM my_table;
```
```kusto APL equivalent theme={null}
['my-dataset']
| extend decoded = base64_decode_toarray(encodedField)
```
## Usage
### Syntax
```kusto theme={null}
base64_decode_toarray(base64_input)
```
### 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
An array of integers representing the decoded byte values. If the input string is not valid Base64, the function returns an empty array.
## Use case examples
You want to decode a Base64-encoded field in logs to inspect raw payloads for debugging or transformation.
**Query**
```kusto theme={null}
['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 theme={null}
['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
* [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.
# base64_decode_tostring
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/base64-decode-tostring
This page explains how to use the base64_decode_tostring function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval decoded=base64decode(field_name)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT FROM_BASE64(field_name) AS decoded FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend decoded = base64_decode_tostring(field_name)
```
## Usage
### Syntax
```kusto theme={null}
base64_decode_tostring(value)
```
### Parameters
| Name | Type | Required | Description |
| ----- | ------ | -------- | ------------------------------------------------- |
| value | string | Yes | The Base64-encoded string to be decoded to UTF-8. |
### Returns
Returns the decoded UTF-8 string from the Base64-encoded input.
## Use case examples
Decode Base64-encoded messages or tokens in HTTP logs to analyze their content.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# base64_encode_fromarray
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/base64-encode-fromarray
This page explains how to use the base64_encode_fromarray function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval encoded=custom_base64_encode(byte_array_field)
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT TO_BASE64(BINARY 'data') AS encoded;
```
```kusto APL equivalent theme={null}
datatable(bytes: dynamic)
[
dynamic([192, 168, 1, 1])
]
| extend encoded = base64_encode_fromarray(bytes)
```
## Usage
### Syntax
```kusto theme={null}
base64_encode_fromarray(array)
```
### Parameters
| Name | Type | Required | Description |
| ----- | ------- | -------- | ----------------------------------------------------------------------- |
| array | dynamic | ✓ | A dynamic array of integers between 0 and 255 representing byte values. |
### Returns
If successful, returns a string representing the Base64-encoded version of the input byte array. If the input is not a valid array of bytes, the result is an empty string.
## Example
Use this function to encode request metadata for logging or comparison with external systems that require Base64-encoded fields.
**Query**
```kusto theme={null}
['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
* [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.
# base64_encode_tostring
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/base64-encode-tostring
This page explains how to use the base64_encode_tostring function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval encoded=base64encode(field_name)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT TO_BASE64(field_name) AS encoded FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend encoded = base64_encode_tostring(field_name)
```
## Usage
### Syntax
```kusto theme={null}
base64_encode_tostring(value)
```
### Parameters
| Name | Type | Required | Description |
| ----- | ------ | -------- | ----------------------------------------- |
| value | string | Yes | The input string to be encoded as Base64. |
### Returns
Returns the input string encoded as a Base64 string.
## Use case examples
Encode HTTP content types for secure transmission or storage in Base64 format.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# coalesce
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/coalesce
This page explains how to use the coalesce function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| eval result=coalesce(field1, field2, field3)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT COALESCE(field1, field2, field3) AS result FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend result = coalesce(field1, field2, field3)
```
## Usage
### Syntax
```kusto theme={null}
coalesce(expr1, expr2, ..., exprN)
```
### Parameters
| Name | Type | Required | Description |
| ------------------------ | ------ | -------- | ----------------------------------------------------------------------- |
| expr1, expr2, ..., exprN | scalar | Yes | A list of expressions to evaluate. At least one expression is required. |
### Returns
Returns the value of the first expression that is not null. For string expressions, returns the first non-empty string.
## Use case examples
Provide fallback values when analyzing HTTP logs where certain fields might be missing or empty.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [isnotnull](/apl/scalar-functions/string-functions/isnotnull): Checks if a value is not 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 is not empty and not null. Use this to validate strings before coalescing them.
# countof
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/countof
This page explains how to use the countof function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| rex field=message max_match=0 "error"
| eval error_count=mvcount(error)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT (LENGTH(field) - LENGTH(REPLACE(field, 'search', ''))) / LENGTH('search') AS count FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend count = countof('search', field)
```
## Usage
### Syntax
```kusto theme={null}
countof(search, text)
```
### 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 the number of times the search string appears in the text.
## Use case examples
Count how many times specific HTTP methods appear in URIs to identify API usage patterns.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# countof_regex
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/countof-regex
This page explains how to use the countof_regex function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| rex field=message max_match=0 "error|warning"
| eval pattern_count=mvcount(rex)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT REGEXP_COUNT(field, 'pattern') AS count FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend count = countof_regex('pattern', field)
```
## Usage
### Syntax
```kusto theme={null}
countof_regex(regex, text)
```
### 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 the number of times the regex pattern matches in the text.
## Use case examples
Count numeric patterns in URIs to identify parameterized endpoint usage.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# extract
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/extract
This page explains how to use the extract function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| rex field=message "user=(?\w+)"
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT REGEXP_SUBSTR(field, 'pattern', 1, 1, NULL, 1) AS extracted FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend extracted = extract('pattern', 1, field)
```
## Usage
### Syntax
```kusto theme={null}
extract(regex, captureGroup, text)
```
### 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 the substring matched by the specified capture group, or null if no match is found.
## Use case examples
Extract user IDs from HTTP request URIs to identify which users are accessing specific endpoints.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# extract_all
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/extract-all
This page explains how to use the extract_all function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| rex field=message max_match=0 "error_(?\d+)"
| mvexpand code
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT REGEXP_EXTRACT_ALL(field, 'pattern') AS matches FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend matches = extract_all('pattern', dynamic([1]), field)
```
## Usage
### Syntax
```kusto theme={null}
extract_all(regex, captureGroups, text)
```
### 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 (e.g., `dynamic([1])` or `dynamic([1,2])`). |
| text | string | Yes | The source string to search. |
### 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
Extract all numeric values from URIs to analyze parameter patterns in API requests.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# format_bytes
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/format-bytes
This page explains how to use the format_bytes function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| 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 theme={null}
['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 theme={null}
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 theme={null}
['sample-http-logs']
| extend size_str = format_bytes(resp_header_size_bytes)
```
## Usage
### Syntax
```kusto theme={null}
format_bytes(value, precision, units, base)
```
### 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 a formatted string representing the byte value with appropriate units.
## Use case examples
Format response header sizes as human-readable values for better analysis of payload patterns.
**Query**
```kusto theme={null}
['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 theme={null}
['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
* [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.
# format_url
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/format-url
This page explains how to use the format_url function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you typically concatenate URL parts manually with `eval`. APL's `format_url` provides a structured approach.
```sql Splunk example theme={null}
| eval full_url=scheme."://".host.":".port.path
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT CONCAT(scheme, '://', host, COALESCE(CONCAT(':', port), ''), path) AS url FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend url = format_url(dynamic({'scheme': scheme, 'host': host, 'port': port, 'path': path}))
```
## Usage
### Syntax
```kusto theme={null}
format_url(url_parts)
```
### 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 a properly formatted URL string constructed from the provided components.
## Use case examples
Reconstruct full URLs from parsed components to analyze complete request patterns.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# gettype
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/gettype
This page explains how to use the gettype function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you use `typeof` to check types. APL's `gettype` provides similar functionality with consistent type names.
```sql Splunk example theme={null}
| eval field_type=typeof(field_name)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT TYPEOF(field_name) AS field_type FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend field_type = gettype(field_name)
```
## Usage
### Syntax
```kusto theme={null}
gettype(expression)
```
### Parameters
| Name | Type | Required | Description |
| ---------- | ---- | -------- | ------------------------------------------------ |
| expression | any | Yes | The expression whose type you want to determine. |
### Returns
Returns a string representing the runtime type: `string`, `int`, `long`, `real`, `bool`, `datetime`, `timespan`, `dynamic`, `array`, `dictionary`, or `null`.
## Use case examples
Identify the data types of fields to ensure proper query operations and data validation.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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 is not 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.
# indexof
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/indexof
This page explains how to use the indexof function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you might use `searchmatch` or string manipulation. APL's `indexof` provides a direct way to find substring positions.
```sql Splunk example theme={null}
| eval pos=if(match(field, "search"), strpos(field, "search"), -1)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT POSITION('search' IN field) - 1 AS pos FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend pos = indexof(field, 'search')
```
## Usage
### Syntax
```kusto theme={null}
indexof(source, lookup, start_index, length, occurrence)
```
### 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 the zero-based index position of the first occurrence of the lookup string, or -1 if not found.
## Use case examples
Find the position of API version indicators in URIs to categorize and analyze API usage patterns.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# indexof_regex
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/indexof-regex
This page explains how to use the indexof_regex function in APL.
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).
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval match_index=if(match(field, "pattern"), 0, -1)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT CASE WHEN REGEXP_LIKE(field, 'pattern') THEN 0 ELSE -1 END FROM table;
```
```kusto APL equivalent theme={null}
['dataset']
| extend match_index = indexof_regex(field, 'pattern')
```
## Usage
### Syntax
```kusto theme={null}
indexof_regex(string, match [, start [, occurrence [, length]]])
```
### 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
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 `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 theme={null}
['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 theme={null}
['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 theme={null}
['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.
# isascii
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/isascii
This page explains how to use the isascii function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval is_ascii=if(match(field, "^[\x00-\x7F]+$"), "true", "false")
```
```kusto APL equivalent theme={null}
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 theme={null}
SELECT input,
CASE WHEN input ~ '^[\x00-\x7F]+$' THEN true ELSE false END AS is_ascii
FROM my_table;
```
```kusto APL equivalent theme={null}
datatable(input:string)
[
'hello',
'こんにちは'
]
| extend is_ascii = isascii(input)
```
## Usage
### Syntax
```kusto theme={null}
isascii(value)
```
### Parameters
| Name | Type | Description |
| ----- | ------ | ------------------------------------------- |
| value | string | The input string to check for ASCII content |
### 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
Identify non-ASCII characters in request URIs to detect unusual or malformed traffic.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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.
# isempty
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/isempty
This page explains how to use the isempty function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you check for empty values using conditions like `field=""` or `isnull(field)`. APL's `isempty` combines both checks.
```sql Splunk example theme={null}
| where field="" OR isnull(field)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT * FROM logs WHERE field IS NULL OR field = '';
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| where isempty(field)
```
## Usage
### Syntax
```kusto theme={null}
isempty(value)
```
### Parameters
| Name | Type | Required | Description |
| ----- | ------ | -------- | ----------------------------------------- |
| value | scalar | Yes | The value to check for emptiness or null. |
### Returns
Returns `true` if the value is an empty string or null, otherwise returns `false`.
## Use case examples
Identify HTTP requests with missing or empty geographic information for data quality monitoring.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [isnotempty](/apl/scalar-functions/string-functions/isnotempty): Returns true if a value is not 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.
# isnotempty
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/isnotempty
This page explains how to use the isnotempty function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| where field!="" AND isnotnull(field)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT * FROM logs WHERE field IS NOT NULL AND field <> '';
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| where isnotempty(field)
```
## Usage
### Syntax
```kusto theme={null}
isnotempty(value)
```
### Parameters
| Name | Type | Required | Description |
| ----- | ------ | -------- | -------------------------------------------------- |
| value | scalar | Yes | The value to check for non-emptiness and non-null. |
### Returns
Returns `true` if the value is not an empty string and not null, otherwise returns `false`.
## Use case examples
Filter HTTP logs to only include requests with valid geographic information for accurate location-based analytics.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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 is not 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.
# isnotnull
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/isnotnull
This page explains how to use the isnotnull function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you check for non-null values using `isnotnull()` function. APL's `isnotnull` works the same way.
```sql Splunk example theme={null}
| where isnotnull(field)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT * FROM logs WHERE field IS NOT NULL;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| where isnotnull(field)
```
## Usage
### Syntax
```kusto theme={null}
isnotnull(value)
```
### Parameters
| Name | Type | Required | Description |
| ----- | ------ | -------- | -------------------------------- |
| value | scalar | Yes | The value to check for non-null. |
### Returns
Returns `true` if the value is not null, otherwise returns `false`. Note that empty strings return `true` because they are not null.
## Use case examples
Filter HTTP logs to only include requests where duration information is available for performance analysis.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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 is not 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.
# isnull
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/isnull
This page explains how to use the isnull function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you check for null values using `isnull()` function. APL's `isnull` works the same way.
```sql Splunk example theme={null}
| where isnull(field)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT * FROM logs WHERE field IS NULL;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| where isnull(field)
```
## Usage
### Syntax
```kusto theme={null}
isnull(value)
```
### Parameters
| Name | Type | Required | Description |
| ----- | ------ | -------- | ---------------------------- |
| value | scalar | Yes | The value to check for null. |
### Returns
Returns `true` if the value is null, otherwise returns `false`. Note that empty strings return `false` because they are not null.
## Use case examples
Identify HTTP requests with missing duration information to assess data quality and completeness.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [isnotnull](/apl/scalar-functions/string-functions/isnotnull): Returns true if a value is not 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.
# parse_bytes
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/parse-bytes
This page explains how to use the parse_bytes function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you typically need custom eval expressions to parse byte strings. APL's `parse_bytes` provides this functionality natively.
```sql Splunk example theme={null}
| 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 theme={null}
['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 theme={null}
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 theme={null}
['sample-http-logs']
| extend bytes = parse_bytes(size_str)
```
## Usage
### Syntax
```kusto theme={null}
parse_bytes(bytes_string, base)
```
### 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 the numeric value in bytes, or 0 if the string cannot be parsed.
## Use case examples
Parse human-readable size strings to analyze and aggregate data transfer volumes.
**Query**
```kusto theme={null}
['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 theme={null}
['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
* [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.
# parse_csv
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/parse-csv
This page explains how to use the parse_csv function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| makemv delim="," field_name
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT STRING_TO_ARRAY(field_name, ',') AS values FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend values = parse_csv(field_name)
```
## Usage
### Syntax
```kusto theme={null}
parse_csv(csv_text)
```
### Parameters
| Name | Type | Required | Description |
| --------- | ------ | -------- | ---------------------------------------------------- |
| csv\_text | string | Yes | A string containing comma-separated values to parse. |
### Returns
Returns a string array containing the individual values from the CSV string. Properly handles quoted values and escaped characters.
## Use case examples
Parse comma-separated status codes or error types from log messages.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [split](/apl/scalar-functions/string-functions/split): Splits strings by any delimiter. Use this when working with non-CSV delimiters or when quote handling is not 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.
# parse_json
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/parse-json
This page explains how to use the parse_json function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you use `spath` to parse JSON. APL's `parse_json` provides similar functionality with dynamic object support.
```sql Splunk example theme={null}
| spath input=json_field
| rename "field.name" as extracted_value
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT JSON_EXTRACT(json_field, '$.field.name') AS extracted_value FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend parsed = parse_json(json_field)
| extend extracted_value = parsed.field.name
```
## Usage
### Syntax
```kusto theme={null}
parse_json(json_string)
```
### Parameters
| Name | Type | Required | Description |
| ------------ | ------ | -------- | ---------------------------------------- |
| json\_string | string | Yes | A string containing valid JSON to parse. |
### Returns
Returns a dynamic object representing the parsed JSON. If the JSON is invalid, returns the original string.
## Use case examples
Parse JSON-formatted log messages to extract specific fields for analysis.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
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
* [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.
# parse_path
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/parse-path
This page explains how to use the parse_path function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | rex field=uri "(?\/api\/v1\/[^\?]+)"
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT SUBSTRING(uri, 1, CHARINDEX('/', uri)) AS directory FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend directory = parse_path(uri).directory
```
## Usage
### Syntax
```kusto theme={null}
parse_path(source)
```
### Parameters
| Name | Type | Description |
| ------ | ------ | ---------------------------------------------------- |
| source | string | A string representing a path, file URI, or full URL. |
### Returns
Returns a dynamic object with the following fields:
* Scheme
* RootPath
* DirectoryPath
* DirectoryName
* Filename
* Extension
* AlternateDataStreamName
## Use case example
Extract endpoint directories and file extensions from HTTP request URIs.
**Query**
```kusto theme={null}
['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.
# parse_url
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/parse-url
This page explains how to use the parse_url function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you use `rex` or URL-specific extractions. APL's `parse_url` provides structured URL parsing in one function.
```sql Splunk example theme={null}
| rex field=url "(?https?)://(?[^/]+)(?.*)"
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(url, '://', -1), '/', 1) AS host FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend parsed = parse_url(url)
| extend host = parsed.host
```
## Usage
### Syntax
```kusto theme={null}
parse_url(url)
```
### Parameters
| Name | Type | Required | Description |
| ---- | ------ | -------- | -------------------------------- |
| url | string | Yes | An absolute URL string to parse. |
### Returns
Returns a dynamic object containing URL components: `scheme`, `host`, `port`, `path`, `username`, `password`, `query`, `fragment`.
## Use case examples
Parse URLs from HTTP logs to analyze traffic patterns by host and path.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# parse_urlquery
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/parse-urlquery
This page explains how to use the parse_urlquery function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you use `rex` or URL parsing to extract query parameters. APL's `parse_urlquery` provides structured query string parsing.
```sql Splunk example theme={null}
| rex field=url "\\?(?.*)"
| eval params=split(query_string, "&")
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend params = parse_urlquery(uri)
```
In ANSI SQL, query string parsing requires complex string manipulation. APL's `parse_urlquery` provides native parsing.
```sql SQL example theme={null}
SELECT SUBSTRING(url, POSITION('?' IN url) + 1) AS query_string FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend params = parse_urlquery(uri)
```
## Usage
### Syntax
```kusto theme={null}
parse_urlquery(query_string)
```
### Parameters
| Name | Type | Required | Description |
| ------------- | ------ | -------- | -------------------------------------------------------------- |
| query\_string | string | Yes | A URL query string (with or without the leading '?') to parse. |
### Returns
Returns a dynamic object containing the query parameters as key-value pairs.
## Use case examples
Extract and analyze query parameters from API requests to understand search patterns and filter usage.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['sample-http-logs']
| extend query_params = parse_urlquery('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
* [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 is not 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.
# replace_regex
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/replace-regex
This page explains how to use the replace_regex function in APL.
The `replace_regex` function replaces all matches of a regular expression pattern with another string. This function is an alias for `replace` and provides the same functionality for regex-based text replacement.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you use `rex` with mode=sed for regex replacements. APL's `replace_regex` provides the same functionality with simpler syntax.
```sql Splunk example theme={null}
| rex field=message mode=sed "s/error_([0-9]+)/ERROR-\\1/g"
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend formatted = replace_regex('error_([0-9]+)', 'ERROR-$1', uri)
```
In ANSI SQL, you use `REGEXP_REPLACE` for regex replacements. APL's `replace_regex` provides similar functionality with consistent syntax.
```sql SQL example theme={null}
SELECT REGEXP_REPLACE(field, 'pattern', 'replacement', 'g') AS result FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend result = replace_regex('pattern', 'replacement', field)
```
## Usage
### Syntax
```kusto theme={null}
replace_regex(regex, rewrite, text)
```
### Parameters
| Name | Type | Required | Description |
| ------- | ------ | -------- | ----------------------------------------------------------------------------------------- |
| regex | string | Yes | The regular expression pattern to search for. Can include capture groups. |
| rewrite | string | Yes | The replacement string. Use $0 for the entire match, $1 for the first capture group, etc. |
| text | string | Yes | The source string to perform replacements on. |
### Returns
Returns the text with all regex matches replaced by the rewrite pattern. Non-overlapping matches.
## Use case examples
Standardize HTTP status codes by adding descriptive prefixes for better readability.
**Query**
```kusto theme={null}
['sample-http-logs']
| extend formatted_status = replace_regex('^(2[0-9]{2})$', 'SUCCESS-$1', status)
| extend formatted_status = replace_regex('^(4[0-9]{2})$', 'CLIENT_ERROR-$1', formatted_status)
| extend formatted_status = replace_regex('^(5[0-9]{2})$', 'SERVER_ERROR-$1', formatted_status)
| summarize request_count = count() by formatted_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_status%20%3D%20replace_regex\(%27%5E\(2%5B0-9%5D%7B2%7D\)%24%27%2C%20%27SUCCESS-%241%27%2C%20status\)%20%7C%20extend%20formatted_status%20%3D%20replace_regex\(%27%5E\(4%5B0-9%5D%7B2%7D\)%24%27%2C%20%27CLIENT_ERROR-%241%27%2C%20formatted_status\)%20%7C%20extend%20formatted_status%20%3D%20replace_regex\(%27%5E\(5%5B0-9%5D%7B2%7D\)%24%27%2C%20%27SERVER_ERROR-%241%27%2C%20formatted_status\)%20%7C%20summarize%20request_count%20%3D%20count\(\)%20by%20formatted_status%20%7C%20sort%20by%20request_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| formatted\_status | request\_count |
| ----------------- | -------------- |
| SUCCESS-200 | 8765 |
| CLIENT\_ERROR-404 | 2341 |
| SERVER\_ERROR-500 | 1234 |
| CLIENT\_ERROR-403 | 987 |
This query adds descriptive prefixes to status codes using regex capture groups, making log analysis more intuitive.
Extract and reformat duration values in span attributes by normalizing units.
**Query**
```kusto theme={null}
['otel-demo-traces']
| extend duration_str = strcat(tostring(duration / 1ms), 'ms')
| extend normalized = replace_regex('([0-9]+)ms', '$1 milliseconds', duration_str)
| project _time, ['service.name'], duration, duration_str, normalized
| 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%20duration_str%20%3D%20strcat\(tostring\(duration%20%2F%201ms\)%2C%20%27ms%27\)%20%7C%20extend%20normalized%20%3D%20replace_regex\(%27\(%5B0-9%5D%2B\)ms%27%2C%20%27%241%20milliseconds%27%2C%20duration_str\)%20%7C%20project%20_time%2C%20%5B%27service.name%27%5D%2C%20duration%2C%20duration_str%2C%20normalized%20%7C%20limit%2010%22%7D)
**Output**
| \_time | service.name | duration | duration\_str | normalized |
| -------------------- | ------------ | -------- | ------------- | ---------------- |
| 2024-11-06T10:00:00Z | frontend | 125ms | 125ms | 125 milliseconds |
| 2024-11-06T10:01:00Z | checkout | 234ms | 234ms | 234 milliseconds |
This query normalizes duration format using regex capture groups to ensure consistent unit representation across different services.
Mask sensitive data patterns like credit card numbers or SSNs using regex capture groups.
**Query**
```kusto theme={null}
['sample-http-logs']
| extend masked_uri = replace_regex('([0-9]{4})[0-9]{8}([0-9]{4})', '$1********$2', uri)
| extend masked_uri = replace_regex('([0-9]{3})-[0-9]{2}-([0-9]{4})', '$1-XX-$2', masked_uri)
| project _time, uri, masked_uri, 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%20masked_uri%20%3D%20replace_regex\('\(%5B0-9%5D%7B4%7D\)%5B0-9%5D%7B8%7D\(%5B0-9%5D%7B4%7D\)'%2C%20'%241********%242'%2C%20uri\)%20%7C%20extend%20masked_uri%20%3D%20replace_regex\('\(%5B0-9%5D%7B3%7D\)-%5B0-9%5D%7B2%7D-\(%5B0-9%5D%7B4%7D\)'%2C%20'%241-XX-%242'%2C%20masked_uri\)%20%7C%20project%20_time%2C%20uri%2C%20masked_uri%2C%20id%2C%20status%20%7C%20limit%2010%22%7D)
**Output**
| \_time | uri | masked\_uri | id | status |
| -------------------- | ------------------------ | -------------------------------- | ------- | ------ |
| 2024-11-06T10:00:00Z | /api?cc=1234567890123456 | /api?cc=1234\*\*\*\*\*\*\*\*3456 | user123 | 403 |
| 2024-11-06T10:01:00Z | /api?ssn=123-45-6789 | /api?ssn=123-XX-6789 | user456 | 401 |
This query masks sensitive personally identifiable information like credit card numbers and SSNs using regex capture groups to preserve format while hiding sensitive digits.
## List of related functions
* [replace](/apl/scalar-functions/string-functions/replace): Alias for replace\_regex. Use either name based on preference.
* [replace\_string](/apl/scalar-functions/string-functions/replace-string): Replaces plain string matches without regex. Use this for faster replacement when regex patterns are not needed.
* [extract](/apl/scalar-functions/string-functions/extract): Extracts the first regex match. Use this when you need to capture text rather than modify it.
* [extract\_all](/apl/scalar-functions/string-functions/extract-all): Extracts all regex matches. Use this when you need multiple captured values without replacement.
# replace_string
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/replace-string
This page explains how to use the replace_string function in APL.
The `replace_string` function replaces all occurrences of a plain string with another string. Use this function when you need exact string matching without regular expression patterns, which makes it faster and simpler than regex-based replacement.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you use `replace` for simple string replacements. APL's `replace_string` provides the same functionality.
```sql Splunk example theme={null}
| eval cleaned=replace(field, "old_text", "new_text")
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend cleaned = replace_string('old_text', 'new_text', field)
```
In ANSI SQL, you use `REPLACE` for string replacements. APL's `replace_string` provides similar functionality.
```sql SQL example theme={null}
SELECT REPLACE(field, 'old_text', 'new_text') AS cleaned FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend cleaned = replace_string('old_text', 'new_text', field)
```
## Usage
### Syntax
```kusto theme={null}
replace_string(lookup, rewrite, text)
```
### Parameters
| Name | Type | Required | Description |
| ------- | ------ | -------- | --------------------------------------------- |
| lookup | string | Yes | The plain string to search for and replace. |
| rewrite | string | Yes | The replacement string. |
| text | string | Yes | The source string to perform replacements on. |
### Returns
Returns the text with all occurrences of the lookup string replaced by the rewrite string. Matches do not overlap.
## Use case examples
Normalize HTTP methods by replacing abbreviations with full names for consistency.
**Query**
```kusto theme={null}
['sample-http-logs']
| extend normalized_method = replace_string('GET', 'Retrieve', method)
| extend normalized_method = replace_string('POST', 'Create', normalized_method)
| extend normalized_method = replace_string('PUT', 'Update', normalized_method)
| extend normalized_method = replace_string('DELETE', 'Remove', normalized_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%20replace_string\(%27GET%27%2C%20%27Retrieve%27%2C%20method\)%20%7C%20extend%20normalized_method%20%3D%20replace_string\(%27POST%27%2C%20%27Create%27%2C%20normalized_method\)%20%7C%20extend%20normalized_method%20%3D%20replace_string\(%27PUT%27%2C%20%27Update%27%2C%20normalized_method\)%20%7C%20extend%20normalized_method%20%3D%20replace_string\(%27DELETE%27%2C%20%27Remove%27%2C%20normalized_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 |
| ------------------ | ------ | -------------- |
| Retrieve | 200 | 5432 |
| Create | 201 | 2341 |
| Retrieve | 404 | 1234 |
| Update | 200 | 987 |
This query replaces HTTP method abbreviations with descriptive action names, making logs more readable for non-technical audiences.
Standardize service names by replacing environment-specific prefixes.
**Query**
```kusto theme={null}
['otel-demo-traces']
| extend clean_service = replace_string('prod-', '', ['service.name'])
| extend clean_service = replace_string('staging-', '', clean_service)
| extend clean_service = replace_string('dev-', '', clean_service)
| summarize span_count = count() by clean_service, 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%20extend%20clean_service%20%3D%20replace_string\(%27prod-%27%2C%20%27%27%2C%20%5B%27service.name%27%5D\)%20%7C%20extend%20clean_service%20%3D%20replace_string\(%27staging-%27%2C%20%27%27%2C%20clean_service\)%20%7C%20extend%20clean_service%20%3D%20replace_string\(%27dev-%27%2C%20%27%27%2C%20clean_service\)%20%7C%20summarize%20span_count%20%3D%20count\(\)%20by%20clean_service%2C%20kind%20%7C%20sort%20by%20span_count%20desc%20%7C%20limit%2010%22%7D)
**Output**
| clean\_service | kind | span\_count |
| -------------- | -------- | ----------- |
| frontend | server | 4532 |
| checkout | client | 3421 |
| cart | internal | 2987 |
This query removes environment prefixes from service names to enable cross-environment analysis and aggregation.
Anonymize IP addresses by replacing specific segments for privacy compliance.
**Query**
```kusto theme={null}
['sample-http-logs']
| extend simulated_ip = '192.168.1.100'
| extend anonymized_ip = replace_string('.100', '.XXX', simulated_ip)
| extend anonymized_ip = replace_string('.1.', '.X.', anonymized_ip)
| project _time, uri, simulated_ip, anonymized_ip, 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%20simulated_ip%20%3D%20'192.168.1.100'%20%7C%20extend%20anonymized_ip%20%3D%20replace_string\('.100'%2C%20'.XXX'%2C%20simulated_ip\)%20%7C%20extend%20anonymized_ip%20%3D%20replace_string\('.1.'%2C%20'.X.'%2C%20anonymized_ip\)%20%7C%20project%20_time%2C%20uri%2C%20simulated_ip%2C%20anonymized_ip%2C%20status%2C%20%5B'geo.country'%5D%20%7C%20limit%2010%22%7D)
**Output**
| \_time | uri | simulated\_ip | anonymized\_ip | status | geo.country |
| -------------------- | ----------- | ------------- | -------------- | ------ | ------------- |
| 2024-11-06T10:00:00Z | /admin | 192.168.1.100 | 192.168.X.XXX | 403 | United States |
| 2024-11-06T10:01:00Z | /api/secret | 192.168.1.100 | 192.168.X.XXX | 401 | Unknown |
This query anonymizes IP addresses by replacing specific octets with placeholders, enabling security analysis while maintaining privacy compliance.
## List of related functions
* [replace](/apl/scalar-functions/string-functions/replace): Replaces strings using regular expressions. Use this when you need pattern matching capabilities.
* [replace\_regex](/apl/scalar-functions/string-functions/replace-regex): Alias for replace with regex support. Use this for pattern-based replacements.
* [strcat](/apl/scalar-functions/string-functions/strcat): Concatenates strings. Use this when building new strings rather than replacing parts of existing ones.
* [substring](/apl/scalar-functions/string-functions/substring): Extracts parts of strings. Use this when you need to extract rather than replace text.
# reverse
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/reverse
This page explains how to use the reverse function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, reversing strings typically requires custom functions or scripts. APL's `reverse` provides this functionality natively.
```sql Splunk example theme={null}
| eval reversed=mvreverse(split(field, ""))| eval reversed=mvjoin(reversed, "")
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT REVERSE(field) AS reversed FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend reversed = reverse(field)
```
## Usage
### Syntax
```kusto theme={null}
reverse(value)
```
### Parameters
| Name | Type | Required | Description |
| ----- | ------ | -------- | ---------------------------- |
| value | string | Yes | The input string to reverse. |
### Returns
Returns the input string with its characters in reverse order.
## Use case examples
Detect palindromic patterns in URIs or identifiers for data validation.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# split
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/split
This page explains how to use the split function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you use the `split` function similarly. APL's `split` provides the same functionality.
```sql Splunk example theme={null}
| eval parts=split(field, ",")
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT STRING_SPLIT(field, ',') AS parts FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend parts = split(field, ',')
```
## Usage
### Syntax
```kusto theme={null}
split(source, delimiter)
```
### Parameters
| Name | Type | Required | Description |
| --------- | ------ | -------- | --------------------------------- |
| source | string | Yes | The source string to split. |
| delimiter | string | Yes | The delimiter string to split on. |
### Returns
Returns a string array containing the substrings separated by the delimiter.
## Use case examples
Split URI paths into segments for hierarchical analysis of API endpoint structure.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# strcat
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/strcat
This page explains how to use the strcat function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you concatenate strings using the `.` operator or the `concat` function. APL's `strcat` provides similar functionality.
```sql Splunk example theme={null}
| eval combined=field1."-".field2."-".field3
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT CONCAT(field1, '-', field2, '-', field3) AS combined FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend combined = strcat(field1, '-', field2, '-', field3)
```
## Usage
### Syntax
```kusto theme={null}
strcat(arg1, arg2, ..., argN)
```
### 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 all arguments concatenated into a single string.
## Use case examples
Build composite keys from multiple fields for unique request identification.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# strcat_delim
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/strcat-delim
This page explains how to use the strcat_delim function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you typically concatenate with repeated delimiters. APL's `strcat_delim` provides a more concise approach.
```sql Splunk example theme={null}
| eval combined=field1.",".field2.",".field3
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT CONCAT_WS(',', field1, field2, field3) AS combined FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend combined = strcat_delim(',', field1, field2, field3)
```
## Usage
### Syntax
```kusto theme={null}
strcat_delim(delimiter, arg1, arg2, ..., argN)
```
### 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 all arguments concatenated with the delimiter between each argument.
## Use case examples
Create CSV-formatted log records for export or integration with external systems.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# strcmp
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/strcmp
This page explains how to use the strcmp function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you typically use comparison operators. APL's `strcmp` provides explicit lexicographic comparison with numeric return values.
```sql Splunk example theme={null}
| eval result=case(str1str2, 1, true(), 0)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT CASE
WHEN str1 < str2 THEN -1
WHEN str1 > str2 THEN 1
ELSE 0
END AS result FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend result = strcmp(str1, str2)
```
## Usage
### Syntax
```kusto theme={null}
strcmp(string1, string2)
```
### Parameters
| Name | Type | Required | Description |
| ------- | ------ | -------- | ----------------------------- |
| string1 | string | Yes | The first string to compare. |
| string2 | string | Yes | The second string to compare. |
### Returns
Returns an integer: -1 if string1 is less than string2, 0 if they are equal, 1 if string1 is greater than string2.
## Use case examples
Compare HTTP methods to establish custom ordering for request type analysis.
**Query**
```kusto theme={null}
['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 theme={null}
['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
* [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.
# string_size
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/string-size
This page explains how to use the string_size function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
... | eval uri_length=len(uri)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT LENGTH(uri) AS uri_length
FROM sample_http_logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend uri_length = string_size(uri)
```
## Usage
### Syntax
```kusto theme={null}
string_size(source)
```
### Parameters
| Parameter | Type | Description |
| --------- | -------- | ---------------------------- |
| `source` | `string` | The input string expression. |
### Returns
An integer representing the number of bytes in the string. If the string is empty, the function returns `0`.
## Use case examples
You can use `string_size` to detect unusually long URIs that might indicate an attempted exploit or malformed request.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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.
# strip_ansi_escapes
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/strip-ansi-escapes
This page explains how to use the strip_ansi_escapes function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
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 theme={null}
| rex mode=sed field=message 's/\x1b\[[0-9;]*m//g'
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT REGEXP_REPLACE(message, '\x1b\[[0-9;]*m', '', 'g')
FROM logs
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend clean_message = strip_ansi_escapes(message)
```
## Usage
### Syntax
```kusto theme={null}
strip_ansi_escapes(text)
```
### Parameters
| Name | Type | Description |
| ------ | -------- | -------------------------------------------------------------------- |
| `text` | `string` | The string containing ANSI escape sequences that you want to remove. |
### Returns
A string with all ANSI escape sequences removed, leaving only the plain text content.
## 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.
# strlen
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/strlen
This page explains how to use the strlen function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you use the `len` function. APL's `strlen` provides the same functionality.
```sql Splunk example theme={null}
| eval length=len(field)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT LENGTH(field) AS length FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend length = strlen(field)
```
## Usage
### Syntax
```kusto theme={null}
strlen(source)
```
### Parameters
| Name | Type | Required | Description |
| ------ | ------ | -------- | ---------------------- |
| source | string | Yes | The string to measure. |
### Returns
Returns the length of the string in characters (not bytes).
## Use case examples
Analyze URI lengths to identify potential long-URL attacks or data exfiltration attempts.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# strrep
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/strrep
This page explains how to use the strrep function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, repeating strings typically requires custom functions or loops. APL's `strrep` provides this functionality natively.
```sql Splunk example theme={null}
| eval repeated=mvjoin(mvappend("text","text","text"), "")
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT REPEAT('text', 3) AS repeated FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend repeated = strrep('text', 3)
```
## Usage
### Syntax
```kusto theme={null}
strrep(value, multiplier, delimiter)
```
### 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 the string repeated the specified number of times with optional delimiters between repetitions.
## Use case examples
Create visual separators or formatting patterns for log output visualization.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['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
* [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.
# substring
Source: https://axiom.co/docs/apl/scalar-functions/string-functions/substring
This page explains how to use the substring function in APL.
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.
## For users of other query languages
If you come from other query languages, this section explains how to adjust your existing queries to achieve the same results in APL.
In Splunk SPL, you use the `substr` function. APL's `substring` provides similar functionality with zero-based indexing.
```sql Splunk example theme={null}
| eval extracted=substr(field, 5, 10)
```
```kusto APL equivalent theme={null}
['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 theme={null}
SELECT SUBSTRING(field, 5, 10) AS extracted FROM logs;
```
```kusto APL equivalent theme={null}
['sample-http-logs']
| extend extracted = substring(field, 4, 10)
```
## Usage
### Syntax
```kusto theme={null}
substring(source, startingIndex, length)
```
### 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 the extracted substring. Returns empty string if startingIndex is beyond the string length.
## Use case examples
Extract specific segments from fixed-format URIs or identifiers.
**Query**
```kusto theme={null}
['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 theme={null}
['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 theme={null}
['sample-http-logs']
| extend uri_start = substring(uri, 0, 10)
| extend uri_has_exploit = indexof(uri_start, '..') >= 0 or indexof(uri_start, '