Chapter 9
Monitoring: metrics, logs, alerts and Network Watcher
You've built a small but real environment. Now you need to see what it does and hear when something goes wrong. This chapter covers Azure Monitor (metrics, logs, KQL, alerts, Insights) and Network Watcher. Everything in the lab points at resources you already have.
Theory
Azure Monitor in one picture
Azure Monitor is the umbrella for collecting, analysing and acting on telemetry. It works with two main kinds of data, and almost every question in this area comes down to "is this a metric or a log, and how does it get there?"
Metrics
Platform metrics (like a VM's Percentage CPU or a storage account's Transactions) are collected automatically, every minute, and kept for 93 days. You don't configure anything. Note that a VM's platform metrics come from the host; metrics from inside the OS (memory used, disk free space) need the Azure Monitor Agent.
In Metrics explorer you build a chart by picking a scope, a metric and an aggregation. Reading those charts correctly is a skill the exam tests:
- Aggregation: Avg, Min, Max, Sum, Count. A CPU chart with Avg can hide short spikes that Max shows.
- Time granularity: each point summarises 1 minute, 5 minutes, 1 hour⦠Coarse granularity smooths the line.
- Dimensions: many metrics can be split or filtered by a dimension, for example load balancer Health Probe Status per Backend IP, or storage Transactions per API name or Response type.
- Charts can be pinned to a dashboard, added to a workbook, or turned into an alert rule.
Logs and Log Analytics
Types of logs
- Activity log: who did what on the control plane of a subscription (create VM, delete NSG, assign role, policy denials, service health). Collected automatically and kept for 90 days. Send it to a workspace with a diagnostic setting to keep it longer and query it with KQL (table
AzureActivity). - Resource logs: what happens inside a resource (blob reads, Key Vault access, load balancer events). Not collected until you create a diagnostic setting.
- Microsoft Entra logs: sign-in and audit logs of the tenant.
- Guest OS logs: Windows event logs, Linux syslog, performance counters, through the Azure Monitor Agent (AMA) and a data collection rule (DCR) that says what to collect and where to send it.
Diagnostic settings
A diagnostic setting on a resource (or subscription) sends selected log categories and/or metrics to one or more destinations:
- Log Analytics workspace: to query with KQL, alert on, and use in Insights.
- Storage account: cheap long-term archive (e.g. for auditors).
- Event Hubs: to stream to a SIEM or a third-party tool.
- Partner solutions (e.g. Datadog, Elastic).
A resource can have up to five diagnostic settings. For storage accounts, diagnostic settings are per service (blob, file, queue, table). At scale, you deploy diagnostic settings with Azure Policy (DeployIfNotExists, chapter 2).
Log Analytics workspaces
A Log Analytics workspace stores log data in tables (AzureActivity, Heartbeat, Perf, StorageBlobLogsβ¦). Things to know:
- Retention: interactive (analytics) retention from 30 days to 2 years, plus long-term retention up to 12 years at a lower price. The first 31 days are included in the ingestion price.
- Pricing: you pay mainly per GB ingested. Commitment tiers give a discount; a daily cap stops runaway costs (and data).
- Table plans: Analytics (full features), Basic and Auxiliary (cheaper, limited queries) for high-volume, low-value logs.
- Access control: workspace-context (you see all data you have workspace rights to) or resource-context (you see only logs of resources you have access to). Use few workspaces; many organisations use one per region or per environment.
Querying with KQL
Kusto Query Language (KQL) reads like a pipeline: start with a table, then pass the rows through operators with |. You don't need to be an expert, but you must be able to read a query and say what it returns.
// How many operations did each person do in the last day?
AzureActivity
| where TimeGenerated > ago(1d)
| where ActivityStatusValue == "Success"
| summarize Operations = count() by Caller
| order by Operations desc
| take 10
| Operator | Does | SQL equivalent |
|---|---|---|
where | Filter rows | WHERE |
project | Choose (and rename) columns | SELECT |
extend | Add a calculated column | SELECT β¦, x AS y |
summarize β¦ by | Aggregate: count(), avg(), max(), dcount()β¦ | GROUP BY |
bin(TimeGenerated, 5m) | Group times into buckets | |
order by / sort by, take / top | Sort, limit | ORDER BY, TOP |
join, union | Combine tables | JOIN, UNION |
render timechart | Draw a chart |
String comparisons: == is case-sensitive, =~ isn't; contains matches substrings, has matches whole terms (and is faster).
Alerts
An alert rule has four parts:
- Scope: the resource(s) to watch. A metric alert can cover many VMs of one type in one region at once.
- Condition: the signal and its logic. Signal types:
- Metric: "avg CPU > 80% over 5 minutes". Static threshold or dynamic thresholds (machine learning learns what's normal).
- Log search: a KQL query, run on a schedule, that fires when results meet a condition.
- Activity log: "someone deleted a VM", "a policy denied a request", Service Health (Azure incidents and planned maintenance in your regions) and Resource Health (your specific resource became unavailable).
- Actions: one or more action groups.
- Details: name and severity from Sev 0 (critical) to Sev 4 (verbose).
Metric alerts are stateful by default: they fire once, and resolve automatically when the condition is no longer true. An alert instance has a monitor condition (Fired / Resolved) and a user response you set (New, Acknowledged, Closed).
Action groups
An action group is a reusable list of who to notify and what to run:
- Notifications: email, SMS, Azure mobile app push, voice call, and Email Azure Resource Manager role (e.g. all Owners of the subscription).
- Actions: Automation runbook, Azure Function, Logic App, webhook / secure webhook, ITSM connector, Event Hub.
There are rate limits: for example no more than one SMS every 5 minutes and about 100 emails per hour per address. So an alert storm doesn't flood your phone.
Alert processing rules
Alert processing rules act on alerts after they fire, without editing each alert rule. Two uses:
- Suppression: remove all action groups during a planned maintenance window ("no notifications for
rg-az104-labon Saturday 22:00β02:00"). The alerts are still created, you just aren't notified. - Add action groups at scale: "send every Sev 0 alert in this subscription to the on-call team as well".
A rule has a scope (subscription, resource group, resources), optional filters (severity, alert rule name, resource type, monitor serviceβ¦) and an optional schedule (once, daily, weekly).
"Stop notifications for all alerts in a resource group during a monthly maintenance window, with the least effort." Not "disable every alert rule": create one alert processing rule with suppression and a schedule.
Insights
Insights are ready-made monitoring experiences for specific resource types, built on metrics, logs and workbooks:
- VM insights: CPU, memory, disk and network performance per VM, and (optionally) a Map of processes and connections. Enabling it installs the Azure Monitor Agent and creates a data collection rule that sends data to a workspace.
- Storage insights: capacity, transactions, latency and availability across all your storage accounts. Works from metrics, nothing to enable.
- Network insights: topology, health of load balancers, gateways, and connectivity.
- Container insights for AKS and Container Apps; Application Insights for code-level performance and failures of web apps.
Network Watcher
Network Watcher is a regional service for diagnosing IaaS network problems. It's enabled automatically per region (in a resource group called NetworkWatcherRG). Know which tool answers which question:
| Question | Tool |
|---|---|
| "Is this packet allowed or denied by an NSG, and by which rule?" | IP flow verify |
| "Which NSG rules apply, and in what order?" | NSG diagnostics / Effective security rules |
| "Where does traffic to this IP go? Is a UDR sending it somewhere odd?" | Next hop / Effective routes |
| "Can VM A reach host B on port X right now, and if not, why?" | Connection troubleshoot |
| "Alert me when latency or reachability between A and B changes, over time." | Connection Monitor |
| "What's actually on the wire?" | Packet capture |
| "Why is my VPN tunnel down?" | VPN troubleshoot |
| "Who talks to whom, how much traffic, allowed or denied?" | Flow logs + Traffic analytics |
| "What does my network look like?" | Topology |
Flow logs record IP traffic. VNet flow logs are the current version; the older NSG flow logs can no longer be created new and are being retired. Flow logs are written to a storage account, and Traffic analytics processes them into a Log Analytics workspace for dashboards.
Connection Monitor runs continuous tests from sources (Azure VMs, scale sets, on-premises machines with the agent) to destinations (VMs, URLs, IPs, FQDNs) over TCP, HTTP or ICMP. It reports checks failed % and round-trip time, and you can alert on them.
AZ-305 asks you to design a monitoring solution: how many workspaces (and where, for data residency), which logs go where (workspace for analysis, storage for cheap archive, Event Hubs for a SIEM), and how to enforce it with Azure Policy. Remember: activity log 90 days by default; longer means a diagnostic setting.
Self-check
1. Auditors need to know who deleted a VM 5 months ago. Nothing was configured. Can you find out?
C. Set up a subscription diagnostic setting now for the future.
2. You want to query a Key Vault's access events with KQL. What must you configure?
A. Resource logs need a diagnostic setting; agents are for VMs.
3. A VM can't reach 10.50.0.4. You suspect a UDR sends traffic to a firewall that's down. Which tool shows that quickest?
D. Next hop returns the next hop type, IP and the route table used.
4. What does this query return? Heartbeat | summarize max(TimeGenerated) by Computer
B. A common query to find agents that stopped reporting.
5. You need memory usage (% used) of Linux VMs in Azure Monitor. What do you need?
C. The host can't see inside the guest's memory use. (There's an "Available Memory Bytes" platform metric, but not % used per process.)
Lab: watch your environment
Everything here uses log-az104 (chapter 4), the VMs (chapter 5), the load balancer (chapter 6), the storage account (chapter 7) and the route from chapter 3. Start the VMs and paste the variables:
RG=rg-az104-lab; LOC=westeurope; LOC2=northeurope; INIT=xx
ST=staz104${INIT}01
az vm start -g $RG -n vm-web1 --no-wait; az vm start -g $RG -n vm-web2 --no-wait
LAWID=$(az monitor log-analytics workspace show -g $RG -n log-az104 --query id -o tsv)
-
Task
Read the metrics
Build one chart with the CPU of both web VMs as separate lines over the last 4 hours, and decide whether Avg or Max aggregation is more useful to spot short spikes. Build a second chart for
lbe-webshowing Data Path Availability. Pin both to a dashboard.Done when- A dashboard shows both charts.
Solution Β· Portal
- Monitor β Metrics β scope: select both VMs (same type and region) β metric Percentage CPU, aggregation Max β Apply splitting by Resource (if offered) or add a second metric line.
- Time range Last 4 hours. Save to dashboard β Pin to dashboard (new dashboard
az104). - New chart: scope
lbe-web, metric Data Path Availability, aggregation Avg β pin.
Max shows short spikes; Avg over 5 minutes smooths them away.
-
Task
Collect the logs that aren't collected by default
Send to
log-az104:- the subscription's activity log (all categories);
- the blob service logs of your storage account (read, write, delete) plus its transaction metrics.
Then read a blob (e.g. download
hello.txtin the portal's storage browser) so there's something to see.Done when- Both diagnostic settings exist.
- After 5β15 minutes, the tables
AzureActivityandStorageBlobLogshave rows.
Solution Β· Portal
- Monitor β Activity log β Export Activity Logs β Add diagnostic setting: tick all categories, destination Send to Log Analytics workspace β
log-az104. - Storage account β Monitoring β Diagnostic settings β blob β Add diagnostic setting: StorageRead, StorageWrite, StorageDelete, metric Transaction β
log-az104.
Solution Β· Azure CLI
az monitor diagnostic-settings subscription create -n diag-activity --location $LOC --workspace $LAWID --logs '[ {"category":"Administrative","enabled":true},{"category":"Security","enabled":true}, {"category":"ServiceHealth","enabled":true},{"category":"Alert","enabled":true}, {"category":"Recommendation","enabled":true},{"category":"Policy","enabled":true}, {"category":"Autoscale","enabled":true},{"category":"ResourceHealth","enabled":true}]' STID=$(az storage account show -g $RG -n $ST --query id -o tsv) az monitor diagnostic-settings create -n diag-blob --resource $STID/blobServices/default --workspace $LAWID \ --logs '[{"category":"StorageRead","enabled":true},{"category":"StorageWrite","enabled":true},{"category":"StorageDelete","enabled":true}]' \ --metrics '[{"category":"Transaction","enabled":true}]' -
Task
Look inside the VMs
Enable VM insights for both web VMs, sending data to
log-az104. Find out which extension got installed and which data collection rule was created.Done when- Monitor β Virtual Machines β Performance shows CPU and memory for both VMs (allow 10β15 minutes).
- You can name the agent extension and the DCR.
Solution Β· Portal
vm-web1β Monitoring β Insights β Enable β choose Azure Monitor Agent, create or pick a data collection rule (e.g.MSVMI-az104) targetinglog-az104; process/dependency map optional β Configure.- Repeat for
vm-web2with the same DCR. - The VM's Extensions + applications shows AzureMonitorLinuxAgent; the DCR is under Monitor β Data Collection Rules.
-
Task
Get an email when a web server is overloaded
Create an action group
ag-az104-emailthat emails you. Then create one metric alert rulealert-vm-cputhat covers both web VMs and fires at Sev 2 when average CPU is above 80% over 5 minutes. Trigger it by loading the CPU ofvm-web1.Done when- You receive the "Fired" email, and later a "Resolved" email.
- Monitor β Alerts shows the alert; you change its state to Acknowledged.
Hint
For load: Run command
timeout 900 sh -c 'yes > /dev/null'keeps one core at 100% for 15 minutes.Solution Β· Portal
- Monitor β Alerts β Action groups β Create: name
ag-az104-email, display nameaz104, Notifications: Email/SMS/Push/Voice β your email. - Monitor β Alerts β Create β Alert rule: scope β filter resource type Virtual machines, select both VMs. Condition Percentage CPU, static, Average, greater than 80, check every 1 min, lookback 5 min. Actions:
ag-az104-email. Details: Sev 2, namealert-vm-cpu. vm-web1β Run command βtimeout 900 sh -c 'yes > /dev/null'.
Solution Β· Azure CLI
az monitor action-group create -g $RG -n ag-az104-email --short-name az104 --action email me <your-email> VM1=$(az vm show -g $RG -n vm-web1 --query id -o tsv); VM2=$(az vm show -g $RG -n vm-web2 --query id -o tsv) az monitor metrics alert create -g $RG -n alert-vm-cpu --scopes $VM1 $VM2 \ --target-resource-type Microsoft.Compute/virtualMachines --region $LOC \ --condition "avg Percentage CPU > 80" --window-size 5m --evaluation-frequency 1m \ --severity 2 --action ag-az104-email az vm run-command invoke -g $RG -n vm-web1 --command-id RunShellScript \ --scripts "nohup timeout 900 sh -c 'yes > /dev/null' >/dev/null 2>&1 &" -
Task
Silence alerts during maintenance
Next Saturday from 22:00 to 23:00 you'll patch the VMs. Make sure no alert in
rg-az104-labsends notifications in that window, without changing or disabling any alert rule.Done when- An alert processing rule with a one-time schedule and suppression exists for the resource group.
Solution Β· Portal
Monitor β Alerts β Alert processing rules β Create: scope
rg-az104-lab, rule type Suppress notifications, scheduling At a specific time, start Saturday 22:00, end 23:00 (your time zone), nameapr-maintenance.Solution Β· Azure CLI
RGID=$(az group show -n $RG --query id -o tsv) # change the dates to your next Saturday az monitor alert-processing-rule create -g $RG -n apr-maintenance --rule-type RemoveAllActionGroups \ --scopes $RGID --schedule-start-datetime "2026-11-21 22:00:00" --schedule-end-datetime "2026-11-21 23:00:00" \ --schedule-time-zone "W. Europe Standard Time" --description "Patch window" -
Task
Answer questions with KQL
In
log-az104β Logs, write queries that answer:- When did each VM last send a heartbeat?
- What was the CPU utilisation per VM in 5-minute buckets over the last hour, as a chart? (You should see your load test.)
- Who did which operations in your subscription today?
- Which blob operations hit your storage account, with status and caller IP?
Done when- All four queries return results, and query 2 shows a time chart with the CPU spike.
Hint
Tables:
Heartbeat,InsightsMetrics(VM insights; namespaceProcessor, nameUtilizationPercentage),AzureActivity,StorageBlobLogs.Solution Β· KQL
// 1 Heartbeat | summarize LastSeen = max(TimeGenerated) by Computer // 2 InsightsMetrics | where TimeGenerated > ago(1h) | where Namespace == "Processor" and Name == "UtilizationPercentage" | summarize CPU = avg(Val) by bin(TimeGenerated, 5m), Computer | render timechart // 3 AzureActivity | where TimeGenerated > ago(1d) | summarize Operations = count() by Caller, OperationNameValue | order by Operations desc // 4 StorageBlobLogs | where TimeGenerated > ago(1d) | project TimeGenerated, OperationName, StatusCode, CallerIpAddress, Uri | order by TimeGenerated desc -
Task
Diagnose the network with Network Watcher
- Prove that traffic from
vm-web1to203.0.113.10is dropped, and name the route that drops it. - Check right now whether
vm-web1can reach your storage account's file endpoint on port 445, and which IP it connects to. - Set up continuous monitoring (every 30 seconds) from
vm-web1to the file endpoint on 445 and towww.microsoft.comon 443, namedcm-az104.
Done when- Next hop: type None, route
blackhole-testnet(from chapter 3). - Connection troubleshoot: Reachable, via
10.20.2.x(the private endpoint). cm-az104shows checks and round-trip times for both destinations.
Solution Β· Portal
- Network Watcher β Next hop: VM
vm-web1, source IP its private IP, destination203.0.113.10. - Network Watcher β Connection troubleshoot: source
vm-web1, destination Manuallystaz104xx01.file.core.windows.net, TCP port 445. (It installs the Network Watcher agent if needed.) - Network Watcher β Connection monitor β Create: name
cm-az104, region; test group with sourcevm-web1, destinations: External addressesstaz104xx01.file.core.windows.netandwww.microsoft.com; test configurations TCP 445 and HTTPS 443, frequency 30 s; workspacelog-az104.
Solution Β· Azure CLI
IP1=$(az vm show -g $RG -n vm-web1 -d --query privateIps -o tsv) az network watcher show-next-hop -g $RG --vm vm-web1 --source-ip $IP1 --dest-ip 203.0.113.10 az network watcher test-connectivity -g $RG --source-resource vm-web1 \ --dest-address $ST.file.core.windows.net --dest-port 445 \ --query "{status:connectionStatus, hops:hops[].address}" # Connection Monitor is easiest in the portal (see the portal solution). - Prove that traffic from
VM insights, the activity log and blob logs send a small amount of data (well under 1 GB for this lab, a few euros at most). Connection Monitor tests are cheap at this scale. You delete everything in chapter 10.
- Activity log and blob logs flow into
log-az104; VM insights runs on both VMs. - Action group
ag-az104-email, alert rulealert-vm-cpu, alert processing ruleapr-maintenance. - Connection monitor
cm-az104watches the path fromvm-web1to storage.