
A practical triage order for Azure Application Gateway and Front Door WAF logs — the five questions an analyst asks before opening a single ticket.
Azure WAF logs are signal-rich and very, very noisy. The difference between drowning in them and pulling an incident out in fifteen minutes is the order in which you ask questions. This is the order I use — five questions, each with the KQL to answer it, applied in sequence before raising anything.
Assumes you have diagnostic settings enabled with resource-specific destination tables and logs landing in a Log Analytics workspace. The queries below target Application Gateway WAF — table AGWFirewallLogs for firewall events and AGWAccessLogs for the matching request records. If you’re still on legacy Azure diagnostics, the same data lives in AzureDiagnostics under categories ApplicationGatewayFirewallLog and ApplicationGatewayAccessLog, but field names get the classic _s / _d suffixes and you have to filter by category. Switch to resource-specific tables if you can — the schema is cleaner and queries are faster.
The first question is always shape. One attacker hammering your endpoint reads very differently from a botnet doing distributed reconnaissance.
AGWFirewallLogs
| where TimeGenerated > ago(1h)
| where Action == "Blocked"
| summarize Blocks = count() by ClientIp
| top 20 by BlocksIf the top one or two IPs account for 80%+ of blocks, you’re looking at a single noisy source — usually a scanner or someone fuzzing. If the distribution is flat across dozens of IPs, that’s a distributed pattern and worth more attention.
Don’t escalate yet. Keep going.
The next reality check: is your policy actually blocking, or are you in Detection mode and quietly logging everything?
AGWFirewallLogs
| where TimeGenerated > ago(24h)
| summarize Requests = count() by Action
| order by Requests descAction will typically be Blocked, Matched (rule matched but not blocked — Detection mode behavior), Detected, or Allowed. The mode lives on the WAF policy, but the action distribution tells you the truth: if you see thousands of Matched and zero Blocked, the policy is in Detection mode regardless of what the dashboard says.
This is where production WAF deployments fail quietly. A “we’re protected” policy that’s actually still in Detection from the original rollout, never flipped, is more common than anyone wants to admit. Settle that question before anything else.
Now look at what the WAF is catching. The top rule IDs tell you the attack pattern.
AGWFirewallLogs
| where TimeGenerated > ago(24h)
| where Action == "Blocked"
| summarize Blocks = count() by RuleId, Message
| top 10 by BlocksYou’re looking for two things:
RuleId (e.g. 942100 — SQL injection) dominates, you have a clear attack vector — and likely a clear false-positive candidate if the source IP matches a known legitimate caller.For the OWASP Core Rule Set, the first three digits of the rule ID tell you the category (913 scanner, 920 protocol enforcement, 941 XSS, 942 SQLi, 949 blocking eval, etc.). Memorize the ranges you see most.
Group the same blocks by hostname and request URI to see what the attacker is actually after.
AGWFirewallLogs
| where TimeGenerated > ago(24h)
| where Action == "Blocked"
| summarize Blocks = count() by Hostname, RequestUri
| top 20 by BlocksTargeted blocks on /wp-login.php when you don’t run WordPress are noise. Targeted blocks on your actual login endpoint are not. The signal-to-noise ratio jumps the moment you cross-reference hostname with a real endpoint someone cares about.
The most important question — and the one most analysts skip. Just because the WAF blocked a thousand requests doesn’t mean the attacker didn’t also send one the WAF allowed that hit a real vulnerability.
Correlate the suspect client IP against the matching access log. Application Gateway emits a TransactionId on both the firewall log and the access log for the same request — that’s your join key:
let suspectIP = "203.0.113.45";
AGWFirewallLogs
| where TimeGenerated > ago(24h)
| where ClientIp == suspectIP
| project TimeGenerated, Action, RuleId, Message, RequestUri, TransactionId
| join kind=inner (
AGWAccessLogs
| where TimeGenerated > ago(24h)
| where ClientIp == suspectIP
| project TimeGenerated, HttpStatus, RequestUri, TransactionId
) on TransactionId
| where HttpStatus between (200 .. 299)
| order by TimeGenerated descAny 2xx response correlated by TransactionId is a request the WAF saw, decided about, and the backend served successfully. If the same client also has Blocked rows for similar payloads, the 2xx rows are where to look hardest — they’re what slipped through.
When question 3 surfaces a noisy rule firing against a known-good client (a CI job, a partner integration, an internal service), the fix is rarely “disable the rule.” It’s usually one of:
RequestHeaderNames), specific selector (x-myapp-token), specific rule. Never blanket-exclude a whole rule.Document the reason in your exclusion’s display name. Future-you will not remember why Exclusion-027 exists.
Escalate when:
Everything else is noise to characterize, document, and move on from.
WAF logs reward discipline. Walk the five questions in order — source shape, actual action, rule concentration, target, what landed — and the noise filters itself out without you needing to tune queries for hours. The five-minute version of this checklist beats the perfect dashboard you’ll never build.
Are you looking for
Consulting Services,
Support Plans or
Training & Workshops?