Skip to content

Module 3.10: Azure Monitor & Log Analytics

Complexity: [MEDIUM] | Time to Complete: 2.5h | Prerequisites: Module 3.3 (VMs), Module 3.1 (Entra ID). This module balances practical incident-readiness with conceptual grounding so you can configure monitoring both quickly and correctly under pressure.

After completing this module, you will be able to complete these practical outcomes and apply them to production incidents.

  • Configure Azure Monitor with Log Analytics workspaces, diagnostic settings, and custom metric collection
  • Implement alert rules with action groups, dynamic thresholds, and multi-resource metric alerts
  • Deploy Application Insights for distributed tracing, dependency mapping, and performance anomaly detection
  • Design centralized monitoring architectures using Azure Monitor across multiple subscriptions and resource types

Hypothetical scenario: a logistics company runs a fleet management application on Azure. Customer complaints triple over two weeks while drivers report slow load times and intermittent errors. The engineering team has not configured monitoring beyond default portal metrics. When someone finally investigates, they find an Azure SQL database at sustained high DTU utilization. A reporting query deployed with a routine release runs every five minutes and dominates database capacity. The outage was preventable: a metric alert on database utilization and a log query on slow queries would have surfaced the regression within hours instead of weeks.

Observability is not a luxury. It is the difference between proactively fixing problems and reactively apologizing to customers. Azure Monitor is the platform’s unified observability service, collecting metrics and logs from every Azure resource, virtual machine, container, and application. Log Analytics provides the query engine to make sense of that data. Alerts turn signals into actions. Application Insights traces requests through distributed systems.

In this module, you will learn how Azure Monitor collects and organizes data, how to explore metrics in Metrics Explorer, how to write KQL (Kusto Query Language) queries against Log Analytics, how to create alerts with action groups, and how Application Insights provides end-to-end request tracing. By the end, you will deploy monitoring agents on a VM, ingest custom logs, write KQL queries, and create an alert that sends email when a threshold is breached.


Azure Monitor is not a single service—it is a platform composed of interconnected components designed as a chain. Think of three layers: ingestion (agents, diagnostic settings, App Insights SDKs, platform pipelines), storage and analysis (metrics time-series store and Log Analytics tables), and action (alerts, autoscale, Logic Apps, webhooks). Data from subscriptions, regions, and on-premises Arc machines can funnel into shared workspaces so SRE sees one operational picture instead of fifty siloed portals.

Enterprise landing zones typically designate a monitoring subscription that hosts Log Analytics workspaces, action groups, and alert rules. Spoke subscriptions hold applications with diagnostic settings and DCR associations pointing at the hub workspace. Azure Policy can deploy diagnostic settings and AMA associations automatically so new resources do not ship without telemetry. Tag resources with environment, cost-center, and owner so KQL can filter AzureActivity and AzureResourceGraph results during incidents.

Cross-subscription visibility does not require moving resources—only correct routing and RBAC. Readers need Log Analytics Reader on the workspace; alert rule authors need Monitoring Contributor on scopes they manage. Document which workspace holds production security data versus production application data before an incident forces ad-hoc merging.

The Azure Activity log records control-plane operations (who created a VM, who deleted a resource group). It lands in the AzureActivity table when collected to a workspace. Resource logs (data plane) describe what happened inside the resource—SQL deadlocks, Key Vault access, App Service HTTP logs. Diagnostic settings control resource log flow. Confusing the two leads to gaps: Activity log alerts catch deletions but not query time-outs; SQL diagnostic logs catch slow queries but not RBAC assignments. Enable both for production data stores.

flowchart TD
subgraph DS [Data Sources]
R[Azure Resources]
V[VMs Agent]
C[Containers AKS/ACA]
A[Applications App Insights]
end
subgraph AMP [Azure Monitor Platform]
M["Metrics Store<br/>(Time-series DB)<br/>• Platform metrics<br/>• Custom metrics<br/>• 93 day retention<br/>• Near real-time"]
L["Log Analytics Workspace<br/>(Kusto / KQL engine)<br/>• Activity logs<br/>• Resource logs<br/>• VM agent logs<br/>• App traces<br/>• Custom logs"]
end
R --> M
R --> L
V --> M
V --> L
C --> M
C --> L
A --> M
A --> L
subgraph CONS [Consumers]
C1[Metrics Explorer / Workbooks]
C2[KQL Queries / Power BI]
C3[Dashboards / Grafana]
C4[Alerts / APIs]
end
M --> C1
M --> C3
M --> C4
L --> C1
L --> C2
L --> C3
L --> C4

Stop and think: If a critical application crashes due to an out-of-memory exception, which monitoring capability (metrics or logs) would alert you that the memory was exhausted, and which would help you find the specific line of code that caused it?

AspectMetricsLogs
Data typeNumeric time-series (counters, gauges)Semi-structured text (JSON, syslog)
LatencyNear real-time (~1 minute)2-5 minutes (ingestion delay)
Retention93 days (free)31 days free, up to 12 years paid
Query languageSimple (filter, aggregate)KQL (powerful, SQL-like)
CostFree (platform metrics)Per-GB ingested (see pricing)
Best forDashboards, alerting on thresholdsRoot cause analysis, auditing, forensics
ExamplesCPU %, memory %, request count, latencyError messages, audit events, request traces

The observability funnel analogy

Metrics are the speedometer and fuel gauge on your dashboard—continuous numbers that tell you something is wrong in seconds. Logs are the flight recorder—richer, heavier, and essential after an incident to reconstruct decisions and errors. Diagnostic settings are the cables that connect each system to the recorder. Alerts are the seatbelt warning light: useless if nobody hears the chime.

The data planes in depth: metrics, logs, and diagnostic settings

Section titled “The data planes in depth: metrics, logs, and diagnostic settings”

Azure Monitor splits telemetry into two complementary data platforms. Metrics land in a regional time-series store optimized for numeric aggregation, charting, and fast threshold evaluation. Logs land in a Log Analytics workspace as typed tables queried with Kusto Query Language (KQL). Neither plane replaces the other: metrics answer “is something wrong right now?” while logs answer “what exactly happened and who triggered it?”

Platform metrics are emitted automatically by most Azure resources. They are multi-dimensional (you can slice by instance, region, or HTTP status code where the resource supports dimensions), near-real-time with about one-minute granularity for many resource types, and retained for roughly 93 days without ingestion charges for standard platform metrics. Metrics Explorer is the portal experience for ad-hoc charts; the same metric definitions power metric alerts and autoscale rules. Custom metrics (from your application or from the Azure Monitor Agent) follow the same pipeline but may incur charges depending on volume and configuration.

Log Analytics workspaces are the central SaaS data store for log tables. One workspace can hold activity logs, resource diagnostic logs, VM agent data, container stdout, and Application Insights tables side by side. That consolidation is what makes cross-resource KQL possible: you can join a Key Vault audit event with an App Service request trace in one query instead of hunting across siloed tools. Workspace-level settings include default retention, daily ingestion cap, commitment tier pricing, and optional dedicated clusters for advanced scenarios such as customer-managed keys.

Table plans control cost versus query power per table. The Analytics plan is the default for high-value operational data: full KQL, alerts, Insights, dashboards, and interactive retention (30 days by default, extendable with long-term retention). The Basic plan reduces ingestion cost for medium-touch troubleshooting data; you still run KQL on a single table (with lookup into Analytics tables), and simple log alerts are supported. The Auxiliary plan minimizes ingestion cost for verbose or compliance-oriented streams but is not optimized for real-time analysis or alerting. Choosing the wrong plan for a firehose table (for example, verbose debug logs on Analytics) is one of the fastest ways to inflate a bill without improving incident response.

Retention and archive are layered. Interactive analytics retention applies while data is “hot” in the workspace. Long-term retention can extend total retention up to 12 years per table at a lower storage rate, with search jobs or restore operations when you need to query cold data. Deleting rows with the purge API does not reduce retention charges—you must lower retention settings on the workspace or table. Plan retention per compliance need, not “keep everything forever by default.”

Diagnostic settings are the routing pipe from Azure resources into destinations you control. A single diagnostic setting can send resource logs (formerly diagnostic logs) and optionally metrics to a Log Analytics workspace, an Azure Storage account, or an Event Hub. Sending to a workspace enables KQL, log alerts, and correlation with other tables. Storage is cheaper for long-term archive and compliance exports but is not queryable with KQL until you re-import or use external tools. Event Hub fits streaming to SIEM pipelines or custom processors. Many teams use workspace for operations and Storage or Event Hub for regulated archives. Diagnostic settings are not enabled by default on every resource—treat them as day-one infrastructure, often enforced with Azure Policy.

