2017-10-04 18 views
0

TL; DR質問:すべてのリクエストボディをコピーせずに、ASP.NET Coreの既存のExceptionTelemetryインスタンスにリクエストボディを取得する方法はありますか?ASP.NET InsightのExceptTelemetryをASP.NETコアのリクエストボディで強化する方法

私はアプリケーションの洞察のための例外テレメトリにリクエストボディを含めることができます。私。私は、例外が発生したときにのみ要求をしたい。

ASP.NET CoreとApplication Insightsの両方のドキュメントを参照すると、遠隔測定を豊かにする「正しい」方法がTelemetryProcessorsまたはTelemetryInitializersを使用しているように思われるため、カスタムテレメトリイニシャライザでリクエスト本体を取得してみましたリクエストボディストリームは、読みたいときに閉じられ/破棄されます(App Insightテレメトリ初期化ツールが実行されているときに、すでに巻き戻しが行われているため、巻き戻しは役立ちません)。

私はコピーが要求ストリームミドルウェア持つことによってそれを解決することになった:

public async Task Invoke(HttpContext context) 
{ 
    var stream = context.Request.Body; 

    try 
    { 
     using (var buffer = new MemoryStream()) 
     { 
      // Copy the request stream and rewind the copy 
      await stream.CopyToAsync(buffer); 
      buffer.Position = 0L; 

      // Create another copy and rewind both 
      var otherBuffer = new MemoryStream(); 
      await buffer.CopyToAsync(otherBuffer); 
      buffer.Position = 0L; 
      otherBuffer.Position = 0L; 

      // Replace the request stream by the first copy 
      context.Request.Body = buffer; 

      // Put a separate copy in items collection for other things to use 
      context.Items["RequestStreamCopy"] = otherBuffer; 
      context.Response.RegisterForDispose(otherBuffer); 

      await next(context); 
     } 
    } 
    finally 
    { 
     context.Request.Body = stream; 
    } 
} 

そして、私の初期化子:

public AiExceptionInitializer(IHttpContextAccessor httpContextAccessor) 
{ 
    this.httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException("httpContextAccessor"); 
} 

public void Initialize(ITelemetry telemetry) 
{ 
    var context = this.httpContextAccessor.HttpContext; 

    if (context == null) 
    { 
     return; 
    } 

    lock (context) 
    { 
     var request = context.Features.Get<RequestTelemetry>(); 
     if (request == null) 
     { 
      return; 
     } 

     this.OnInitializeTelemetry(context, request, telemetry); 
    } 
} 

protected void OnInitializeTelemetry(HttpContext platformContext, RequestTelemetry requestTelemetry, ITelemetry telemetry) 
{ 
    if (telemetry is ExceptionTelemetry exceptionTelemetry) 
    { 
     var stream = platformContext.Items["RequestStreamCopy"] as MemoryStream; 

     try 
     { 
      if (stream?.Length <= 0) 
      { 
       return; 
      } 

      // Rewind the stream position just to be on the safe side 
      stream.Position = 0L; 

      using (var reader = new StreamReader(stream, Encoding.UTF8, true, 1024, true)) 
      { 
       string requestBody = reader.ReadToEnd(); 
       exceptionTelemetry.Properties.Add("HttpRequestBody", requestBody); 
      } 
     } 
     finally 
     { 
      if (stream != null) 
      { 
       // Rewind the stream for others to use. 
       stream.Position = 0L; 
      } 
     } 
    } 
} 

しかし、これはそれぞれの要求ストリーム(2回)をコピーすることを要求では、それが失敗で使用されているだけでは、私にとってはかなり非効率的です。 このようなことをする他の方法があるのだろうかと思っています。ここでは、すべての要求のストリームをコピーして、失敗したものをシリアライズする必要はありません。

新しいExceptionTelemetryインスタンスを作成するミドルウェアを作成することはできますが、私が知っている限り(私は間違っているかもしれません)、アプリケーション・インサイトで2つの例外インスタンス(つまり、私と私の必要とする追加されたプロパティを持つただ一つの例外の代わりに、AI拡張によって生成されたもの)。

