Introduction
Azure Monitor is Microsoft Azure's unified solution for collecting, analyzing, and acting on monitoring data from your cloud resources. In 2026, with the rise of hybrid workloads and AI, it combines Logs (KQL), Metrics, Alerts, and Workbooks for full observability. This intermediate tutorial walks you through deploying a Log Analytics workspace, querying logs, setting up metric alerts, and creating actionable dashboards.
Why it matters: Poor monitoring costs a fortune—30% of cloud incidents stem from inadequate observability (Gartner 2025). You'll monitor a Windows VM with diagnostics, run advanced KQL queries, and automate responses via alerts. At the end, you'll have a production-ready, scalable, and secure setup—bookmark it for your Azure DevOps toolkit.
Prerequisites
Create the resource group and Log Analytics workspace
#!/bin/bash
# Variables (adapt to your environment)
SUBSCRIPTION_ID="votre-subscription-id"
RESOURCE_GROUP="rg-monitor-demo"
LOCATION="francecentral"
WORKSPACE_NAME="log-analytics-monitor-2026"
# Login and select subscription
az login
az account set --subscription $SUBSCRIPTION_ID
# Create the resource group
az group create --name $RESOURCE_GROUP --location $LOCATION --tags "project=monitor-demo" "env=dev"
# Create the Log Analytics Workspace
az monitor log-analytics workspace create \
--resource-group $RESOURCE_GROUP \
--workspace-name $WORKSPACE_NAME \
--location $LOCATION \
--sku PerGB2018 \
--retention-time 30 \
--immediate-purge-data-on-deletion true
# Retrieve the workspace ID for later steps
echo "Workspace ID: $(az monitor log-analytics workspace show --resource-group $RESOURCE_GROUP --workspace-name $WORKSPACE_NAME --query id -o tsv)"This complete Bash script sets up a resource group and Log Analytics workspace with 30-day retention and immediate purge for GDPR compliance. Use PerGB2018 to optimize costs (pay-as-you-go). Pitfall: Don't forget az login or adapt the variables, or you'll hit a 400 error.
Deploy a test VM to generate logs
To test Azure Monitor, let's deploy a simple Windows VM using an ARM template. This generates Heartbeat and Performance Counter logs. Download the template below and tweak the parameters.
Deploy Windows VM with diagnostics extension
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"vmName": {
"type": "string",
"defaultValue": "vm-monitor-test"
},
"adminUsername": {
"type": "string",
"defaultValue": "azureuser"
},
"adminPassword": {
"type": "securestring"
},
"workspaceId": {
"type": "string"
}
},
"variables": {},
"resources": [
{
"type": "Microsoft.Compute/virtualMachines",
"apiVersion": "2023-03-01",
"name": "[parameters('vmName')]",
"location": "francecentral",
"properties": {
"hardwareProfile": {
"vmSize": "Standard_B2s"
},
"storageProfile": {
"imageReference": {
"publisher": "MicrosoftWindowsServer",
"offer": "WindowsServer",
"sku": "2022-Datacenter",
"version": "latest"
}
},
"osProfile": {
"computerName": "[parameters('vmName')]",
"adminUsername": "[parameters('adminUsername')]",
"adminPassword": "[parameters('adminPassword')]",
"windowsConfiguration": {
"enableAutomaticUpdates": true
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/publicIPAddresses', concat(parameters('vmName'), '-pip'))]"
}
]
}
}
},
{
"type": "Microsoft.Compute/virtualMachines/extensions",
"apiVersion": "2022-03-01",
"name": "[concat(parameters('vmName'), '/MicrosoftMonitoringAgent')]",
"properties": {
"publisher": "Microsoft.Azure.Monitor",
"type": "AzureMonitorWindowsAgent",
"typeHandlerVersion": "1.0",
"autoUpgradeMinorVersion": true,
"settings": {
"workspaceId": "[parameters('workspaceId')]"
}
}
},
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2023-04-01",
"name": "[concat(parameters('vmName'), '-pip')]",
"sku": {
"name": "Basic"
},
"properties": {
"publicIPAllocationMethod": "Dynamic",
"dnsSettings": {
"domainNameLabel": "[concat(parameters('vmName'), '-', uniqueString(resourceGroup().id))]"
}
}
},
{
"type": "Microsoft.Network/networkInterfaces",
"apiVersion": "2023-04-01",
"name": "[concat(parameters('vmName'), '-nic')]",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig1",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', concat(parameters('vmName'), '-pip'))]"
},
"subnet": {
"id": "/subscriptions/{subscription-id}/resourceGroups/rg-monitor-demo/providers/Microsoft.Network/virtualNetworks/vnet-demo/subnets/default"
}
}
}
]
}
}
]
}This complete ARM template deploys a B2s Windows Server 2022 VM with the Azure Monitor Agent extension pointing to your workspace. Replace {subscription-id} and pass workspaceId as a parameter. Pitfall: Create a VNet/subnet first or add it to the template; deploy with az deployment group create --template-file vm-template.json.
Deploy the VM via Azure CLI
#!/bin/bash
RESOURCE_GROUP="rg-monitor-demo"
TEMPLATE_FILE="vm-template.json"
WORKSPACE_ID="$(az monitor log-analytics workspace show --resource-group $RESOURCE_GROUP --workspace-name log-analytics-monitor-2026 --query id -o tsv)"
# Deploy (adapt password)
az deployment group create \
--resource-group $RESOURCE_GROUP \
--template-file $TEMPLATE_FILE \
--parameters adminPassword='P@ssw0rd123!^' workspaceId=$WORKSPACE_ID
# Check deployment
echo "VM IP: $(az vm show -g $RESOURCE_GROUP -n vm-monitor-test --query publicIps -o tsv)"This script deploys the ARM template with the right parameters, including the Monitor agent to stream logs/metrics to the workspace. Wait 5-10 minutes for the first logs. Pitfall: Password must meet Azure policy (uppercase, numbers, symbols) or validation fails.
Query logs with KQL
Once logs are ingested (check in Azure Portal > Log Analytics > Logs), run KQL queries to analyze them. KQL is as powerful as SQL but optimized for big data: joins, time-series, ML anomalies.
KQL query for Heartbeat and CPU usage
Heartbeat
| where TimeGenerated > ago(1h)
| summarize HeartbeatCount = count() by Computer, bin(TimeGenerated, 5m)
| join kind=inner (
Perf
| where ObjectName == "Processor" and CounterName == "% Processor Time"
| where TimeGenerated > ago(1h)
| summarize AvgCPU = avg(CounterValue) by Computer, bin(TimeGenerated, 5m)
) on Computer, $left.TimeGenerated == $right.TimeGenerated
| project TimeGenerated, Computer, HeartbeatCount, AvgCPU
| render timechartThis KQL query joins Heartbeat (available) and Perf CPU over 1 hour, aggregates by 5 minutes, and renders a timechart. Copy-paste into the Logs blade. Pitfall: ago(1h) needs fresh data; if empty, check the agent with the InsightsMetrics table.
Enable diagnostic settings via PowerShell
# Variables
$rgName = "rg-monitor-demo"
$vmName = "vm-monitor-test"
$workspaceId = (Get-AzOperationalInsightsWorkspace -ResourceGroupName $rgName -Name "log-analytics-monitor-2026").CustomerId.Guid
# Login
Connect-AzAccount
Set-AzContext -SubscriptionId "votre-subscription-id"
# Enable diagnostics for VM (logs + metrics to workspace)
$diagSettings = New-AzDiagnosticSetting -Name "diag-to-workspace" `
-ResourceId (Get-AzVM -ResourceGroupName $rgName -Name $vmName).Id `
-WorkspaceId $workspaceId `
-Log '' `
-Metric '' `
-Category "VMInsights", "Perf", "Heartbeat", "WindowsEvent"
Write-Output "Diagnostics enabled: $($diagSettings.Id)"This PowerShell script enables VM diagnostics to the workspace, capturing Perf/Heartbeat/WindowsEvent. Use '' for all logs/metrics. Pitfall: Connect-AzAccount is interactive; for CI/CD, use a service principal.
Create a CPU > 80% metric alert
#!/bin/bash
RESOURCE_GROUP="rg-monitor-demo"
ALERT_NAME="High-CPU-Alert"
SCOPE_ID="$(az vm show -g $RESOURCE_GROUP -n vm-monitor-test --query id -o tsv)"
az monitor metrics alert create \
--name $ALERT_NAME \
--resource-group $RESOURCE_GROUP \
--scopes $SCOPE_ID \
--condition "\"Microsoft.Compute/virtualMachines\" AnyOf \"\"Percentage CPU\" > 80 avg 5m" \
--description "Alerte si CPU >80% sur 5min" \
--action-group "/subscriptions/votre-subscription-id/resourceGroups/rg-monitor-demo/providers/microsoft.insights/actionGroups/actiongroup-demo" \
--severity 2 \
--window-size 5m \
--evaluation-frequency 1m
# Note: Create an Action Group first via Portal or CLICreates a metric alert rule for CPU avg >80% (5min eval every 1min). Link to an Action Group (email/SMS). Pitfall: Replace subscription/actiongroup; test with CPU stress on the VM.
Best practices
- Optimized retention: 30-90 days for dev logs, 365+ for prod; enable purge to cut costs.
- Saved queries: Pin favorites in the Logs blade for quick reuse.
- Strict RBAC: Assign 'Monitoring Reader' minimally; use managed identities for agents.
- Costs: Track ingestion GB/month via Cost Management; filter useless logs.
- Integrate AIOps: Enable Smart Detection for ML-powered anomaly detection.
Common errors to avoid
- Agent not installed: Check
AzureMonitorWindowsAgentextension; redeploy if missing. - Empty queries: 15min ingestion delay; run
search *to validate. - Silent alerts: Test action group; no notifications for Sev0 without one.
- Region mismatch: Match workspace/VM location for <1s latency.
Next steps
Master Application Insights for web apps or Grafana with Azure Data Explorer. Check out our Azure DevOps training and official KQL docs. Contribute to Learni's GitHub for advanced templates.