Terminal window
# Route Key Vault audit logs and all metrics to a workspace (same pattern for SQL, Storage, etc.)
WORKSPACE_ID=$(az monitor log-analytics workspace show -g myRG -n kubedojo-logs --query id -o tsv)
az monitor diagnostic-settings create \
--name "audit-and-metrics-to-law" \
--resource "/subscriptions/<sub>/resourceGroups/myRG/providers/Microsoft.Sql/servers/myserver/databases/mydb" \
--workspace "$WORKSPACE_ID" \
--logs '[{"category":"SQLSecurityAuditEvents","enabled":true},{"category":"Errors","enabled":true}]' \
--metrics '[{"category":"AllMetrics","enabled":true}]'
# Export the same categories to Storage for cheap long-term retention (archive path)
STORAGE_ID=$(az storage account show -g myRG -n mymonitorarchive --query id -o tsv)
az monitor diagnostic-settings create \
--name "sql-logs-to-storage" \
--resource "/subscriptions/<sub>/resourceGroups/myRG/providers/Microsoft.Sql/servers/myserver/databases/mydb" \
--storage-account "$STORAGE_ID" \
--logs '[{"category":"SQLSecurityAuditEvents","enabled":true}]'

When you design monitoring, draw two arrows from every critical resource: one to metrics for dashboards and fast alerts, one through diagnostic settings to the right log destination for forensics. Skipping diagnostic settings leaves you with platform metrics only—you will see that CPU spiked but not which query or principal caused it.

Network Watcher VNet flow logs are the current path for virtual-network traffic visibility. Legacy NSG flow logs retire 2027-09-30, and new NSG flow logs can no longer be created after 2025-06-30—plan migrations to VNet flow logs. Traffic analytics can generate very large log volume. Treat flow logging as a deliberate cost decision: flow logs to Storage or Event Hub for security analytics, not always to Analytics tables unless security operations queries them daily. Azure Monitor for networks provides curated views when network diagnostics and topology data are enabled.

For Azure Kubernetes Service, control plane metrics remain platform-managed while workload telemetry flows through Container Insights or OpenTelemetry collectors. Align with your platform team on whether container stdout belongs in ContainerLogV2 on Analytics or Auxiliary—verbose application logging from dozens of pods is a common reason production workspaces exceed ingestion forecasts.

Applications can send custom structured logs with the Logs Ingestion API and DCR-defined destinations. Prefer structured JSON with stable schemas so tables remain queryable and transformations can drop fields before billing. Custom logs without retention planning often become “forever Analytics tables” that dominate workspace cost.


Every Azure resource automatically emits platform metrics to the Azure Monitor metrics store, so the most important signals are collected as soon as the resource is provisioned. That means a virtual machine, database, storage account, or container app will usually surface CPU, memory, network, and disk telemetry without you having to install an agent first. In practice, Metrics Explorer is your fastest way to validate whether a resource is healthy right now, because it shows near-real-time values that are already there by default. Use it first to confirm baseline behavior, and only after you understand the normal operating range should you start defining alerts and deeper log investigation.

Terminal window
# List available metrics for a VM
az monitor metrics list-definitions \
--resource "/subscriptions/<sub>/resourceGroups/myRG/providers/Microsoft.Compute/virtualMachines/myVM" \
--query '[].{Name:name.value, Unit:unit, Aggregation:primaryAggregationType}' -o table
# Query a specific metric (CPU percentage, last 1 hour, 5-minute intervals)
az monitor metrics list \
--resource "/subscriptions/<sub>/resourceGroups/myRG/providers/Microsoft.Compute/virtualMachines/myVM" \
--metric "Percentage CPU" \
--interval PT5M \
--start-time "$(date -u -v-1H '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date -u -d '-1 hour' '+%Y-%m-%dT%H:%M:%SZ')" \
--aggregation Average Maximum \
-o table
# Query storage account metrics
az monitor metrics list \
--resource "/subscriptions/<sub>/resourceGroups/myRG/providers/Microsoft.Storage/storageAccounts/mystorage" \
--metric "Transactions" \
--interval PT1H \
--aggregation Total \
-o table

Common metrics you should usually monitor are your first line of defense in day-two operations. Platform metrics appear in Metrics Explorer within about a minute for many resource types, which is why on-call starts charts before KQL during a sev-1. Custom metrics let applications emit business KPIs (orders queued, jobs failed) via the Metrics REST API or OpenTelemetry—useful when infrastructure looks healthy but the business pipeline is stuck.

Multi-dimensional metrics let one alert rule split on dimension values (for example HTTP status code on App Service). Each dimension value can become a billed time series in large scopes, so prefer focused scopes or fewer dimensions when cost matters. Metric namespaces separate platform metrics (Microsoft.Compute/virtualMachines) from guest OS and custom namespaces; verify you alert on the namespace that actually receives data after AMA or diagnostic settings are configured.

Teams tune default thresholds during normal traffic periods, then revise after observing seasonality. Document baseline charts in a workbook so new engineers know what “healthy” looks like before they page the team for a expected nightly batch spike.

ResourceCritical MetricsAlert Threshold (suggestion)
VMPercentage CPU, Available Memory Bytes, Disk IOPSCPU > 85%, Memory < 10%, Disk queue > 5
SQL DatabaseDTU percentage, Connection failedDTU > 80%, Failed connections > 10/5min
Storage AccountAvailability, E2E LatencyAvailability < 99.9%, Latency > 100ms
App ServiceResponse time, HTTP 5xx, CPU %Response > 2s, 5xx > 5/min, CPU > 80%
Container AppReplica count, Request count, Response timeResponse > 1s, Restarts > 3/hour
Key VaultService API Latency, AvailabilityLatency > 1s, Availability < 99.9%

When building dashboards, pin the charts that on-call checks first—DTU for databases, Percentage CPU for VMs, Http5xx for App Service—then link each chart to the alert rule that protects the same signal. That pairing trains the team that a green dashboard and a silent alert channel are related, not redundant.


A Log Analytics workspace is the central repository for log data in Azure Monitor. Logs from resources, VMs, containers, and applications can all be sent to the same workspace, which makes cross-resource troubleshooting possible without switching between dozens of portal blades. You use the workspace not just to store data but to reason across it: one KQL query can correlate a Key Vault AuditEvent, an App Service AppRequest, and a SQL AzureDiagnostics row that share a time window and resource ID.

Workspace architecture across subscriptions

Section titled “Workspace architecture across subscriptions”

Platform teams often deploy one workspace per environment (production, staging, development) rather than per application. Resources in any subscription can send data to a workspace in another subscription as long as RBAC and network rules allow it. That pattern supports centralized monitoring for enterprise landing zones: hub subscriptions host the workspace, spoke subscriptions host workloads with diagnostic settings pointed at the hub. Use Azure Monitor workspace access management and workspace permissions so application teams query only their tables or resource sets.

When Microsoft Sentinel or Defender for Cloud is enabled on a workspace, billing and data mix change—Sentinel charges apply to security tables, and operational data may need a separate workspace to avoid paying Sentinel rates on routine VM performance logs. The workspace design guidance documents tradeoffs between single workspace simplicity and multi-workspace isolation.

Data collection rules and ingestion pipeline

Section titled “Data collection rules and ingestion pipeline”

Modern collection uses Data Collection Rules (DCRs) attached to Azure Monitor Agent on VMs, or platform-native diagnostic settings on PaaS resources. DCRs define streams (performance, syslog, Windows events), optional transformations that filter or reshape JSON before ingestion, and destinations (workspace, metrics, storage). Transformations are a cost control: drop DEBUG fields, strip PII, or route noisy tables to Basic plans before billable ingestion occurs.

Terminal window
# List tables in a workspace (discover what is actually ingesting)
az monitor log-analytics workspace table list \
--resource-group myRG \
--workspace-name kubedojo-logs \
--query '[].{Name:name, Plan:plan}' -o table
Terminal window
# Create a Log Analytics workspace
az monitor log-analytics workspace create \
--resource-group myRG \
--workspace-name kubedojo-logs \
--location eastus2 \
--retention-time 90
# Get workspace details
az monitor log-analytics workspace show \
--resource-group myRG \
--workspace-name kubedojo-logs \
--query '{Name:name, ID:customerId, RetentionDays:retentionInDays}' -o table
# Enable diagnostic logs for a resource (send to Log Analytics)
WORKSPACE_ID=$(az monitor log-analytics workspace show \
-g myRG -n kubedojo-logs --query id -o tsv)
az monitor diagnostic-settings create \
--name "send-to-log-analytics" \
--resource "/subscriptions/<sub>/resourceGroups/myRG/providers/Microsoft.KeyVault/vaults/myVault" \
--workspace "$WORKSPACE_ID" \
--logs '[{"category":"AuditEvent","enabled":true}]' \
--metrics '[{"category":"AllMetrics","enabled":true}]'
TableSourceContains
AzureActivityAll resourcesControl plane operations (create, delete, update)
AzureDiagnosticsResources with diagnosticsResource-specific logs (SQL, Key Vault, etc.)
HeartbeatAzure Monitor AgentVM health status (every minute)
SyslogLinux VMsSystem logs (auth, kernel, daemon)
EventWindows VMsWindows Event Log entries
PerfVMs with agentPerformance counters (CPU, memory, disk)
ContainerLogV2AKSContainer stdout/stderr
AppRequestsApplication InsightsHTTP request traces
AppExceptionsApplication InsightsException traces