+0

は、私はおそらく、代わりの機能ではわからないストリームを入れることができますが、それは問題には無関係であるI推測。 –

+0

例外追跡ミドルウェアを書くことは可能ですが、それを使って要求の本文をコンテキストに取り込むだけですか? (言い換え:カスタム例外追跡ミドルウェアで依頼主体が依然として利用可能かどうか)はいの場合、この本体をその例外ミドルウェア内のコンテキストに書き込むことができます。その後、遠隔測定初期化子では、本体が存在しないかどうかを確認できますコンテキスト内でnullを返し、それを使用して例外プロパティを設定します。したがって、ミドルウェアをAIミドルウェアの前に注文することによって、イニシャライザが実行される前にコンテキストを生成することができます。 –

+0

完全にはわかりませんが、私は試してみてお知らせします。 –

答えて

0

@DmitryMatveevのコメントのおかげで、別の解決策が見つかりました。私はそれが最も効果的だとは思っていませんが、私が持っていたものよりも優れています!

ミドルウェアはトラッキング例外のみを対象とし、すぐに身体をシリアル化します(ストリームコピーが残っているかもしれませんが、私の場合は必要ありません)。

using System; 
using System.IO; 
using System.Text; 
using System.Threading.Tasks; 
using Microsoft.AspNetCore.Http; 
using Microsoft.AspNetCore.Http.Internal; 

public class ExceptionBodyTrackingMiddleware 
{ 
    public const string ExceptionRequestBodyKey = "ExceptionRequestBody"; 
    private readonly RequestDelegate next; 

    public ExceptionBodyTrackingMiddleware(RequestDelegate next) 
    { 
     this.next = next ?? throw new ArgumentNullException(nameof(next)); 
    } 

    public async Task Invoke(HttpContext context) 
    { 
     try 
     { 
      context.Request.EnableRewind(); 
      await this.next.Invoke(context); 
     } 
     catch (Exception) 
     { 
      RegisterRequestBody(context); 

      throw; 
     } 
    } 

    private static void RegisterRequestBody(HttpContext context) 
    { 
     if (context.Request.Body?.CanSeek == false) 
     { 
      return; 
     } 

     var body = CopyStreamToString(context.Request.Body); 
     context.Items[ExceptionRequestBodyKey] = body; 
    } 

    private static string CopyStreamToString(Stream stream) 
    { 
     var originalPosition = stream.Position; 
     RewindStream(stream); 
     string requestBody = null; 

     using (var reader = new StreamReader(stream, Encoding.UTF8, true, 1024, true)) 
     { 
      requestBody = reader.ReadToEnd(); 
     } 
     stream.Position = originalPosition; 
     return requestBody; 
    } 

    private static void RewindStream(Stream stream) 
    { 
     if (stream != null) 
     { 
      stream.Position = 0L; 
     } 
    } 
} 

同様に初期化子が全体の多く簡単になる:私は他の上で1つを行う必要があるとき

public AiExceptionInitializer(IHttpContextAccessor httpContextAccessor) 
{ 
    this.httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException("httpContextAccessor"); 
} 

public void Initialize(ITelemetry telemetry) 
{ 
    var context = this.httpContextAccessor.HttpContext; 

    if (context == null) 
    { 
     return; 
    } 

    lock (context) 
    { 
     var request = context.Features.Get<RequestTelemetry>(); 
     if (request == null) 
     { 
      return; 
     } 

     this.OnInitializeTelemetry(context, request, telemetry); 
    } 
} 

protected void OnInitializeTelemetry(HttpContext platformContext, RequestTelemetry requestTelemetry, ITelemetry telemetry) 
{ 
    if (telemetry is ExceptionTelemetry exceptionTelemetry) 
    { 
     var requestBody = platformContext.Items[ExceptionBodyTrackingMiddleware.ExceptionRequestBodyKey] as string; 
     exceptionTelemetry.Properties.Add("HttpRequestBody", requestBody); 
    } 
} 
関連する問題