Azure is the easiest of the four clouds to wire in, for one reason that has nothing to do with us: Microsoft standardised the alert payload. Metric alerts, log alerts, activity-log alerts and Resource Health all arrive in the same envelope, with the parts that differ quarantined inside one sub-object.
That single decision removes most of the per-signal special-casing the other clouds need.
Turn on the Common Alert Schema
This is the one configuration step that matters, and it is a checkbox on the action group.
Without it you get the older per-signal payloads, which are four different shapes, and the
connector reads essentials — which only the common schema provides.
Common Alert Schema: on"] L["Log alert (KQL)"] --> AG A["Activity log alert"] --> AG RH["Resource Health"] --> AG AG -->|"webhook"| W["/api/webhooks/azure"] W --> E["reads essentials only"] E --> N["One normalized incident"] AG -.->|"schema off"| X["four different shapes
— not supported"] style E fill:#e8f8f4,stroke:#22c1a4 style X fill:#fde8e8,stroke:#e57373
The connector deliberately reads only essentials, never the signal-specific
alertContext. That means a signal type we have never tested against still
produces a usable incident — it just carries less detail. Reading less makes the
connector more robust, not less.
The resource ID is doing a lot of work
Azure identifies everything by a path, and that path is the richest thing in the payload:
/subscriptions/a1b2…/resourceGroups/prod-rg/providers/Microsoft.Web/sites/orders-api
The normalizer splits it into labels — subscription,
resource_group, provider, resource — and uses
the resource name as the service. Those become the things your match rules bind to, which
means a rule like resource_group = prod-rg, provider = Microsoft.Web/sites routes
every app-service alert in production to one runbook.
| Azure severity | Becomes | Note |
|---|---|---|
| Sev0 / Sev1 | critical | Sev0 is reserved for "service is down" in most estates. |
| Sev2 | high | The most commonly used level, and worth routing carefully. |
| Sev3 | warning | |
| Sev4 | info | Frequently noise. Consider whether it should reach an incident tool at all. |
monitorCondition carries Fired or Resolved, which is
how the close is detected. Worth knowing: Azure's resolve payload drops
alertTargetIDs, so the resolved event has no resource, no service and far fewer
labels than the one that opened the alert. Matching on a label-derived fingerprint alone
would lose every close. The alertId is matched first, which is why it does not.
KQL is the workhorse
Three step types, and the second is the one you will use constantly.
| Step | What it does |
|---|---|
azure.metrics | A metric on one resource over a window, by aggregation. Takes the resource ID straight from the incident label. |
azure.logs | A KQL query against Log Analytics. This is where the evidence lives on Azure, and KQL is expressive enough that one step often replaces three. |
cloud.health | Resource Health events on the subscription — is it your service or the platform? |
Because KQL can join, aggregate and compare in a single query, an Azure runbook is usually shorter than its AWS or Kubernetes equivalent. One query can answer "how many exceptions, of what type, on which instances, compared with the same window yesterday" — which on other platforms takes three steps and a reasoning step to compare them.
// step: azure.logs — what changed about the exceptions? AppExceptions | where TimeGenerated > ago(30m) | summarize count() by ProblemId, bin(TimeGenerated, 5m) | order by TimeGenerated desc
Authentication without the SDK
A note for whoever has to approve this. Azure auth here is plain OAuth 2.0 client
credentials against login.microsoftonline.com, or the instance metadata
endpoint when you use a managed identity — no azure-identity in the
image.
That was a deliberate choice. Carrying the Azure, Google and OCI SDKs together would add more to the container than the whole rest of the application, and all three ultimately do what a hundred lines of HTTP does: exchange something for a bearer token. Tokens are cached until shortly before expiry.
Managed identity is the better answer
If you run InsightResolve in Azure, set AZURE_USE_MANAGED_IDENTITY and skip
the client secret entirely. There is then no credential to rotate, leak or store, and
the reviewer's question about secret handling answers itself.
Getting it wired
- Action group with a webhook receiver pointed at
/api/webhooks/azure?token=…, Common Alert Schema enabled. - An app registration with
Monitoring Readeron the subscriptions you want read, plusLog Analytics Readeron the workspace — or a managed identity with the same two roles. - Set the workspace GUID as
AZURE_LOG_ANALYTICS_WORKSPACEso KQL steps need only the query. - Check Integrations. Azure shows as configured once the tenant is set; credentials are verified on first use, and a failure there names the scope it could not get a token for.
Stated plainly
The Azure connector is built to the documented Common Alert Schema and the published Monitor and Log Analytics APIs, and tested end to end through the product against those shapes — but not against a live Azure tenant. The query steps fail safe. The normalizer is the part to verify with one real alert from your own action group, and it is a five-minute check.
Next in this series
InsightResolve on Google Cloud — and why Error Reporting is the most underrated triage signal on GCP.