The _ResourceId and _SubscriptionId columns on many tables tie rows back to Azure Resource Manager IDs. Use them in where clauses to scope dashboards per team without separate workspaces: where _ResourceId contains "/resourceGroups/prod-app/". The _IsBillable column marks rows excluded from ingestion charges—useful when reconciling usage reports with raw row counts.

Querying Entra sign-in data requires the SigninLogs table (often via Microsoft Graph connector or Sentinel). Security investigations blend SigninLogs, AzureActivity, and AuditLogs depending on which diagnostic streams you enabled—document which table is authoritative for your tenant.


KQL is the query language for Azure Monitor logs, and it is designed for operational data where you usually start with a lot of signals and narrow down quickly. It is similar to SQL in readability, but its distinctive pipe-based model lets you chain transformations step by step, so each line of a query progressively refines what you care about. In practice, this is powerful because you can run a broad query to find candidate events, then immediately project only the columns and statistics you need without rewriting the whole statement.

Read KQL top to bottom as a pipeline: each pipe takes the result of the previous step. Start wide with a table and time filter, then narrow with where, shape with project or extend, aggregate with summarize, and present with order or render. The Log Analytics portal Simple mode helps beginners without syntax; KQL mode is what alert rules and workbooks store—learn both because on-call will paste alert queries into the editor during incidents.

Time filters are non-negotiable for cost and speed. Tables are date-partitioned; where TimeGenerated > ago(1h) limits scanned data. Use bin(TimeGenerated, 5m) for trend charts so alert queries and dashboards share the same grain. When comparing weeks, use ago(7d) and ago(14d) in parallel queries rather than open-ended scans.

// Basic query structure: Table | operator1 | operator2 | ...
// Find all failed login attempts in the last 24 hours
SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType != 0 // 0 = success
| project TimeGenerated, UserPrincipalName, IPAddress, ResultDescription
| order by TimeGenerated desc
| take 50
// Count errors by type in the last hour
AzureDiagnostics
| where TimeGenerated > ago(1h)
| where Category == "AuditEvent"
| where ResultType == "Failure"
| summarize FailureCount = count() by OperationName
| order by FailureCount desc
// Calculate P50, P95, P99 response times
AppRequests
| where TimeGenerated > ago(1h)
| summarize
P50 = percentile(DurationMs, 50),
P95 = percentile(DurationMs, 95),
P99 = percentile(DurationMs, 99),
Total = count()
by bin(TimeGenerated, 5m)
| order by TimeGenerated asc
// Find VMs with high CPU (from performance counters)
Perf
| where TimeGenerated > ago(15m)
| where CounterName == "% Processor Time"
| where InstanceName == "_Total"
| summarize AvgCPU = avg(CounterValue), MaxCPU = max(CounterValue) by Computer
| where AvgCPU > 80
| order by AvgCPU desc

Joining infrastructure and application signals

Section titled “Joining infrastructure and application signals”

Production investigations rarely stay in one table. A failed checkout might require AppRequests joined to AppDependencies on OperationId, then cross-checked against AzureDiagnostics for SQL time-outs on the same ResourceId. Use join kind=inner when you need matching keys only; use lookup when enriching a large table with a small reference set (region map, service owner table) to avoid expensive full scans.

// Correlate slow requests with SQL dependency failures (last hour)
AppRequests
| where TimeGenerated > ago(1h)
| where DurationMs > 3000
| project RequestTime = TimeGenerated, OperationId, RequestName = Name, RequestDuration = DurationMs
| join kind=inner (
AppDependencies
| where TimeGenerated > ago(1h)
| where DependencyType == "SQL"
| where Success == false
| project OperationId, SqlTarget = Target, SqlResult = ResultCode
) on OperationId
| order by RequestTime desc
| take 50

Azure Monitor workbooks combine metrics, log queries, and text into shareable operational views. Pin single charts to Azure dashboards for executive summaries. Azure Managed Grafana integrates with Monitor for teams that already standardized on Grafana dashboards—useful when you unify cloud and on-premises metrics in one pane.

OperatorPurposeExample
whereFilter rowswhere Status == 500
projectSelect/rename columnsproject Time=TimeGenerated, User
summarizeAggregate (count, avg, sum, etc.)summarize count() by Status
order bySort resultsorder by TimeGenerated desc
takeLimit result counttake 100
extendAdd computed columnsextend DurationSec = DurationMs / 1000
binGroup time into bucketsbin(TimeGenerated, 5m)
joinJoin two tablesjoin kind=inner (Table2) on Key
renderVisualize as chartrender timechart
agoRelative timeago(24h), ago(7d)
betweenRange filterwhere Value between (10 .. 100)

Practical KQL Queries for Day-to-Day Operations

Section titled “Practical KQL Queries for Day-to-Day Operations”
// Who deleted resources in the last 7 days?
AzureActivity
| where TimeGenerated > ago(7d)
| where OperationNameValue endswith "delete"
| where ActivityStatusValue == "Success"
| project TimeGenerated, Caller, ResourceGroup,
Resource = tostring(split(ResourceId, "/")[-1]),
Operation = OperationNameValue
| order by TimeGenerated desc
// Disk space usage trends (last 24 hours)
Perf
| where TimeGenerated > ago(24h)
| where ObjectName == "LogicalDisk" and CounterName == "% Free Space"
| where InstanceName !in ("_Total", "HarddiskVolume1")
| summarize AvgFreeSpace = avg(CounterValue) by Computer, InstanceName, bin(TimeGenerated, 1h)
| order by AvgFreeSpace asc
// Top 10 slowest API endpoints
AppRequests
| where TimeGenerated > ago(1h)
| where Success == true
| summarize AvgDuration = avg(DurationMs), P99 = percentile(DurationMs, 99), Count = count() by Name
| where Count > 10 // Filter out infrequent endpoints
| order by P99 desc
| take 10
// Anomaly detection: sudden spike in errors
AppExceptions
| where TimeGenerated > ago(24h)
| summarize ErrorCount = count() by bin(TimeGenerated, 15m)
| render timechart

Pause and predict: If you configure an alert to trigger when CPU exceeds 90%, but you do not assign an Action Group to it, what will happen when the CPU hits 100%?

Alerts connect signal detection to response. A durable alert design has four parts: the signal (metric, log query, or activity log), the evaluation logic (threshold, dynamic band, or KQL aggregation), the severity (0–4 in Azure Monitor), and an action group that notifies humans or triggers automation. Alerts without action groups are dashboard decorations—they change state in the portal while on-call engineers sleep through the incident.

TypeMonitorsEvaluation FrequencyUse Case
Metric alertNumeric time-seriesAs low as 1 minuteCPU, DTU, latency, queue depth—fast, cheap signals
Log alert (scheduled query)KQL result count5–15 minutes typicalError rates, security patterns, cross-table correlation
Activity log alertControl plane eventsNear real-timeDeletes, RBAC changes, policy violations
Smart detectionApp Insights anomaliesContinuousFailure-rate spikes, dependency degradation

Metric alerts excel when the condition is a single numeric threshold on platform or custom metrics. They evaluate frequently and cost less per evaluation than scanning gigabytes of logs. Log alerts excel when the condition requires text, joins, or business logic—“more than 5% of checkout requests returned HTTP 500 in the last 15 minutes” is natural KQL, awkward as a raw metric. Expect log alerts to run on a slower cadence because each evaluation scans workspace data.

Dynamic thresholds on metric alerts use machine learning to learn seasonal baselines instead of a fixed “CPU > 85%.” They help workloads with daily cycles (batch jobs, business-hours traffic) but still need tuning for brand-new services with little history. Multi-resource metric alerts apply one rule to many VMs or databases in a scope, which reduces rule sprawl when you operate fleet-wide SRE policies.

Action groups, suppression, and alert processing rules

Section titled “Action groups, suppression, and alert processing rules”

