Azure Monitor
Monitor your Azure estate — Virtual Machines, App Service apps, Azure SQL databases, Storage accounts, and Service Bus namespaces — through Azure Monitor, with curated dashboards and alerts you import in one step.
Azure Monitor is a polled API rather than a scrape endpoint, but no adapter is
needed: the LeanSignal agent gateway is an OpenTelemetry Collector
distribution and bundles the
azuremonitor receiver,
which polls the Azure Monitor metrics API with a read-only Entra ID identity,
discovers resources per subscription and resource group, and emits one series
per metric. The gateway keeps full fidelity locally and forwards only the
demanded subset to your central dataplane — so polling broadly stays cheap,
and importing a demand is what decides which Azure series are stored centrally.
The panels and alerts cover the five services most Azure subscriptions run on — Virtual Machines, App Service, Azure SQL, Storage, and Service Bus — with the thresholds Azure operators actually page on: memory headroom, 5xx ratios, database storage and worker saturation, storage availability, and message backlog.
Prerequisites
Section titled “Prerequisites”- A LeanSignal agent gateway deployed and connected — one per cluster or
environment. If you haven’t deployed it yet, follow
Install the agent (source:
LeanSignal/leansignal-agent). It should show as Connected under Agents in the LeanSignal app. - Network reachability to the gateway’s OTLP port —
4317(gRPC) or4318(HTTP). See Agent configuration. - A read-only Azure identity — an Entra ID app registration or managed
identity with the built-in Monitoring Reader role on each subscription you
poll — and an OpenTelemetry Collector running the
azuremonitorreceiver, which the LeanSignal agent gateway already bundles. Both configured in Setup. - Editor or admin role in the LeanSignal app (importing a demand creates dashboards and alert rules).
Two pieces: a read-only Azure identity, and a collector running the
azuremonitor receiver that polls Azure Monitor and forwards OTLP to the
gateway.
1. Azure — a read-only identity. Create a service principal with the
built-in Monitoring Reader role on every subscription you poll (repeat
--scopes per subscription):
az ad sp create-for-rbac --name leansignal-monitor \ --role "Monitoring Reader" \ --scopes /subscriptions/<subscription-id>The output gives you appId (client ID), password (client secret), and
tenant. Managed identity and workload identity work too — set
credentials: managed_identity (or workload_identity) instead of the client
secret below.
2. Collector — poll Azure Monitor, forward OTLP to the gateway (not a SaaS
endpoint). One services entry per resource type; metrics limits the poll to
the metrics and aggregations the dashboards actually chart:
receivers: azuremonitor: subscription_ids: ["${env:AZURE_SUBSCRIPTION_ID}"] # list every subscription tenant_id: "${env:AZURE_TENANT_ID}" client_id: "${env:AZURE_CLIENT_ID}" client_secret: "${env:AZURE_CLIENT_SECRET}" collection_interval: 60s # One series per resource per metric. Turn this on to break metrics down by # their Azure dimensions (API name, entity name, …) — panels aggregate over # them either way, at the cost of many more series. dimensions: enabled: false # Off by default; the dashboards use it to tell subscriptions apart. resource_attributes: azuremonitor.subscription_id: enabled: true services: - Microsoft.Compute/virtualMachines - Microsoft.Web/sites - Microsoft.Sql/servers/databases - Microsoft.Storage/storageAccounts - Microsoft.ServiceBus/namespaces metrics: "microsoft.compute/virtualmachines": Percentage CPU: [average] Available Memory Bytes: [average] Network In Total: [total] Network Out Total: [total] Disk Read Bytes: [total] Disk Write Bytes: [total] CPU Credits Remaining: [average] OS Disk IOPS Consumed Percentage: [average] Data Disk IOPS Consumed Percentage: [average] "microsoft.web/sites": Requests: [total] Http4xx: [total] Http5xx: [total] HttpResponseTime: [average] MemoryWorkingSet: [average] CpuTime: [total] "microsoft.sql/servers/databases": cpu_percent: [average] storage_percent: [average] connection_successful: [total] connection_failed: [total] deadlock: [total] physical_data_read_percent: [average] log_write_percent: [average] workers_percent: [average] sessions_percent: [average] "microsoft.storage/storageaccounts": Availability: [average] Transactions: [total] UsedCapacity: [average] SuccessE2ELatency: [average] SuccessServerLatency: [average] Ingress: [total] Egress: [total] "microsoft.servicebus/namespaces": ActiveMessages: [average] DeadletteredMessages: [average] IncomingMessages: [total] OutgoingMessages: [total] ServerErrors: [total] UserErrors: [total] ThrottledRequests: [total]
processors: # Required — see "Azure units" below. Keeps names as azure_<metric>_<aggregation>. transform/azure_units: error_mode: ignore metric_statements: - set(metric.unit, "") where IsMatch(metric.name, "^azure_") batch: {}
exporters: otlp: endpoint: leansignal-agent:4317 # per-cluster gateway (use :4318 for HTTP) tls: insecure: true # in-cluster plaintext; provide ca_file for TLS
service: pipelines: metrics: receivers: [azuremonitor] processors: [transform/azure_units, batch] exporters: [otlp]Running this on the gateway itself instead? Add the azuremonitor receiver and
the transform/azure_units processor to its own config, then add them to its
metrics/all pipeline — the otlp exporter and the separate service block
above are only for a standalone collector.
To confirm metrics are arriving, look for the azure_ family in the LeanSignal
app under Metrics with the source set to Available, or on the gateway
host:
curl -s http://127.0.0.1:8428/api/v1/label/__name__/values | grep -o 'azure_[a-z_]*' | sort -u | headLogs and traces
Section titled “Logs and traces”Azure resource logs reach the gateway through Event Hubs, the path Azure
itself is built around: add a diagnostic setting on each resource (or a
subscription-level policy) that streams its log categories to an Event Hub,
then point the gateway’s bundled azureeventhub receiver at it — the streams
then behave like any other logs.
receivers: azureeventhub: connection: "${env:AZURE_EVENTHUB_CONNECTION_STRING}" # …;EntityPath=<hub-name> group: leansignal # consumer group format: azureAdd azureeventhub to a logs pipeline exporting OTLP to the gateway (or to
the gateway’s own logs/all pipeline). Traces are an application concern,
not an Azure Monitor one — instrument the workloads running on Azure with the
OpenTelemetry SDKs (see the runtime integrations) and point
them at the gateway; apps already wired for Application Insights export OTLP the
same way.
Demands
Section titled “Demands”Azure Monitor ships as a ready-made demand in three variants. You don’t copy any JSON — in the LeanSignal app go to Demands, press the ˅ arrow on the Add Demand button (the arrow opens the import menu), choose Import from catalog…, and pick the variant. Each variant below lists the slug that identifies its published bundle. See Integrations for the full import flow.
The variants are nested — Standard is a superset of Essential, Extended a superset of Standard. Pick whichever is closest to what you need and treat it as a starting point: after import everything is a normal, editable copy, so retune thresholds, add or drop panels, and adjust queries for your environment — your edits reshape the demand automatically. Start small and re-import a larger variant later if you outgrow it.
The failure signals. 1 dashboard · 8 panels · 5 alerts — CPU, memory headroom, 5xx responses, storage headroom, availability, and message backlog at a glance.
Import this variant from the catalog. Its demand slug:
azure-monitor-otel-demand-essentialDashboard — CPU utilization, Available memory, Requests (per period), 4xx / 5xx responses (per period), CPU utilization, Storage used, Availability, and Active and dead-lettered messages.
Alerts (5)
| Alert | Severity | Fires when |
|---|---|---|
| App Service high 5xx ratio | warning | 5xx > 5% of requests for 10m |
| Azure SQL storage nearly full | critical | > 90% of allocated storage for 15m |
| Storage account availability degraded | critical | availability < 99% for 10m |
| Service Bus dead-lettered messages | warning | any dead-lettered message for 15m |
| VM low available memory | critical | < 256 MiB available for 15m |
Well-rounded coverage. 1 dashboard · 17 panels · 9 alerts — adds network and disk throughput, response time, connections, transactions, and message throughput. The recommended default.
Import this variant from the catalog. Its demand slug:
azure-monitor-otel-demand-standardDashboard — everything in Essential, plus Network in / out (per period), Disk read / write (per period), Response time (avg), Memory working set, Connections (per period), Deadlocks (per period), Transactions (per period), Used capacity, and Incoming / outgoing messages (per period).
Alerts (9)
| Alert | Severity | Fires when |
|---|---|---|
| App Service high 5xx ratio | warning | 5xx > 5% of requests for 10m |
| Azure SQL storage nearly full | critical | > 90% of allocated storage for 15m |
| Storage account availability degraded | critical | availability < 99% for 10m |
| Service Bus dead-lettered messages | warning | any dead-lettered message for 15m |
| VM low available memory | critical | < 256 MiB available for 15m |
| VM high CPU | warning | > 90% CPU for 30m |
| Azure SQL high CPU | warning | > 90% CPU for 15m |
| Azure SQL connection failures | warning | > 10 failed logins per period for 10m |
| App Service slow responses | warning | average response time > 2s for 15m |
Deep subscription visibility. 1 dashboard · 25 panels · 12 alerts — CPU credits, IOPS saturation, worker saturation, storage latency, and namespace errors for estates you need to diagnose, not just watch.
Import this variant from the catalog. Its demand slug:
azure-monitor-otel-demand-extendedDashboard — everything in Standard, plus CPU credits remaining (B-series), Disk IOPS consumed, CPU time (per period), Data I/O and log write, Workers and sessions, End-to-end / server latency, Ingress / egress (per period), and Errors and throttling (per period).
Alerts (12)
| Alert | Severity | Fires when |
|---|---|---|
| App Service high 5xx ratio | warning | 5xx > 5% of requests for 10m |
| Azure SQL storage nearly full | critical | > 90% of allocated storage for 15m |
| Storage account availability degraded | critical | availability < 99% for 10m |
| Service Bus dead-lettered messages | warning | any dead-lettered message for 15m |
| VM low available memory | critical | < 256 MiB available for 15m |
| VM high CPU | warning | > 90% CPU for 30m |
| Azure SQL high CPU | warning | > 90% CPU for 15m |
| Azure SQL connection failures | warning | > 10 failed logins per period for 10m |
| App Service slow responses | warning | average response time > 2s for 15m |
| VM CPU credits low | warning | B-series credit balance < 20 for 15m |
| Azure SQL worker saturation | warning | > 80% of tier workers for 15m |
| Service Bus throttled requests | warning | any throttled request for 10m |
Dashboards
Section titled “Dashboards”Each variant imports one Azure Monitor dashboard, its panels grouped into
collapsible sections. Every dashboard carries an Instance filter (on the
resource_group label), so one import covers the whole fleet — view every instance
at once, or focus on one. Legends are sortable tables showing last/mean/max per
series, so the outlier instance stands out. Dashboards are demand-driven:
importing one tells your agents to forward exactly the timeseries its panels
query, and nothing else. Panels are ordinary Perses panels — edit queries, add
panels, or retune them after import, and your changes reshape the demand
automatically.
Alerts
Section titled “Alerts”Thresholds are conservative starting points — tune them to your workload.
Next steps
Section titled “Next steps”- Demand-driven observability — why importing a demand also shapes what your agents collect.
- Agent configuration — the full agent config surface.
Thanks for your feedback!
Report sent — thank you!