Back to CourseIntermediate6 min read
Azure /

Azure Monitor: Metrics and Logs

Understand the difference between metrics and logs in Azure Monitor, and wire diagnostic settings so the data you need exists.

Bishal Biswas

Bishal Biswas

Self Learner

Published on: October 25, 2026
Last updated: September 7, 2026
Intermediate Topic

Two different data types

Metrics   numeric, time series, pre aggregated, cheap, retained 93 days,
          near real time. What you alert on.
Logs      structured records, queryable with KQL, retained as configured,
          a few minutes behind. What you investigate with.

Both come from the same platform and behave completely differently. Confusing them is why people find Azure Monitor confusing.

"CPU is above 80 percent"       a metric
"Which requests failed and why" logs

Platform metrics are free and automatic

az monitor metrics list \
  --resource /subscriptions/.../sites/app-shop \
  --metric CpuPercentage MemoryPercentage Http5xx Requests \
  --start-time 2026-09-08T00:00:00Z --interval PT5M \
  --aggregation Average Total --output table

az monitor metrics list-definitions \
  --resource /subscriptions/.../sites/app-shop \
  --query "[].{Name:name.value, Unit:unit}" -o table

Every resource emits platform metrics with no configuration and no cost. list-definitions is how you discover what a service actually exposes, which is faster than searching documentation.

Logs need diagnostic settings

This is the step people miss. Platform logs are not collected unless you say so, and a resource with no diagnostic setting produces nothing you can query later.

az monitor diagnostic-settings create \
  --name to-law \
  --resource /subscriptions/.../sites/app-shop \
  --workspace /subscriptions/.../workspaces/law-shop \
  --logs '[{"categoryGroup":"allLogs","enabled":true}]' \
  --metrics '[{"category":"AllMetrics","enabled":true}]'

az monitor diagnostic-settings categories list \
  --resource /subscriptions/.../sites/app-shop -o table

Discovering during an incident that the logs were never collected is a bad moment, and it is entirely avoidable.

Enforce it with policy so every resource gets one automatically:

az policy assignment create \
  --name diagnostics-everywhere \
  --scope /providers/Microsoft.Management/managementGroups/mg-contoso \
  --policy-set-definition "$DIAGNOSTICS_INITIATIVE_ID" \
  --params '{"logAnalytics":{"value":"/subscriptions/.../workspaces/law-central"}}' \
  --location uksouth --mi-system-assigned --role Contributor \
  --identity-scope /providers/Microsoft.Management/managementGroups/mg-contoso

The workspace

az monitor log-analytics workspace create \
  --name law-shop --resource-group rg-shop-prod-uks \
  --location uksouth \
  --retention-time 90 \
  --sku PerGB2018
One workspace per region and environment is a reasonable default.
Too many   cross resource queries become awkward
Too few    access control becomes coarse, and one team sees another's data

Data must generally be in one workspace to join cleanly, so a single workspace per environment is usually better than one per application.

Retention and cost

az monitor log-analytics workspace update \
  --name law-shop --resource-group rg-shop-prod-uks \
  --retention-time 30

az monitor log-analytics workspace table update \
  --workspace-name law-shop --resource-group rg-shop-prod-uks \
  --name AppTraces --retention-time 30 --total-retention-time 365
Interactive retention  queryable normally. Charged per GB per month.
Total retention        archived, cheaper, needs a search job to query.

Per table retention is the important lever. Audit logs might need seven years while verbose traces need thirty days, and setting one retention for everything means paying archive prices for noise or losing what you needed.

Table plans

az monitor log-analytics workspace table update \
  --workspace-name law-shop --resource-group rg-shop-prod-uks \
  --name ContainerLogV2 --plan Basic
Analytics  full KQL, alerting, joins. The normal plan.
Basic      much cheaper ingestion, limited KQL, no alerting, 30 day retention.
Auxiliary  cheapest, for high volume low value logs.

Basic logs suit high volume verbose data you search occasionally and never alert on. Moving container stdout or network flow logs to Basic can cut a bill substantially.

The restriction matters: you cannot alert on Basic tables, so anything driving an alert stays on Analytics.

Controlling ingestion

Ingestion volume is what drives cost, so filtering at source is the most effective lever.