An action group is a named set of notification channels: email, SMS, voice, webhook (PagerDuty, Slack, Teams), Azure Functions, Logic Apps, or ITSM connectors. Reuse action groups across rules so routing changes happen in one place. Alert processing rules add schedule-based suppression (maintenance windows), deduplication behavior, or routing overrides—for example, suppress non-critical Sev 3 alerts during a planned change window while still allowing Sev 1 pages.

Log alerts should encode why the condition matters. This query fires when failed requests exceed 5% of traffic in a 15-minute window—actionable for customer-facing APIs:

AppRequests
| where TimeGenerated > ago(15m)
| summarize
Total = count(),
Failed = countif(toint(ResultCode) >= 500)
| extend FailRate = 100.0 * Failed / Total
| where Total > 100 and FailRate > 5

The Total > 100 guard prevents alerting during low-traffic periods when three errors look like 100% failure rate. Wire the scheduled query rule to the same action group as your CPU alerts so on-call sees correlated infrastructure and application signals.

Autoscale uses the same metric platform as alerts but changes capacity instead of notifying humans. VM scale sets, App Service plans, and other scalable resources can add or remove instances when CPU, queue length, or custom metrics cross thresholds. Autoscale rules and metric alerts complement each other: autoscale handles predictable load (morning traffic), alerts catch misconfiguration (autoscale maxed out but latency still high). Test scale-out and scale-in cooldowns in staging—aggressive scale-in during a traffic spike causes flapping outages.

Terminal window
# Create an action group (email + webhook)
az monitor action-group create \
--resource-group myRG \
--name "platform-team" \
--short-name "platform" \
--email-receiver "oncall" "oncall@company.com" \
--webhook-receiver "pagerduty" "https://events.pagerduty.com/integration/xxx/enqueue"
# Create a metric alert: VM CPU > 85% for 5 minutes
VM_ID="/subscriptions/<sub>/resourceGroups/myRG/providers/Microsoft.Compute/virtualMachines/myVM"
ACTION_GROUP_ID=$(az monitor action-group show -g myRG -n platform-team --query id -o tsv)
az monitor metrics alert create \
--resource-group myRG \
--name "high-cpu-alert" \
--description "VM CPU exceeds 85% for 5 minutes" \
--severity 2 \
--scopes "$VM_ID" \
--condition "avg Percentage CPU > 85" \
--window-size 5m \
--evaluation-frequency 1m \
--action "$ACTION_GROUP_ID"
# Create a log alert: more than 10 errors in 15 minutes
WORKSPACE_ID=$(az monitor log-analytics workspace show -g myRG -n kubedojo-logs --query id -o tsv)
az monitor scheduled-query create \
--resource-group myRG \
--name "high-error-rate" \
--description "More than 10 errors in 15 minutes" \
--severity 1 \
--scopes "$WORKSPACE_ID" \
--condition "count 'Failures' > 10 resource id _ResourceId at least 1 violations out of 1 aggregated points" \
--condition-query Failures="AzureDiagnostics | where Category == 'AuditEvent' | where ResultType == 'Failure'" \
--window-size 15m \
--evaluation-frequency 5m \
--action-groups "$ACTION_GROUP_ID"
# Create an activity log alert: resource group deleted
az monitor activity-log alert create \
--resource-group myRG \
--name "rg-deleted-alert" \
--description "Resource group was deleted" \
--condition "category=Administrative and operationName=Microsoft.Resources/subscriptions/resourceGroups/delete" \
--action-group "$ACTION_GROUP_ID"

Azure Monitor alert severity runs from 0 (Critical) to 4 (Verbose). Map severities to response channels before go-live: Sev 0–1 pages on-call, Sev 2 opens a ticket, Sev 3–4 posts to chat or a dashboard only. Document runbooks linked from action group webhooks so recipients know whether to scale SQL, restart pods, or fail over. Test action groups quarterly—expired PagerDuty keys are a common reason real outages stay silent while portal shows “Fired.”

SeverityTypical signalSuggested channel
0–1Customer-facing outage, data loss riskPagerDuty / phone / SMS
2Degraded SLO, capacity near limitEmail + ticket system
3Early warning, non-customer impactTeams / Slack webhook
4Informational trendDashboard only, no page

Before declaring an Azure workload production-ready, verify observability end to end. This checklist prevents the “metrics exist but nobody gets paged” failure mode from the module opener scenario.

AreaMinimum barVerification
Platform metricsCPU, memory, disk, or service-specific SLO metrics chartedMetrics Explorer shows 24h baseline
Diagnostic settingsData-plane logs for SQL, KV, Storage, App ServiceAzureDiagnostics or resource-specific tables receive rows
WorkspaceOne workspace per environment, retention documentedaz monitor log-analytics workspace show
Agent / DCRAMA on VMs, Container Insights on AKS if applicableHeartbeat or ContainerLogV2 rows in last 15m
App InsightsWorkspace-based component linked, sampling configuredAppRequests shows traffic after deploy
AlertsMetric alerts for capacity; log alert for error rateTest fire to action group in lower environment
Action groupsRoutes per severity, no orphaned rulesPortal shows action on every enabled rule
Cost guardsTable plans for verbose logs, no DEBUG fleet-wideUsage blade reviewed weekly first month
RunbooksLink from alert payload or wikiOn-call drill completes in under 15 minutes

Hypothetical scenario: a team ships AKS without Container Insights but installs Prometheus only for their own squad. During an outage, platform engineers cannot see pod restart loops in the shared workspace while application engineers see healthy HTTP metrics on the ingress. The checklist forces shared visibility before merge to main.


Application Insights is the APM layer of Azure Monitor. It adds application context that infrastructure metrics alone cannot provide: HTTP requests, dependency calls (SQL, HTTP APIs, Azure services), exceptions, traces, and custom events. When a user reports a slow checkout, App Insights lets you follow one operation ID across the API gateway, order service, and payment service instead of guessing which tier failed.

Modern Application Insights resources are workspace-based: telemetry is stored in tables such as AppRequests, AppDependencies, and AppExceptions inside a linked Log Analytics workspace. Billing for ingestion and retention follows the workspace pricing model, including commitment tiers and table plans. Classic Application Insights resources still exist in older deployments; they bill ingestion separately and cannot use workspace commitment tiers. New designs should always create workspace-based components and link them explicitly to a production or non-production workspace per environment.

Distributed tracing, correlation, and the Application Map

Section titled “Distributed tracing, correlation, and the Application Map”

Application Insights implements distributed tracing using the W3C traceparent standard. Each incoming request receives an operation ID; outbound calls propagate that context so dependencies appear under the same end-to-end transaction. The Application Map visualizes services as nodes and calls as edges, highlighting failure rates and latency between components. Live Metrics streams near-real-time server health (request rate, failures, CPU) without waiting for full ingestion—useful during a deploy or load test, though it is not a substitute for retained logs.

Instrumentation: SDK, auto-instrumentation, and OpenTelemetry

Section titled “Instrumentation: SDK, auto-instrumentation, and OpenTelemetry”

You have three common instrumentation paths. Auto-instrumentation (Azure App Service, Container Apps, Functions when you set APPLICATIONINSIGHTS_CONNECTION_STRING) captures requests and dependencies with minimal code changes. Application Insights SDKs (.NET, Java, Node.js, Python) give finer control over telemetry initializers, custom dimensions, and sampling configuration. OpenTelemetry with the Azure Monitor OpenTelemetry Distro is the long-term standards-based path: export OTLP to the same workspace-backed tables without locking into a single vendor SDK. Use auto-instrumentation for speed in platform-hosted apps; use OpenTelemetry when you run polyglot microservices or need portable instrumentation across clouds.

High-traffic applications can generate enormous telemetry volume. Adaptive sampling reduces ingestion while preserving errors and slow requests. Fixed sampling drops a percentage of successful telemetry. Without sampling, verbose INFO logging plus full dependency tracking on every request can dominate workspace ingestion charges—especially when every pod in a large AKS fleet emits identical successful calls. Tune sampling in staging with realistic load before production cutover.

sequenceDiagram
participant C as Client Browser
participant A as API GW Service
participant O as Order Service
participant P as Payment Service
Note over C, P: Shared Request ID (Operation ID): abc-123
C->>A: HTTP Request
A->>O: Forward Request
O->>P: Process Payment
Note over C, P: Application Insights traces the entire request across all services using this correlation ID
Terminal window
# Create Application Insights resource
az monitor app-insights component create \
--app kubedojo-app-insights \
--resource-group myRG \
--location eastus2 \
--kind web \
--application-type web \
--workspace "$WORKSPACE_ID"
# Get the instrumentation key and connection string
az monitor app-insights component show \
--app kubedojo-app-insights -g myRG \
--query '{InstrumentationKey:instrumentationKey, ConnectionString:connectionString}' -o json

