KQL · failures
Requests marked failed despite a 2xx result code
Find requests recorded as failed although they returned a 2xx status, a sign of custom success logic or an exception recorded on the request.
requests failures
let lookback = 7d;
requests
| where timestamp > ago(lookback)
| where tostring(success) =~ "false"
| where resultCode startswith "2"
| summarize
failed = sum(itemCount),
example_operation_Id = take_any(operation_Id)
by operation_Name, resultCode, cloud_RoleName
| order by failed desc Returns: One row per operation, 2xx result code and cloud role among requests recorded as failed, with the sampling-adjusted count and one operation_Id to inspect.
Pitfalls
- An empty result is the normal case. Rows here mean something other than the status code decided success, and that something is worth knowing about.
- resultCode is text, so startswith "2" matches 200 to 299. For non-HTTP requests the code can be something else entirely and is left out.
- The reverse case matters too: 5xx responses recorded as successful. The variation below finds them.
- With sampling on, the example operation is one that was kept; open it with the end-to-end trace query.
How it works
success and resultCode are separate fields. The data model says a web request counts as successful when the response code is below 400 or equal to 401, and that success should be set to false when an exception or an error result interrupted the operation. Most of the time the two agree. When a 200 is recorded as a failure, something other than the status code made that decision, and you want to know what before trusting your failure rate.
Line by line
let lookback = 7d;uses a week, because these rows are rare and a day may hold none.where tostring(success) =~ "false"keeps failed requests. The classic schema storessuccessas a string; comparing the text form works whichever way it is stored.where resultCode startswith "2"keeps 2xx codes.startswithcompares text, which is right here becauseresultCodeis a string.summarize failed = sum(itemCount)counts them the sampling-aware way, andtake_any(operation_Id)keeps one example operation per row.by operation_Name, resultCode, cloud_RoleNameshows which route and which service, so you can find the code responsible.
What usually causes it
Three things, all worth checking. A telemetry initializer or OpenTelemetry processor that sets success by its own rule, for example treating a 200 with an error body as a failure. Code that records an exception on the request after the response status was already sent, such as an error while streaming a response. Or an instrumentation that marks the request failed because the operation reported an error. The initializers and processors guide shows where such rules live in the classic SDK and in OpenTelemetry. Open the example operation with the end-to-end trace query to see what else happened in it.
Variations
- 5xx recorded as successful: replace the two filters with
| where tostring(success) =~ "true"and| where resultCode startswith "5". Rows there usually mean an initializer that marks errors as successful on purpose. - Workspace tables: use
AppRequests,TimeGenerated,Success == false,ResultCode,ItemCount,OperationId,OperationNameandAppRoleName.