az monitor data-collection rule create \
  --name dcr-vm-logs --resource-group rg-shop-prod-uks --location uksouth \
  --rule-file dcr.json
{
  "dataSources": {
    "performanceCounters": [{
      "name": "perf",
      "streams": ["Microsoft-Perf"],
      "samplingFrequencyInSeconds": 60,
      "counterSpecifiers": [
        "\\Processor(_Total)\\% Processor Time",
        "\\Memory\\Available Bytes",
        "\\LogicalDisk(_Total)\\% Free Space"
      ]
    }],
    "syslog": [{
      "name": "syslog",
      "streams": ["Microsoft-Syslog"],
      "facilityNames": ["auth", "authpriv", "daemon"],
      "logLevels": ["Warning", "Error", "Critical", "Alert", "Emergency"]
    }]
  }
}

Data collection rules replaced the older agent configuration and are far more precise: choose exactly which counters and which syslog levels are collected, per machine group.

Collecting Info level syslog from a hundred machines is expensive and rarely useful.

Transformations

source
| where Level != "Verbose"
| project-away UserAgent, RawHeaders
| extend Environment = "production"

An ingestion time transformation filters and reshapes data before it is stored and billed. Dropping verbose entries and removing large unused columns at ingestion is cheaper than storing them and filtering at query time.

It also lets you remove sensitive fields before they land, which is sometimes a compliance requirement rather than a cost one.

Commitment tiers

az monitor log-analytics workspace update \
  --name law-shop --resource-group rg-shop-prod-uks \
  --sku CapacityReservation --level 200

Committing to a daily volume gives a discount that grows with the commitment. Above roughly 100 GB per day the saving is meaningful.

Check actual ingestion first:

Usage
| where TimeGenerated > ago(30d)
| where IsBillable
| summarize GB = sum(Quantity) / 1000 by bin(TimeGenerated, 1d)
| render timechart
Usage
| where TimeGenerated > ago(7d)
| where IsBillable
| summarize GB = sum(Quantity) / 1000 by DataType
| order by GB desc

That second query names the tables costing the most, which is where any cost reduction starts.

Metric alerts

az monitor metrics alert create \
  --name high-error-rate --resource-group rg-shop-prod-uks \
  --scopes /subscriptions/.../sites/app-shop \
  --condition "total Http5xx > 10" \
  --window-size 5m --evaluation-frequency 1m \
  --severity 1 \
  --action /subscriptions/.../actionGroups/ag-oncall

Metric alerts are cheaper and faster than log alerts, so use a metric where one exists. Log alerts are for conditions no metric expresses.

Dynamic thresholds

az monitor metrics alert create \
  --name unusual-traffic --resource-group rg-shop-prod-uks \
  --scopes /subscriptions/.../sites/app-shop \
  --condition "avg Requests > dynamic medium 4 of 5 since 2026-09-01T00:00:00Z" \
  --window-size 5m --evaluation-frequency 5m \
  --action /subscriptions/.../actionGroups/ag-oncall

Dynamic thresholds learn the normal pattern including daily and weekly seasonality. For a metric with a strong business rhythm, that is far better than a fixed number which is either too noisy at night or too loose at peak.

Multi resource alerts

az monitor metrics alert create \
  --name vm-cpu-high --resource-group rg-shop-prod-uks \
  --scopes /subscriptions/.../resourceGroups/rg-shop-prod-uks \
  --target-resource-type Microsoft.Compute/virtualMachines \
  --target-resource-region uksouth \
  --condition "avg Percentage CPU > 85" \
  --window-size 15m --action /subscriptions/.../actionGroups/ag-oncall

One alert rule covering every virtual machine in a resource group, including ones created later. Much better than one rule per machine that somebody has to remember to create.

Quick recap

  • Metrics are numeric, cheap and near real time, and logs are queryable records for investigation.
  • Platform metrics are automatic, and platform logs need a diagnostic setting.
  • Enforce diagnostic settings with policy, or you discover the gap during an incident.
  • Set retention per table, since audit logs and verbose traces have different needs.
  • Basic table plans cut cost and cannot be alerted on.
  • Filter at ingestion with data collection rules and transformations, since volume drives cost.
  • Prefer metric alerts where a metric exists, and use dynamic thresholds for seasonal traffic.