Most Azure SDKs and popular frameworks can auto-instrument when you set the APPLICATIONINSIGHTS_CONNECTION_STRING environment variable, so you do not need to manually wire tracing into every endpoint first. Once set, the platform can automatically generate request, dependency, and exception telemetry for each component in a workflow, which makes correlation much easier than hand-built log markers. In this model, you are not waiting to build custom tracing first—you are standardizing on a shared context model and only then refining what to collect for your specific service behaviors.

For Kubernetes, install the OpenTelemetry Collector or enable codeless attachment where supported so pod telemetry reaches the same workspace as VM and platform logs. Consistent cloud_RoleName and cloud_RoleInstance dimensions keep the Application Map readable when replica counts change during autoscale events.

Terminal window
# Configure App Insights on a Container App
az containerapp update \
--resource-group myRG \
--name my-app \
--set-env-vars "APPLICATIONINSIGHTS_CONNECTION_STRING=$APPINSIGHTS_CONN_STRING"

Availability tests probe public URLs or private endpoints from Azure regions on a schedule. They emit metrics and alerts when synthetic transactions fail—ideal for “can users reach the site?” checks that infrastructure metrics miss (DNS, TLS, CDN, third-party SaaS). Combine synthetic tests with server-side App Requests so you distinguish global network issues from application regressions.

Smart detection rules in Application Insights use ML on failure rates, dependency duration, and performance anomalies. They complement explicit alert rules when you lack baseline history. Smart detection does not replace log-based error-rate alerts for contractual SLOs; it surfaces unknown-unknown patterns during early rollout.

// Request performance by endpoint (last hour)
AppRequests
| where TimeGenerated > ago(1h)
| summarize
AvgDuration = avg(DurationMs),
P95 = percentile(DurationMs, 95),
FailRate = countif(Success == false) * 100.0 / count(),
Total = count()
by Name
| order by Total desc
// Dependency call failures (DB, HTTP, etc.)
AppDependencies
| where TimeGenerated > ago(1h)
| where Success == false
| summarize FailCount = count() by Target, DependencyType, ResultCode
| order by FailCount desc
// End-to-end transaction search
AppRequests
| where OperationId == "abc123"
| union AppDependencies | where OperationId == "abc123"
| union AppExceptions | where OperationId == "abc123"
| project TimeGenerated, ItemType = Type, Name, DurationMs, Success, ResultCode
| order by TimeGenerated asc

When App Insights shows elevated failures, walk this sequence before scaling infrastructure blindly. First, open the Application Map to see which dependency turned red. Second, run AppExceptions grouped by ProblemId for stack signature. Third, filter AppDependencies where Success == false for the failing tier. Fourth, check platform metrics (SQL DTU, App Service CPU) for the same timestamp. Fifth, pull resource logs via AzureDiagnostics if the dependency is Azure PaaS. This order moves from user symptom to code path to infrastructure constraint without duplicating work across tools.


The Azure Monitor Agent replaces the legacy Log Analytics Agent (MMA/OMS). It collects performance counters, syslog, Windows events, and custom logs from VMs, then forwards data according to Data Collection Rules. Configuration is policy-driven: security teams publish DCRs for auth syslog, platform teams publish DCRs for perf counters, and associations attach rules to VM scale sets, Arc-enabled servers, and standalone VMs without reinstalling agents.

AMA supports managed identity for authentication to ingestion endpoints, multiple destinations (workspace plus metrics), and transformations at collection time. The legacy MMA agent cannot use modern table plans or transformations—migrate during any VM refresh project. On Azure Kubernetes Service, container logs often flow via Container Insights (ContainerLogV2) rather than per-node AMA, but node-level syslog and perf counters still use AMA on the underlying nodes when required for host-level debugging.

VM insights adds curated performance, dependency, and health views for virtual machines when AMA and the right DCRs are associated. Container Insights monitors AKS and Arc-enabled Kubernetes with Prometheus metrics, container logs, and Kubernetes events funneled to the workspace. Enable these solutions when you operate fleets—raw tables alone overwhelm engineers who need opinionated dashboards during incidents.

Terminal window
# Install Azure Monitor Agent on a Linux VM
az vm extension set \
--resource-group myRG \
--vm-name myVM \
--name AzureMonitorLinuxAgent \
--publisher Microsoft.Azure.Monitor \
--enable-auto-upgrade true
# Create a Data Collection Rule (DCR) - defines what to collect
az monitor data-collection rule create \
--resource-group myRG \
--name "vm-performance-rule" \
--location eastus2 \
--data-flows '[{
"streams": ["Microsoft-Perf", "Microsoft-Syslog"],
"destinations": ["logAnalyticsWorkspace"]
}]' \
--destinations '{
"logAnalytics": [{
"workspaceResourceId": "'$WORKSPACE_ID'",
"name": "logAnalyticsWorkspace"
}]
}' \
--data-sources '{
"performanceCounters": [{
"name": "perfCounters",
"streams": ["Microsoft-Perf"],
"samplingFrequencyInSeconds": 60,
"counterSpecifiers": [
"\\Processor(_Total)\\% Processor Time",
"\\Memory\\Available MBytes Memory",
"\\LogicalDisk(_Total)\\% Free Space",
"\\LogicalDisk(_Total)\\Disk Reads/sec",
"\\LogicalDisk(_Total)\\Disk Writes/sec"
]
}],
"syslog": [{
"name": "syslogDataSource",
"streams": ["Microsoft-Syslog"],
"facilityNames": ["auth", "authpriv", "cron", "daemon", "kern"],
"logLevels": ["Warning", "Error", "Critical", "Alert", "Emergency"]
}]
}'
# Associate the DCR with the VM
DCR_ID=$(az monitor data-collection rule show -g myRG -n vm-performance-rule --query id -o tsv)
az monitor data-collection rule association create \
--name "vm-dcr-association" \
--resource "/subscriptions/<sub>/resourceGroups/myRG/providers/Microsoft.Compute/virtualMachines/myVM" \
--rule-id "$DCR_ID"

For virtual machine scale sets, associate the DCR at the scale set resource so new instances inherit collection automatically. Azure Arc-enabled servers outside Azure use the same AMA package with Arc connectivity—hybrid fleets benefit from one DCR model for on-premises and cloud. Test DCR changes on a canary instance before fleet-wide association; a misconfigured syslog facility can double ingestion overnight.


Cost lens: ingestion, retention, and alert economics

Section titled “Cost lens: ingestion, retention, and alert economics”

Monitoring spend is rarely a single line item. It accumulates across Log Analytics ingestion, retention beyond included periods, Application Insights telemetry, metric alert evaluations, and data you never meant to collect. Understanding the knobs prevents observability from becoming an unbounded tax.

Log Analytics ingestion and commitment tiers

Section titled “Log Analytics ingestion and commitment tiers”

Log Analytics bills ingested data volume in gigabytes per workspace. Pay-as-you-go is the default; commitment tiers commit to a daily ingestion volume (starting at 100 GB/day) at a lower per-GB rate when sustained volume justifies it. Billable size is based on the string representation of columns stored, not the raw JSON payload—Microsoft documents that billed volume is often substantially smaller than incoming event size for Analytics tables, but verbose custom fields still add up. Use the workspace Usage and estimated costs blade to compare pay-as-you-go versus commitment before you lock a 31-day commitment period.

The first 5 GB per billing account per month are free for Log Analytics ingestion (regional pricing applies beyond that—check Azure Monitor pricing for your region). Auxiliary and Basic table plans reduce ingestion cost for tables that do not need full interactive analytics; query charges apply when you scan Basic or Auxiliary data interactively.

Default interactive retention is commonly 30 days for many tables (90 days for some Microsoft Sentinel and Application Insights scenarios). Extending retention or using long-term retention adds monthly charges per GB retained. Purging data does not retroactively reduce retention billing—you must lower retention settings. Archive expensive verbose tables to Auxiliary with explicit total retention for compliance instead of keeping debug logs on Analytics for a year.

Application Insights and sampling as a cost control

Section titled “Application Insights and sampling as a cost control”

Workspace-based Application Insights stores all telemetry in linked workspace tables, so App Insights ingestion is workspace ingestion. Adaptive and fixed sampling is the primary cost lever for high-traffic apps: drop redundant successful requests while retaining errors and outliers. Live Metrics Stream does not add ingestion volume for retained data, but it does not replace historical analysis either.

Metric alerts, log alerts, and hidden volume drivers

Section titled “Metric alerts, log alerts, and hidden volume drivers”

