Why This Matters
A dashboard should be your cockpit not an art gallery. Yes, pretty graphs are nice, but you’re here for insights.
In this post, you’ll learn how to:
- Create dashboards with purpose
- Visualise data from Log Analytics and metrics
- Combine resource-level tiles with workspace insights
- Share dashboards across teams without the noise
Think of it as putting visibility on tap—because no one wants to fly blind in production.
What are Azure Monitor Dashboards?
Azure dashboards are fully customisable canvases where you can display:
- Metrics from Azure Monitor
- Log Analytics queries
- Alerts, events, and health data
- External data via markdown, HTML, or Power BI snippets
You can mix data across subscriptions and regions, and even pin charts directly from Resource Groups or workbooks.
The goal? One screen that tells you what you need to know, when you need to know it.
Hands-On: Azure Portal Steps
Step 1: Start a New Dashboard
- Go to Azure Portal → Dashboard
- Click + New dashboard
- Name it something useful (e.g.
My-Production-Ops-Dashboard
)
You’ll get a blank template, but don’t panic we’re going to fill it with useful tiles, not just eye candy.
Step 2: Add Resource Metric Charts
- Click Edit → + Add → Metrics chart
- Select a target resource (e.g., App Service, VM, Function App)
- Choose metrics to visualise (e.g., CPU %, HTTP 5xx, Disk IOPS)
- Customise time range and chart style
- Click Done Customising → Save your dashboard
🍺
Brewed Insight: Pin directly from a resource’s Metrics blade when you find a useful view that is meaningful to what you are trying to monitor.
Step 3: Add Log Analytics Query Visuals
-
Add a tile with custom logs:
- Edit dashboard → + Add tile → “Log Analytics”
-
Choose your Log Analytics workspace
-
Enter your KQL query
Example:
1
2
3
|
AzureDiagnostics
| where ResourceType == "APPLICATIONGATEWAYS"
| summarize TotalRequests = count() by bin(TimeGenerated, 1h)
|
-
Pick a visualisation (time chart, bar, grid)
-
Click Apply, and rename the tile for clarity
Step 4: Group & Share Wisely
- Use sections or naming to group tiles by resource type (e.g., Compute, Network, App Layer)
- Share via RBAC roles or link access if dashboards need broader visibility
Bicep: Dashboard as Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
|
@description('Name of the existing virtual machine to show in the dashboard')
param virtualMachineName string = 'myVM'
@description('Name of the resource group that contains the virtual machine')
param virtualMachineResourceGroup string = 'MyVMResourceGroup'
@description('Name of the Static Site to show in the dashboard')
param staticSiteName string = 'MyStaticSite'
@description('Name of the resource group that contains the static site')
param staticSiteResourceGroup string = 'MyStaticSiteRG'
@description('Subscription ID for the static site resource group')
param staticSiteSubscriptionId string = '0000-0000-0000-0000-0000'
@description('Name of the dashboard to display in Azure portal')
param dashboardDisplayName string = 'Simple Dashboard'
resource staticSite 'Microsoft.Web/staticSites@2023-12-01' existing = {
name: staticSiteName
scope: resourceGroup(staticSiteSubscriptionId,staticSiteResourceGroup)
}
resource dashboard 'Microsoft.Portal/dashboards@2020-09-01-preview' = {
name: replace(dashboardDisplayName, ' ', '-')
location: resourceGroup().location
tags: {
'hidden-title': dashboardDisplayName
}
properties: {
lenses: [
{
order: 0
parts: [
{
position: {
x: 0
y: 0
rowSpan: 3
colSpan: 11
}
metadata: {
inputs: [
{
name: 'queryInputs'
value: {
timespan: {
duration: 'PT1H'
}
id: resourceId(virtualMachineResourceGroup, 'Microsoft.Compute/virtualMachines', virtualMachineName)
chartType: 0
metrics: [
{
name: 'Percentage CPU'
resourceId: resourceId(virtualMachineResourceGroup, 'Microsoft.Compute/virtualMachines', virtualMachineName)
}
]
}
}
]
type: 'Extension/Microsoft_Azure_Monitoring/PartType/MetricsChartPart'
}
}
{
position: {
x: 0
y: 3
rowSpan: 2
colSpan: 3
}
metadata: {
inputs: [
{
name: 'queryInputs'
value: {
timespan: {
duration: 'PT1H'
}
id: resourceId(virtualMachineResourceGroup, 'Microsoft.Compute/virtualMachines', virtualMachineName)
chartType: 0
metrics: [
{
name: 'Disk Read Operations/Sec'
resourceId: resourceId(virtualMachineResourceGroup, 'Microsoft.Compute/virtualMachines', virtualMachineName)
}
{
name: 'Disk Write Operations/Sec'
resourceId: resourceId(virtualMachineResourceGroup, 'Microsoft.Compute/virtualMachines', virtualMachineName)
}
]
}
}
]
type: 'Extension/Microsoft_Azure_Monitoring/PartType/MetricsChartPart'
}
}
{
position: {
x: 3
y: 3
rowSpan: 2
colSpan: 3
}
metadata: {
inputs: [
{
name: 'queryInputs'
value: {
timespan: {
duration: 'PT1H'
}
id: resourceId(virtualMachineResourceGroup, 'Microsoft.Compute/virtualMachines', virtualMachineName)
chartType: 0
metrics: [
{
name: 'Disk Read Bytes'
resourceId: resourceId(virtualMachineResourceGroup, 'Microsoft.Compute/virtualMachines', virtualMachineName)
}
{
name: 'Disk Write Bytes'
resourceId: resourceId(virtualMachineResourceGroup, 'Microsoft.Compute/virtualMachines', virtualMachineName)
}
]
}
}
]
type: 'Extension/Microsoft_Azure_Monitoring/PartType/MetricsChartPart'
}
}
{
position: {
x: 6
y: 3
rowSpan: 2
colSpan: 3
}
metadata: {
inputs: [
{
name: 'queryInputs'
value: {
timespan: {
duration: 'PT1H'
}
id: resourceId(virtualMachineResourceGroup, 'Microsoft.Compute/virtualMachines', virtualMachineName)
chartType: 0
metrics: [
{
name: 'Network In Total'
resourceId: resourceId(virtualMachineResourceGroup, 'Microsoft.Compute/virtualMachines', virtualMachineName)
}
{
name: 'Network Out Total'
resourceId: resourceId(virtualMachineResourceGroup, 'Microsoft.Compute/virtualMachines', virtualMachineName)
}
]
}
}
]
type: 'Extension/Microsoft_Azure_Monitoring/PartType/MetricsChartPart'
}
}
{
position: {
x: 9
y: 3
rowSpan: 2
colSpan: 2
}
metadata: {
inputs: [
{
name: 'id'
value: resourceId(virtualMachineResourceGroup, 'Microsoft.Compute/virtualMachines', virtualMachineName)
}
]
type: 'Extension/Microsoft_Azure_Compute/PartType/VirtualMachinePart'
asset: {
idInputName: 'id'
type: 'VirtualMachine'
}
}
}
{
position: {
x: 0
y: 5
colSpan: 6
rowSpan: 4
}
metadata: {
inputs: [
{
name: 'options'
isOptional: true
}
{
name: 'sharedTimeRange'
isOptional: true
}
]
type: 'Extension/HubsExtension/PartType/MonitorChartPart'
settings: {
content: {
options: {
chart: {
metrics: [
{
resourceMetadata: {
id: staticSite.id
}
name: 'SiteHits'
aggregationType: 1
namespace: 'microsoft.web/staticsites'
metricVisualization: {
displayName: 'SiteHits'
resourceDisplayName: staticSiteName
}
}
]
title: 'Sum SiteHits for ${staticSiteName}'
titleKind: 1
visualization: {
chartType: 2
legendVisualization: {
isVisible: true
position: 2
hideHoverCard: false
hideLabelNames: true
}
axisVisualization: {
x: {
isVisible: true
axisType: 2
}
y: {
isVisible: true
axisType: 1
}
}
disablePinning: true
}
}
}
}
}
}
}
]
}
]
}
}
|
Real-World Observability Strategy
Good dashboards are:
- Role-based (What do SREs/Apps/Security care about?)
- Context-aware (Show logs and metrics in the same view)
- Actionable (Add alerts links, drill-through links to logs)
Avoid the “wall of graphs” syndrome. Each tile should answer:
What’s happening now, and is it affecting things I care about?
Dashboard Architecture Example
graph TD
DSH[Azure Dashboard] --> MetricChart[Resource Metrics]
DSH --> LAQuery[Log Analytics Tiles]
DSH --> Alerts[Alert Summary/Status]
LAQuery --> LA[Log Analytics Workspace]
MetricChart --> AzureVM[Azure VMs]
MetricChart --> AppService[App Service]
MetricChart --> AKS[AKS Metrics]
Alerts --> AzureMonitor[Azure Monitor Alerts]
Gotchas & Guidance
- Time ranges vary: Log tiles and metric charts respect different default timeframes. Align them for clarity.
- Too many queries = slower dashboards: Especially if you’re visualising multiple LA queries.
- RBAC complexity: Users need access to both the dashboard and the underlying resources/data.
- Don’t duplicate Workbooks: Use Workbooks for rich visuals with interactivity. Use Dashboards for quick-glance health/status.
Best Practices
- Standardise dashboard layout across environments
- Show leading indicators (not just lagging events)
- Combine metrics + logs together to tell the full story
- Save key KQL tiles as shared queries for reusability
- Tag dashboards with owner/team for easy triage
🍺
Brewed Insight: A dashboard is only as good as the decisions it enables. I usually build separate boards for:
- Infra health (CPU, Disk, Memory, Networking)
- App behaviour (errors, exceptions, response codes)
- Security posture (sign-ins, alerts, threat intel hits)
That way, each team gets their own console—and no one’s re-living the bad old days of the Big Red Wall of False Positives.
Learn More