APM Tips

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.

Updated

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 stores success as a string; comparing the text form works whichever way it is stored.
  • where resultCode startswith "2" keeps 2xx codes. startswith compares text, which is right here because resultCode is a string.
  • summarize failed = sum(itemCount) counts them the sampling-aware way, and take_any(operation_Id) keeps one example operation per row.
  • by operation_Name, resultCode, cloud_RoleName shows 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, OperationName and AppRoleName.

Sources, checked

  1. Application Insights telemetry data model (request success and resultCode)learn.microsoft.com · checked
  2. take_any() (Kusto)learn.microsoft.com · checked
  3. Migrate an Application Insights classic resource to a workspace-based resource (table schemas)learn.microsoft.com · checked
  4. Telemetry sampling in Application Insightslearn.microsoft.com · checked