Metric alerts bill per rule and can bill per monitored time series when dimensions multiply scopes. Log alerts bill based on scanned data during each evaluation—wide time ranges and unfiltered tables increase cost and latency. The silent budget killer is verbose logging: DEBUG-level application logs, legacy NSG flow logs (retiring 2027-09-30—prefer VNet flow logs), and full HTTP body logging can push a single VM from a few gigabytes per month to tens of gigabytes. Pair diagnostic settings with table plan selection and ingestion transforms to drop noise before it lands in Analytics tables.

Cost driverWhat spikes the billMitigation
Analytics ingestionVNet/NSG flow logs, verbose app logs, all pods at DEBUGBasic/Auxiliary plans, transforms, log level discipline
Retention365+ day retention on high-volume tablesPer-table retention, long-term tier, export to Storage
App InsightsHigh RPS without samplingAdaptive sampling, filter health-check traffic
Log alertsBroad KQL every 5 minutesTight TimeGenerated filters, metric alerts where possible
Metric alertsHundreds of dimensional time seriesConsolidate multi-resource rules, reduce dimensions
Daily cap misconfigurationIngestion stops silently when cap hitSet caps only in dev/test; use alerts on usage meters in prod

Use the workspace Usage and estimated costs view and Analyze usage in a Log Analytics workspace to identify top tables before enabling VNet flow logs or DEBUG logging fleet-wide. Multiply per-VM estimates by instance count: a modest 2 GB/month per VM becomes 200 GB/month at 100 VMs. Commitment tiers help only when sustained volume is predictable—spiky debug logging does not fit a fixed daily commit.


Decision Framework: metrics, logs, destinations, and alerts

Section titled “Decision Framework: metrics, logs, destinations, and alerts”

Operators need a decision path because “turn on monitoring” is too vague. Start from the question you must answer in an incident, then pick the data plane and destination.

start
|
v
Need sub-minute numeric threshold on a resource metric?
| yes -> Metric alert + Metrics Explorer
| no
v
Need text, joins, or % error rate over requests?
| yes -> Log Analytics table + log alert (scheduled query)
| no
v
Need who changed Azure control plane (delete, RBAC)?
| yes -> Activity log alert on AzureActivity
| no
v
Need retained queryable ops data in one place?
| yes -> Diagnostic settings -> Log Analytics workspace
| no
v
Need cheap multi-year archive or SIEM stream?
| yes -> Diagnostic settings -> Storage and/or Event Hub
| no -> Revisit requirement (metrics-only may suffice)
DecisionChooseTradeoff
Fast threshold on CPU, DTU, latencyMetric alertMisses log-only context; cheap evaluation
“>5% HTTP 500 in 15m” with volume guardLog alert on AppRequestsSlower cadence; scans workspace GB
Compliance archive, not daily KQLDiagnostic settings → StorageCheap retention; not interactive in Log Analytics
Real-time export to Splunk/SentinelDiagnostic settings → Event HubStreaming cost + consumer ops
High-volume debug logsAuxiliary or Basic table planLower ingest; query/alert limits on Auxiliary
Fleet-wide CPU policyMulti-resource metric alertOne rule; dimension explosion still costs
Signal needFirst instrumentationWhy
App Service / Functions / ACAConnection string auto-instrumentationFastest path to requests and dependencies
Custom business eventsSDK or OpenTelemetry custom spansDimensions you control
Polyglot Kubernetes microservicesOpenTelemetry distro → workspacePortable across languages
VM syslog and perf countersAzure Monitor Agent + DCRPolicy-driven, no legacy MMA

PatternWhen to use itWhy it worksScaling note
One workspace per environmentDev, staging, prod separationCross-resource KQL without mixing blast radiusUse RBAC and table-level access for teams
Diagnostic settings on day oneAll critical data-plane resourcesMetrics alone miss audit and query textEnforce with Azure Policy at scale
Metric alerts for SLO gatesLatency, availability, CPU, DTUSub-minute evaluation, predictable costAdd dynamic thresholds for seasonal workloads
Log alerts for error-rate logicHTTP 5xx %, auth failuresExpresses business conditionsAlways filter TimeGenerated and minimum volume
Workspace-based App InsightsNew applicationsUnified billing and KQL across infra + appsLink prod workspace only to prod components
Adaptive sampling in productionHigh RPS servicesCuts ingest while keeping failuresValidate in load test before go-live
Anti-patternWhat goes wrongBetter approach
DEBUG logging everywhere in productionIngestion dominates cloud billINFO in prod; DEBUG only on sampled instances
Log alert without time filterScans entire table history; slow and costlyStart every query with where TimeGenerated > ago(...)
One workspace per resourceCannot correlate App + SQL + KV in one KQLCentralize per environment with RBAC
Alerts with no action groupIncidents visible only in portalAttach shared action group per severity tier
Classic App Insights for new appsMisses commitment tiers and table plansWorkspace-based component linked to LAW
All diagnostics to Analytics onlyPaying interactive ingest for archive-only dataRoute verbose streams to Auxiliary or Storage
Static CPU 50% on bursty VMsAlert fatigue; ignored pagesRaise threshold, longer window, or dynamic band

Governance with Azure Policy and automation

Section titled “Governance with Azure Policy and automation”

At scale, manual portal configuration drifts. Azure Policy initiatives for monitoring deploy diagnostic settings, deploy AMA with DCR associations, and enforce tagging that KQL relies on. Pair policy with remediation tasks so existing resources backfill telemetry without a human opening each blade. Logic Apps triggered from action groups can open ServiceNow tickets, post to Teams, or run safe automation (scale out, restart app) when alerts fire—keep automation idempotent and bounded so flapping alerts do not loop scale operations.

Export selected tables to Storage or Event Hub when compliance requires immutable copies outside the workspace. Data export rules run continuously per table; test export filters so you do not duplicate entire workspace firehoses into storage accounts without lifecycle policies.


  1. Log Analytics ingestion is priced per GB ingested (regional rates on the Azure Monitor pricing page), but the first 5 GB per billing account per month are free. A single VM running the Azure Monitor Agent with default performance counters and syslog collection generates approximately 1-3 GB of log data per month. However, enabling verbose application logging or NSG flow logs can push a single VM to 10+ GB per month. Always estimate your ingestion volume before enabling logging on production fleets.

  2. KQL was designed by the same team that built Azure Data Explorer (Kusto), and it processes over 15 exabytes of data per day across Microsoft’s internal telemetry systems. The same language is used in Microsoft Sentinel (SIEM), Azure Monitor, Azure Data Explorer, and Microsoft 365 Defender. Learning KQL is one of the highest-ROI skills for any Azure engineer.

  3. Azure Monitor can detect anomalies automatically using machine learning. The series_decompose_anomalies() KQL function analyzes time-series data and flags data points that deviate significantly from the expected pattern. You do not need to set static thresholds---the model learns what “normal” looks like and alerts on deviations. This catches issues like “response time gradually increased 40% over 3 days” that static thresholds miss.

  4. Application Insights sampling reduces data volume without losing visibility. By default, adaptive sampling kicks in when your application generates more than 5 events per second per server. It intelligently drops redundant telemetry (like 1000 identical successful requests) while preserving anomalies (errors, slow requests). High-traffic estates often cut App Insights ingestion by 80–90% with adaptive sampling while retaining errors and slow requests for diagnosis.


MistakeWhy It HappensHow to Fix It
Not setting up monitoring until after an incident”We will add monitoring later” is the most expensive sentence in engineeringSet up baseline monitoring (CPU, memory, disk, response time) and alerts on day one, before the application goes to production.
Creating one Log Analytics workspace per resourceMisunderstanding of workspace purposeUse one workspace per environment (dev, staging, prod) or per team. Multiple resources send logs to the same workspace. Separate workspaces fragment your data and make cross-resource queries much harder.
Setting alert thresholds too aggressively (e.g., CPU > 50%)Teams want to catch problems earlyAggressive thresholds cause alert fatigue. The team ignores alerts, and real problems are missed. Set thresholds at actionable levels (CPU > 85%, not 50%).
Not configuring action groups on alertsAlerts are created but nobody receives themEvery alert must have an action group. At minimum, email the on-call team. Better: integrate with PagerDuty/Opsgenie for proper incident management.
Using the legacy Log Analytics Agent (MMA) instead of Azure Monitor AgentMMA appears in older documentation and tutorialsMMA is deprecated. Always use the Azure Monitor Agent (AMA) with Data Collection Rules. AMA supports identity-based auth, multiple workspaces, and data transformation.
Writing KQL queries that scan entire tables without time filtersThe query “works” but takes 30 secondsAlways start KQL queries with a where TimeGenerated > ago(...) filter. Log Analytics partitions data by time, so time filters dramatically reduce scan volume and cost.
Not enabling diagnostic settings on critical resourcesDiagnostic settings are not enabled by defaultUse Azure Policy to enforce diagnostic settings across all resources. At minimum, enable them on Key Vault, SQL, storage accounts, and networking resources.
Logging sensitive data (passwords, tokens, PII) to Log AnalyticsApplications log full request/response bodies for debuggingImplement log scrubbing. Never log request bodies, authorization headers, or user PII. Application Insights allows custom telemetry processors to redact sensitive fields before ingestion.

