1. はじめに
ASP.NET CoreとAzure Functions(分離ワーカーモデル)で Web API などを開発していると、Application Insights の「失敗(Failures)」タブが真っ赤に染まっていてヒヤッとした経験はありませんか?
調べてみると、ユーザーがブラウザのタブを閉じたり、処理を途中でキャンセルしたりしたせいで発生しているエラーだった…なんてことがよくありますよね。
監視アラートが鳴り続けると「またキャンセルか…」とノイズ疲れしてしまい、本当に致命的なエラーを見逃す原因になってしまいます。
今回は、この「通信切断エラー」による監視ノイズを減らすための Tips をご紹介します。
本記事では、ASP.NET Core アプリケーションが HttpClient 経由で Azure Functions(.NET Isolated Worker)を呼び出す構成を前提にします。
2. HTTP 499(Client Closed Request)とは?
そもそも「HTTP 499 (Client Closed Request)」とは何でしょうか?
これは元々 Nginx などのプロキシサーバーで使われ始めた非公式のステータスコードですが、ASP.NET Core でも StatusCodes.Status499ClientClosedRequest としてしっかり定義されています。
意味は単純で、「サーバーが処理を完了してレスポンスを返す前に、クライアント側が TCP 接続を切断した」状態を表します。つまり、499 は必ずしもサーバー側のバグを意味するものではなく、クライアント切断というネットワーク上の想定された正常なネットワークの振る舞いなのです。
3. 分離ワーカーモデルで直面する「例外の連鎖」
Azure Functions の分離ワーカーモデル(Isolated Worker)では、このクライアントの切断が「例外の連鎖」を引き起こします。アーキテクチャ上、以下の 2 箇所で別々の例外が飛び交うのが厄介なところです。
- ワーカープロセス(ASP.NET Core側): キャンセルトークン(CancellationToken)が発火し、TaskCanceledException や OperationCanceledException が発生する
- ホストプロセス(Functions Host側): クライアントがすでに居ないのにデータを返そうとして、通信レイヤーで System.Net.Sockets.SocketException が発生する
これらを抑制するために、3 段構えのノイズフィルタリングを実装していきましょう。
4. 解決策:3 段構えのノイズフィルタリング
Step 1: API やビジネスロジックの try-catch を見直す
まず、一番ありがちな「罠」からです。開発者の親切心で書いた以下のようなコードが、Application Insights を汚染している犯人かもしれません。
try
{
// 外部API呼び出しや重いDBアクセスなど
}
catch (Exception ex)
{
// ありがちな「罠」:親切心でエラーログを出してしまうと、グローバルな例外ハンドラに
// 届く前にApp Insightsに「失敗」として記録されてしまう
// _logger.LogError(ex, "API呼び出しでエラーが発生しました");
// throw;
// 【対策】キャンセル時はエラーログを出さずに上位に投げる
if (HttpContext.RequestAborted.IsCancellationRequested)
{
_logger.LogInformation("クライアント切断のため処理を中断しました。");
throw; // この後の GlobalExceptionHandler に処理を任せる
}
_logger.LogError(ex, "本当の予期せぬエラーです");
throw;
}
Step 2: IExceptionHandler でキャンセル例外を吸収する
次に、.NET 8 から導入された IExceptionHandler を使って、飛んできた例外を無害化します(ステータスコードを 499 に書き換えます)。
ここでも「例外の型で絞り込まない」のが最大のポイントです。SocketException などがラップされて飛んでくることもあるので、if (exception is OperationCanceledException) のように型で判定してしまうと、すり抜けてエラーになってしまいます。
using Microsoft.AspNetCore.Diagnostics;
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger<GlobalExceptionHandler> _logger;
public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
{
_logger = logger;
}
public ValueTask<bool> TryHandleAsync(HttpContext context, Exception exception, CancellationToken cancellationToken)
{
// 例外の型で絞り込まず、クライアントが切断済みかどうか「だけ」で判定する
if (context.RequestAborted.IsCancellationRequested)
{
_logger.LogInformation("クライアントの切断によりリクエストがキャンセルされました。(HTTP 499)");
if (!context.Response.HasStarted)
{
context.Response.StatusCode = StatusCodes.Status499ClientClosedRequest;
}
// trueを返すことで例外処理が完了したとみなされる
return ValueTask.FromResult(true);
}
return ValueTask.FromResult(false);
}
}
💡.NET 10 の嬉しいアップデート
.NET 10 からは、ここでtrueを返すだけで、フレームワーク標準の不要なエラーログ出力(App Insights への送信含む)がデフォルトで自動的に抑制されるようになりました。
Step 3: OpenTelemetry で 499 のテレメトリをフィルタする
Step 2 までで、ASP.NET Core アプリケーション側のキャンセル例外はある程度制御できます。
ただし、Application Insights に送信されるテレメトリは、アプリケーションコードから出るものだけとは限りません。Azure Functions の .NET Isolated Worker では、Functions Host 側と Worker 側でログ送信経路が分かれる場合があります。
そのため、アプリケーション側から送信している OpenTelemetry の Activity については、499 を検出して送信対象から外すフィルタを追加します。
注意:
ここで紹介する OpenTelemetry Processor は、アプリケーション側(Language Worker 側)から送信される Activity / Trace に対するフィルタです。 Functions Host 側が直接 Application Insights に送信しているログや例外については、この Processor だけでは抑制できない場合があります。 その場合は host.json のログレベル、Application Insights 側のフィルタ、またはログ送信パイプラインの構成を別途確認してください。
using System.Diagnostics;
using OpenTelemetry;
public class Http499FilterProcessor : BaseProcessor<Activity>
{
public override void OnEnd(Activity activity)
{
// タグからレスポンスのステータスコードを取得
var statusCode =
activity.GetTagItem("http.response.status_code")?.ToString()
?? string.Empty;
// ステータスコードが 499 だったら記録対象から外す
if (statusCode == "499")
{
activity.IsAllDataRequested = false;
activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded;
}
base.OnEnd(activity);
}
}
5. Program.cs での登録まとめ
最後に、作成した部品たちを Program.cs に組み込みます。AddProblemDetails() を忘れると起動時に例外ハンドラの構成エラー(InvalidOperationException)になるので注意してください。
var builder = WebApplication.CreateBuilder(args);
// ハンドラとProblemDetailsを登録
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();
// OpenTelemetryのプロセッサを登録
builder.Services.ConfigureOpenTelemetryTracerProvider((sp, otelBuilder) =>
{
otelBuilder.AddProcessor(new Http499FilterProcessor());
});
var app = builder.Build();
// 例外ハンドラを有効化(ミドルウェアのなるべく最初の方で!)
app.UseExceptionHandler();
app.MapControllers();
app.Run();
6. おわりに
HTTP 499(Client Closed Request)によるエラーログのノイズを抑制するための 3 つのステップをご紹介しました。これらの対策を実施することで、クライアント切断に起因する不要なアラートやエラーログを大幅に減らし、本当に対応すべき障害や異常に集中できる環境を整えることができます。
特に、ASP.NET Core や Azure Functions(分離ワーカー)を利用したシステムでは、キャンセルトークンの適切な伝播や、例外ハンドリングの設計が重要です。加えて、OpenTelemetry などの監視基盤でも 499 のノイズを除外する工夫を取り入れることで、運用負荷の軽減と品質向上につながります。
今後もフレームワークやクラウドサービスのアップデートに注目しつつ、現場の運用課題に即したノイズ対策を継続的に見直していきましょう。