deploy.sh

# TWO DIFFERENT DATA TYPES, and confusing them is why Azure Monitor feels
# confusing.
#   METRICS  numeric, pre aggregated, cheap, 93 days, near real time.
#            WHAT YOU ALERT ON.  ("CPU is above 80 percent")
#   LOGS     structured records, KQL, configurable retention, minutes behind.
#            WHAT YOU INVESTIGATE WITH.  ("which requests failed and why")
# PLATFORM METRICS ARE AUTOMATIC AND FREE. This is how to discover what a
# service exposes, faster than searching documentation:
az monitor metrics list-definitions --resource /subscriptions/.../sites/app-shop \
  --query "[].{Name:name.value, Unit:unit}" -o table
# LOGS NEED A DIAGNOSTIC SETTING, AND THIS IS THE STEP PEOPLE MISS. A
# resource without one produces NOTHING you can query later, and finding
# that out during an incident is entirely avoidable.
az monitor diagnostic-settings create --name to-law \
  --resource /subscriptions/.../sites/app-shop \
  --workspace /subscriptions/.../workspaces/law-shop \
  --logs '[{"categoryGroup":"allLogs","enabled":true}]' \
  --metrics '[{"category":"AllMetrics","enabled":true}]'
# ENFORCE IT WITH POLICY so every resource gets one automatically, including
# resources created later.
# RETENTION PER TABLE IS THE IMPORTANT LEVER: audit logs may need seven
# years while verbose traces need thirty days. One setting for everything
# means paying archive prices for noise, or losing what you needed.
az monitor log-analytics workspace table update --workspace-name law-shop \
  --resource-group rg-shop-prod-uks --name AppTraces \
  --retention-time 30 --total-retention-time 365
# TABLE PLANS. Basic is much cheaper for high volume data you search
# occasionally. THE RESTRICTION THAT MATTERS: YOU CANNOT ALERT ON BASIC.
az monitor log-analytics workspace table update --workspace-name law-shop \
  --resource-group rg-shop-prod-uks --name ContainerLogV2 --plan Basic
# INGESTION VOLUME DRIVES COST, so filtering AT SOURCE is the best lever.
# Data collection rules pick exactly which counters and which syslog levels
# are collected. Info level syslog from a hundred machines is expensive and
# rarely useful.
az monitor data-collection rule create --name dcr-vm-logs \
  --resource-group rg-shop-prod-uks --location uksouth --rule-file dcr.json
# Ingestion TRANSFORMATIONS filter and reshape BEFORE storage and billing,
# and can strip sensitive fields before they land:
#   source | where Level != "Verbose" | project-away UserAgent, RawHeaders
# FIND WHAT IS ACTUALLY COSTING MONEY. Any reduction starts here:
#   Usage | where IsBillable
#   | summarize GB = sum(Quantity) / 1000 by DataType | order by GB desc
# PREFER METRIC ALERTS: cheaper and faster than log alerts. Log alerts are
# for conditions no metric expresses.
az monitor metrics alert create --name high-error-rate \
  --resource-group rg-shop-prod-uks --scopes /subscriptions/.../sites/app-shop \
  --condition "total Http5xx > 10" --window-size 5m --evaluation-frequency 1m \
  --severity 1 --action /subscriptions/.../actionGroups/ag-oncall
# DYNAMIC THRESHOLDS learn daily and weekly seasonality, which beats a fixed
# number that is too noisy at night or too loose at peak.
#   --condition "avg Requests > dynamic medium 4 of 5 since 2026-09-01T00:00:00Z"
# ONE RULE FOR EVERY VM IN A RESOURCE GROUP, including ones created later,
# instead of a rule per machine somebody must remember to create:
az monitor metrics alert create --name vm-cpu-high --resource-group rg-shop-prod-uks \
  --scopes /subscriptions/.../resourceGroups/rg-shop-prod-uks \
  --target-resource-type Microsoft.Compute/virtualMachines \
  --target-resource-region uksouth --condition "avg Percentage CPU > 85"

Was this explanation helpful?

Your feedback helps us improve the content for everyone.