1. Your team is building a new microservice and wants to be notified when the HTTP 500 error rate exceeds 5% for fifteen minutes. They also need to search exact error messages and stack traces from failing requests. How should you use Azure Monitor metrics and logs together?

Use a log alert (scheduled query) on AppRequests for the error-rate condition because percentage-of-traffic logic needs KQL, not a single platform metric. Include a minimum request count guard so low traffic does not false-positive. Evaluation every five to fifteen minutes matches log ingestion latency. For root cause, query the same workspace: AppExceptions for stack traces and AppRequests | where Success == false for URLs and result codes. Platform metrics (CPU, memory) complement App Insights when failures correlate with infrastructure saturation. Metric alerts remain appropriate for pure infrastructure thresholds (DTU, disk queue) where no log query is required.

2. A critical production storage account was unexpectedly deleted sometime during the past weekend, causing an application outage. Your manager has asked you to figure out exactly who deleted the resource and when the deletion occurred. Write a KQL query that retrieves this information from the control plane logs for the last 7 days.
AzureActivity
| where TimeGenerated > ago(7d)
| where OperationNameValue endswith "delete"
| where ActivityStatusValue == "Success"
| project
TimeGenerated,
Caller,
ResourceGroup,
ResourceType = tostring(split(ResourceId, "/")[-2]),
ResourceName = tostring(split(ResourceId, "/")[-1]),
OperationName = OperationNameValue
| order by TimeGenerated desc

This query targets the AzureActivity table, which records all control plane operations (like creations, updates, and deletions) across your Azure subscription. By filtering for operations ending with “delete” and a “Success” status, you isolate the exact events where resources were removed. The project operator then extracts only the most relevant fields—specifically the Caller (which identifies the user or service principal) and the precise TimeGenerated—allowing you to quickly answer who performed the action and when. It sorts the output by time in descending order to surface the most recent deletions first. By querying this centralized control plane log rather than resource-specific logs, you can guarantee visibility even after the resource itself is gone.

3. A junior engineer on your team suggests that to keep things organized, you should create a separate Log Analytics workspace for every single Azure App Service and SQL Database in your production environment (over 50 workspaces total). Why is this a problematic architectural decision?

Creating a separate workspace for every resource severely fragments your log data and breaks your ability to perform end-to-end tracing. When a user request fails, you often need to correlate logs from the frontend App Service with logs from the backend SQL Database. If those logs are in different workspaces, writing a single KQL query to join them becomes significantly harder, slower, and more expensive. The recommended best practice is to centralize logs into a single workspace per environment (e.g., one for production, one for staging) and use Role-Based Access Control (RBAC) to restrict who can query specific tables or resources.

4. You have a fleet of 100 Linux VMs running the Azure Monitor Agent. The security team wants to collect all `auth` and `kern` syslog events, while the platform team only wants to collect CPU and Memory performance counters. How can you configure the agents to satisfy both teams without installing two different monitoring agents?

You can satisfy both teams by using a Data Collection Rule (DCR) in Azure Monitor. A DCR is a centralized configuration resource that defines exactly what data should be collected (like performance counters or syslog facilities), how it should be transformed, and to which Log Analytics workspace it should be routed. Instead of configuring each VM’s agent individually, you create one or more DCRs defining the security and platform requirements, and then associate those rules with your 100 VMs. The Azure Monitor Agent will automatically download the rules and begin collecting only the specified data streams, keeping configuration consistent and easy to manage centrally.

5. Since deploying a new application, your on-call engineers are receiving over 50 alert emails per day. Most of these alerts trigger when a VM's CPU exceeds 50% for a single minute, but the CPU drops back to 20% almost immediately. The engineers have started ignoring all monitoring emails. How should you redesign the alerting strategy to fix this?

You need to redesign the strategy to eliminate alert fatigue by adjusting both the thresholds and the evaluation windows. First, a 50% CPU spike for one minute is normal behavior for many applications; you should raise the threshold to an actionable level (e.g., 85%) and increase the window size so the alert only fires if the CPU remains elevated for 5 or 10 consecutive minutes. Second, you should route alerts based on severity: transient or non-critical issues (Sev 3/4) should be logged to a dashboard or chat channel, while only critical, user-impacting issues (Sev 0/1) should trigger emails or wake up an engineer via PagerDuty. Finally, you might consider using dynamic thresholds that rely on machine learning to alert only when the CPU deviates significantly from its historical baseline.

6. Your architecture consists of a frontend web app, an API gateway, and three backend microservices. A user reports that when they click "Checkout", the page spins for 10 seconds and then fails. You need to figure out which specific backend microservice is causing the delay. How does Application Insights track this single checkout request across all five components?

Application Insights tracks the request across multiple services by implementing distributed tracing based on the W3C Trace Context standard. When the user clicks “Checkout”, the first component (the frontend web app) generates a unique Operation ID for that specific transaction. This Operation ID is automatically injected into the HTTP headers (such as traceparent) of all subsequent outbound calls to the API gateway and backend microservices. Because every service logs its telemetry (requests, dependencies, exceptions) using that exact same Operation ID, you can query Application Insights for that ID to visualize the entire end-to-end transaction, instantly identifying which specific microservice took 10 seconds to respond.

7. Your security team needs a seven-year audit trail of Key Vault access events for compliance, but the SRE team only queries those logs during incidents (a few times per month). Diagnostic settings can send logs to a Log Analytics workspace, Storage, or Event Hub. How do you balance cost and queryability?

Send operational copies to a Log Analytics workspace on an appropriate table plan so SRE can run KQL during incidents—use Basic or Auxiliary for high-volume audit streams if interactive analytics are rare. Send a parallel export to Azure Storage via diagnostic settings for low-cost long-term retention that satisfies compliance archive requirements. Storage is cheaper for years of retained JSON but is not queryable with KQL until you rehydrate or use external tools. Event Hub is optional if a SIEM consumes the stream in real time. The mistake to avoid is keeping seven years of verbose audit data in Analytics tables with default interactive retention, which inflates both ingestion and retention charges without daily operational value.

8. Production App Insights ingestion jumped 400% after a release even though traffic only rose 10%. Developers enabled verbose dependency tracking and DEBUG logging on every microservice. What three changes reduce cost without blinding on-call to real failures?

First, enable adaptive or fixed sampling so successful high-volume requests are represented statistically while errors and slow dependencies are retained. Second, lower log verbosity to INFO in production and restrict DEBUG to short-lived troubleshooting on a single instance. Third, route verbose container stdout to Auxiliary tables (or filter with ingestion transforms) instead of Analytics tables used for alerting. Metric and log alerts on error rate and latency should remain on Analytics or AppRequests tables so customer-impacting failures still page on-call. Validate changes in staging under load so sampling does not hide rare failure modes.


Hands-On Exercise: Monitor Agent on VM with Custom Logs, KQL Query, and Email Alert

Section titled “Hands-On Exercise: Monitor Agent on VM with Custom Logs, KQL Query, and Email Alert”

In this exercise, you deploy a VM with the Azure Monitor Agent, collect performance data and syslog, write KQL queries, and create a metric alert with an email action group. The lab mirrors production patterns: workspace first, agent second, DCR third, validation fourth, alerts last. Skipping validation and jumping straight to alerts is how teams learn the hard way that rules fired on empty data.

Prerequisites: Azure CLI installed and authenticated. Confirm subscription, tenant, and permissions to create resource groups, VMs, Log Analytics workspaces, DCRs, and action groups. Ingestion delay is typically three to eight minutes—budget time in Task 4. Use a disposable email in the action group or expect messages when the CPU stress test triggers the alert.

Conceptual mapping: Task 1 establishes the Log Analytics workspace (logs plane). Task 2–3 configure AMA and DCR (ingestion pipe). Task 4 proves data in Perf and Heartbeat. Task 5 attaches a metric alert (action plane) separate from log queries in Task 6. In production you would add diagnostic settings on PaaS resources and workspace-based App Insights for applications; this lab isolates VM collection so each step stays visible.

