Log-Based Metrics in Azure Monitor
Platform metrics cover infrastructure — CPU, memory, disk, requests. But your business has monitoring requirements that no platform metric was ever designed to track: how many payment processing errors occurred in the last 5 minutes? How deep is the unprocessed job queue? How many users hit the rate limiter? Log-based metrics bridge this gap by turning KQL query results into time-series metrics that can be charted, alerted on, and embedded in dashboards alongside native Azure Monitor metrics.
When log-based metrics are the right tool
Use log-based metrics when:
- The signal you want to monitor exists only in log data (error codes in application logs, specific exception types, business events)
- You want to chart a derived value over time without running the same KQL query manually
- You need a metric that can be compared across multiple resources in a single chart
- You want to alert on a log pattern using the faster and simpler metric alert type rather than the more complex log search alert
Do not use log-based metrics when:
- A native platform metric already exists for the condition — native metrics are cheaper, lower latency, and more reliable
- You need sub-5-minute resolution — log ingestion delay makes this impractical
- The data volume is extremely high — every evaluation of the underlying KQL query consumes workspace query capacity
Creating a log-based metric alert
In Azure Monitor, what is commonly called a “log-based metric alert” is technically a log search alert that measures a numeric value from a KQL query result. You can also create standalone custom metrics from log data using the Application Insights SDK for continuous metric storage.
Here is a complete example: creating a metric alert that fires when more than 10 authentication failures occur in any 10-minute window.
# Create a log search alert that measures authentication failures
az monitor scheduled-query create \
--name "AuthenticationFailures" \
--resource-group myRG \
--scopes /subscriptions/<sub-id>/resourceGroups/myRG/providers/Microsoft.OperationalInsights/workspaces/myWorkspace \
--condition-query "AppTraces | where TimeGenerated > ago(10m) | where Message contains 'authentication failed' | summarize FailureCount = count()" \
--condition-column "FailureCount" \
--condition-operator "GreaterThan" \
--condition-threshold 10 \
--condition-time-aggregation "Total" \
--evaluation-frequency PT10M \
--window-duration PT10M \
--severity 2 \
--action-groups /subscriptions/<sub-id>/resourceGroups/myRG/providers/microsoft.insights/actionGroups/OnCallTeam \
--description "More than 10 authentication failures in 10 minutes — possible brute force"Writing KQL queries for metric generation
The KQL queries that back log-based metrics need to produce a consistent numeric output. The query should always return a single row with a single numeric column (the metric value). Here are several patterns:
// Pattern 1: Count of specific error type in evaluation window
AppExceptions
| where TimeGenerated > ago(10m)
| where ExceptionType == "System.TimeoutException"
| summarize TimeoutCount = count()
// Pattern 2: Business metric — failed payment processing attempts
AppTraces
| where TimeGenerated > ago(5m)
| where customDimensions["eventType"] == "PaymentProcessed"
| summarize
TotalPayments = count(),
FailedPayments = countif(tostring(customDimensions["status"]) == "failed")
| project FailedPayments
// Pattern 3: Queue depth metric from application logs
AppMetrics
| where TimeGenerated > ago(5m)
| where Name == "queue.depth"
| where customDimensions["queueName"] == "order-processing"
| summarize MaxDepth = max(Sum)
// Pattern 4: Percentage metric — cache miss rate
AppMetrics
| where TimeGenerated > ago(5m)
| where Name in ("cache.hit", "cache.miss")
| summarize
Hits = sumif(Sum, Name == "cache.hit"),
Misses = sumif(Sum, Name == "cache.miss")
| extend MissRate = round(100.0 * Misses / (Hits + Misses), 2)
| project MissRate
// Pattern 5: SLA compliance rate
AppRequests
| where TimeGenerated > ago(15m)
| where Name startswith "GET /api/"
| summarize
Total = count(),
WithinSLA = countif(DurationMs < 200 and Success == true)
| extend SLARate = round(100.0 * WithinSLA / Total, 2)
| project SLARateAlways add a | summarize count() | where count_ > 0 guard at the end of your metric query if you want the alert to not fire when there is no matching data (zero results vs. result-count-is-zero). An empty result set is different from a result set containing zero, and some alert configurations handle these differently.
Multi-dimensional log-based metrics
Log search alerts support splitting results by dimension — running the same alert condition separately for each value of a dimension field. This is powerful for monitoring per-service or per-endpoint conditions without creating dozens of separate alert rules.
// Multi-dimensional query: error count split by service name
// Each service triggers the alert independently
AppRequests
| where TimeGenerated > ago(10m)
| where Success == false
| summarize ErrorCount = count() by ServiceName = cloud_RoleNameWhen this query is used in a log search alert with “Split by dimensions” → ServiceName, each service gets its own alert evaluation. If OrderService has 25 errors and PaymentService has 3 errors, only the OrderService instance of the alert fires.
# Create a multi-dimensional log search alert
az monitor scheduled-query create \
--name "PerServiceErrors" \
--resource-group myRG \
--scopes /subscriptions/<sub-id>/resourceGroups/myRG/providers/Microsoft.OperationalInsights/workspaces/myWorkspace \
--condition-query "AppRequests | where TimeGenerated > ago(10m) | where Success == false | summarize ErrorCount = count() by ServiceName = cloud_RoleName" \
--condition-column "ErrorCount" \
--condition-operator "GreaterThan" \
--condition-threshold 20 \
--condition-time-aggregation "Total" \
--condition-dimension-name "ServiceName" \
--condition-dimension-operator "Include" \
--condition-dimension-values "*" \
--evaluation-frequency PT10M \
--window-duration PT10M \
--severity 2 \
--action-groups /subscriptions/<sub-id>/resourceGroups/myRG/providers/microsoft.insights/actionGroups/OnCallTeamPersistent custom metrics via Application Insights
For metrics that need to be stored continuously as time-series (not just evaluated at alert time), use Application Insights’ custom metrics feature. Custom metrics written via the SDK are stored in the Azure Monitor metrics store alongside platform metrics, available for 93 days with full charting and alerting support.
# Verify custom metrics are being received
az monitor metrics list-definitions \
--resource /subscriptions/<sub-id>/resourceGroups/myRG/providers/microsoft.insights/components/myapp-insights \
--output table | grep -i "custom"
# Query custom metric values via CLI
az monitor metrics list \
--resource /subscriptions/<sub-id>/resourceGroups/myRG/providers/microsoft.insights/components/myapp-insights \
--metric "QueueDepth" \
--interval PT5M \
--aggregation Maximum \
--start-time $(date -u -d '2 hours ago' +%Y-%m-%dT%H:%MZ) \
--end-time $(date -u +%Y-%m-%dT%H:%MZ) \
--output tablePractical use: SLO tracking with log-based metrics
Log-based metrics are excellent for tracking Service Level Objectives (SLOs). An SLO like “99.5% of API requests complete in under 500ms” is a log-derived measurement — you need to query request logs to calculate it.
// Weekly SLO compliance report
// Run this as a scheduled query to track SLO trends
AppRequests
| where TimeGenerated > ago(7d)
| where Name startswith "GET /api/"
| summarize by bin(TimeGenerated, 1h)
| join (
AppRequests
| where TimeGenerated > ago(7d)
| where Name startswith "GET /api/"
| where DurationMs < 500 and Success == true
| summarize GoodRequests = count() by bin(TimeGenerated, 1h)
) on TimeGenerated
| extend SLORate = round(100.0 * GoodRequests / count_, 3)
| project TimeGenerated, TotalRequests = count_, GoodRequests, SLORate
| render timechartCommon mistakes
- Using log-based metrics for high-frequency real-time signals. Log ingestion has a 2–5 minute delay. A log-based metric tracking a condition that needs sub-minute alerting will consistently alert 3–7 minutes after the actual event. Use platform metrics for time-sensitive alerting; use log-based metrics for conditions where a few minutes of delay is acceptable.
- Not handling the no-data case. If your query returns no rows when there is no data (rather than returning a row with a zero count), your alert evaluation sees an empty result. Configure your alert to treat missing data as “zero” explicitly in the alert rule settings, or modify your KQL to always return a row.
- Building overly complex KQL queries for metric generation. Log-based metric queries run on a schedule — every 5–15 minutes, indefinitely. A query that takes 10 seconds to run will accumulate meaningful query costs and workspace load over time. Optimize metric queries for speed: narrow the time window, use indexed columns in
whereclauses, and avoid expensive joins. - Duplicating log-based metrics that already exist as platform metrics. HTTP request counts, error rates, and response times for App Service, API Management, and many other services already exist as platform metrics. Building log-based versions of these adds cost and complexity for no benefit. Check the available platform metrics before creating a log-based equivalent.
Summary
- Log-based metrics turn KQL query results into time-series values that can be charted and alerted on when no platform metric covers the condition.
- They are best for business events, custom error codes, and derived calculations that only exist in log data, not for real-time signals where platform metrics already exist.
- Multi-dimensional log search alerts evaluate the same condition per dimension value, reducing the number of separate alert rules needed.
- Application Insights custom metrics provide persistent time-series storage (93 days) for application-emitted metrics alongside platform metrics.
- Log-based metric queries run on a schedule — optimize them for speed to control workspace query costs.
Frequently asked questions
What is a log-based metric?
A log-based metric (also called a custom log metric or metric alert from log analytics) converts the result of a KQL query into a time-series metric value. This allows you to chart and alert on patterns in log data as if they were native Azure Monitor metrics.
How is a log-based metric different from a log search alert?
A log search alert runs a KQL query on a schedule and fires a one-time alert notification. A log-based metric stores the query result as a continuous time-series that can be charted in Metrics Explorer, added to dashboards, and used in metric alert rules.
How often are log-based metrics updated?
Log-based metrics are updated on the schedule you define (minimum 5 minutes). They have higher latency than native platform metrics because they depend on log ingestion completing before the query can run.