Terminal window
RG="kubedojo-monitor-lab"
LOCATION="eastus2"
az group create --name "$RG" --location "$LOCATION"
# Create Log Analytics workspace
az monitor log-analytics workspace create \
--resource-group "$RG" \
--workspace-name monitor-lab-logs \
--location "$LOCATION" \
--retention-time 30
WORKSPACE_ID=$(az monitor log-analytics workspace show \
-g "$RG" -n monitor-lab-logs --query id -o tsv)
WORKSPACE_CUSTOMER_ID=$(az monitor log-analytics workspace show \
-g "$RG" -n monitor-lab-logs --query customerId -o tsv)
Verify Task 1
Terminal window
az monitor log-analytics workspace show -g "$RG" -n monitor-lab-logs \
--query '{Name:name, ID:customerId, Retention:retentionInDays}' -o table

Task 2: Deploy a VM with Azure Monitor Agent

Section titled “Task 2: Deploy a VM with Azure Monitor Agent”
Terminal window
# Create a VM
az vm create \
--resource-group "$RG" \
--name monitor-lab-vm \
--image Ubuntu2204 \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--assign-identity '[system]'
# Install Azure Monitor Agent
az vm extension set \
--resource-group "$RG" \
--vm-name monitor-lab-vm \
--name AzureMonitorLinuxAgent \
--publisher Microsoft.Azure.Monitor \
--enable-auto-upgrade true
Verify Task 2
Terminal window
az vm extension list -g "$RG" --vm-name monitor-lab-vm \
--query '[].{Name:name, Publisher:publisher, State:provisioningState}' -o table

You should see AzureMonitorLinuxAgent with Succeeded state.

Terminal window
az monitor data-collection rule create \
--resource-group "$RG" \
--name "vm-perf-syslog-rule" \
--location "$LOCATION" \
--data-flows '[{
"streams": ["Microsoft-Perf"],
"destinations": ["logAnalytics"]
}, {
"streams": ["Microsoft-Syslog"],
"destinations": ["logAnalytics"]
}]' \
--destinations "{
\"logAnalytics\": [{
\"workspaceResourceId\": \"$WORKSPACE_ID\",
\"name\": \"logAnalytics\"
}]
}" \
--data-sources '{
"performanceCounters": [{
"name": "perfCounters",
"streams": ["Microsoft-Perf"],
"samplingFrequencyInSeconds": 30,
"counterSpecifiers": [
"\\Processor(_Total)\\% Processor Time",
"\\Memory\\Available MBytes Memory",
"\\Memory\\% Used Memory",
"\\LogicalDisk(_Total)\\% Free Space"
]
}],
"syslog": [{
"name": "syslog",
"streams": ["Microsoft-Syslog"],
"facilityNames": ["auth", "authpriv", "daemon", "kern"],
"logLevels": ["Warning", "Error", "Critical", "Alert", "Emergency"]
}]
}'
# Associate DCR with the VM
DCR_ID=$(az monitor data-collection rule show -g "$RG" -n vm-perf-syslog-rule --query id -o tsv)
VM_ID=$(az vm show -g "$RG" -n monitor-lab-vm --query id -o tsv)
az monitor data-collection rule association create \
--name "vm-dcr-assoc" \
--resource "$VM_ID" \
--rule-id "$DCR_ID"
Verify Task 3
Terminal window
az monitor data-collection rule association list --resource "$VM_ID" \
--query '[].{Name:name, RuleId:dataCollectionRuleId}' -o table
Terminal window
# Generate CPU load (stress-ng is not preinstalled on Ubuntu 22.04 images)
az vm run-command invoke -g "$RG" -n monitor-lab-vm \
--command-id RunShellScript \
--scripts "sudo apt-get update -qq && sudo apt-get install -y stress-ng && stress-ng --cpu 2 --timeout 120"
# Fallback if apt fails: yes-loop CPU burn
az vm run-command invoke -g "$RG" -n monitor-lab-vm \
--command-id RunShellScript \
--scripts "for i in \$(seq 1 4); do yes > /dev/null & done; sleep 120; kill \$(jobs -p) 2>/dev/null; true"
echo "Generating CPU load... Waiting 5 minutes for data to appear in Log Analytics."
echo "(Data ingestion typically takes 3-8 minutes)"
sleep 300
Verify Task 4
Terminal window
# Query Log Analytics for performance data
az monitor log-analytics query \
--workspace "$WORKSPACE_CUSTOMER_ID" \
--analytics-query "Perf | where TimeGenerated > ago(10m) | where Computer contains 'monitor-lab' | summarize avg(CounterValue) by CounterName | order by CounterName asc" \
--output table

You should see CPU and memory counter data.

Task 5: Create an Alert Rule with Action Group

Section titled “Task 5: Create an Alert Rule with Action Group”
Terminal window
# Create an action group (email notification)
az monitor action-group create \
--resource-group "$RG" \
--name "lab-alerts" \
--short-name "labalerts" \
--email-receiver "lab-oncall" "your-email@example.com"
# Create a metric alert for high CPU
ACTION_GROUP_ID=$(az monitor action-group show -g "$RG" -n lab-alerts --query id -o tsv)
az monitor metrics alert create \
--resource-group "$RG" \
--name "vm-high-cpu" \
--description "Alert when CPU exceeds 80% for 5 minutes" \
--severity 2 \
--scopes "$VM_ID" \
--condition "avg Percentage CPU > 80" \
--window-size 5m \
--evaluation-frequency 1m \
--action "$ACTION_GROUP_ID"
Verify Task 5
Terminal window
az monitor metrics alert show -g "$RG" -n vm-high-cpu \
--query '{Name:name, Severity:severity, Enabled:enabled, Condition:criteria.allOf[0].{Metric:metricName, Operator:operator, Threshold:threshold}}' -o json

After data appears, practice the investigation pattern you will use in production: start with Heartbeat to prove the agent path, use Perf for resource saturation, then add application tables when App Insights is linked. Save useful queries as workspace saved searches or workbook steps so on-call does not rewrite KQL under stress.

Terminal window
# Query 1: Average CPU over the last 10 minutes
az monitor log-analytics query \
--workspace "$WORKSPACE_CUSTOMER_ID" \
--analytics-query "
Perf
| where TimeGenerated > ago(10m)
| where CounterName == '% Processor Time'
| where InstanceName == '_Total'
| summarize AvgCPU = avg(CounterValue), MaxCPU = max(CounterValue) by bin(TimeGenerated, 1m)
| order by TimeGenerated desc
" --output table
# Query 2: Memory usage
az monitor log-analytics query \
--workspace "$WORKSPACE_CUSTOMER_ID" \
--analytics-query "
Perf
| where TimeGenerated > ago(10m)
| where CounterName == '% Used Memory'
| summarize AvgMemory = avg(CounterValue) by bin(TimeGenerated, 1m)
| order by TimeGenerated desc
" --output table
# Query 3: Agent heartbeat (proves the agent is reporting)
az monitor log-analytics query \
--workspace "$WORKSPACE_CUSTOMER_ID" \
--analytics-query "
Heartbeat
| where TimeGenerated > ago(10m)
| summarize LastHeartbeat = max(TimeGenerated) by Computer, OSType, Version
" --output table
Verify Task 6

All three queries should return data. If they are empty, wait another 5 minutes for data ingestion and retry. The Heartbeat query is the most reliable indicator that the agent is working.

Terminal window
az group delete --name "$RG" --yes --no-wait
  • Log Analytics workspace created
  • VM deployed with Azure Monitor Agent and system-assigned managed identity
  • Data Collection Rule created and associated with the VM
  • Performance counter data visible in Log Analytics (Perf table)
  • KQL queries returning CPU, memory, and heartbeat data
  • Metric alert created with email action group

Link workspace-based Application Insights to the same workspace and deploy a sample web app with APPLICATIONINSIGHTS_CONNECTION_STRING set. Confirm AppRequests rows appear, then clone the HTTP 5xx log alert query from the Alerts section. Add a diagnostic setting on the lab storage account (if created) to practice routing audit logs to Storage while metrics stay in Metrics Explorer—mirroring the split destination pattern from the Decision Framework. These optional steps take another thirty to sixty minutes but cement the difference between metrics-only VM monitoring and full application observability in one workspace. Document the workspace ID, action group name, and alert rule names in your team runbook so the next engineer can extend the lab without rediscovering resource names from CLI history. When you tear down the resource group, confirm alert rules and action groups are gone so orphaned notifications do not fire against deleted VMs. That cleanup habit matters in shared lab subscriptions where forgotten alerts spam the whole team during the next teammate VM stress test in the lab.


Module 3.11: CI/CD with Azure DevOps & GitHub Actions --- Learn how to build automated deployment pipelines that securely deploy to Azure using OIDC authentication, Azure Container Registry, and Container Apps.