ASP.NET MVCとIIS7で生のHTTPリクエスト/レスポンスをログに記録する


140

私は(ASP.NET MVCを使用して)Webサービスを作成しています。サポートの目的で、要求と応答を生のオンザワイヤー形式(HTTPを含む)に可能な限り近い形で記録できるようにしたいと考えていますメソッド、パス、すべてのヘッダー、および本文)をデータベースに挿入します。

私がよくわからないのは、このデータを最小限の「壊れた」方法で取得する方法です。HttpRequestオブジェクトのすべてのプロパティを検査し、それらから文字列を作成することで(そして同様に応答についても)、要求がどのように見えるかを再構成できますが、実際の要求/応答データを取得したいと思いますワイヤーで送った。

フィルター、モジュールなど、任意のインターセプトメカニズムを使用できて嬉しいです。ソリューションはIIS7に固有のものにすることができます。ただし、マネージコードでのみ保持することをお勧めします。

何かお勧めですか?

編集:私はそれHttpRequestSaveAsリクエストをディスクに保存できるメソッドを持っていることに注意しますが、これはパブリックにアクセスできない内部ヘルパーメソッドのロードを使用して内部状態からリクエストを再構築します(これがユーザー指定の保存を許可しない理由わからないストリーム)。オブジェクトからリクエスト/レスポンステキストを再構築するために最善を尽くさなければならないように見え始めています...うめき声。

編集2:メソッド、パス、ヘッダーなどを含むリクエスト全体を言ったことに注意してください。現在の応答は、この情報を含まないボディストリームのみを確認します。

編集3:誰もこの辺りの質問を読みませんか?これまでのところ5つの回答はありますが、1つもまだ完全な未加工のオンザワイヤー要求を取得する方法を示唆していません。はい、出力ストリーム、ヘッダー、URL、その他すべてをリクエストオブジェクトからキャプチャできることはわかっています。私はすでに質問で言った、参照してください:

HttpRequestオブジェクトのすべてのプロパティを検査し、それらから文字列を作成することで(そして同様に応答の場合も)、要求がどのように見えるかを再構成できますが、実際の要求/応答データを取得したいと思いますそれはネットワーク上で送信されます。

あなたが知っている場合は、完全な(ヘッダ、URL、HTTPメソッド、などを含む)生データを単に知っていることは有用であろうこと、次に取得することはできません。同様に、再構築することなくすべてを未加工の形式で取得する方法を知っている場合(はい、私はまだヘッダー、URL、httpメソッドなどを含めることを意味します)、これは私が尋ねたとおり、非常に便利です。しかし、HttpRequest/ HttpResponseオブジェクトから再構築できると言っても役に立たない。そんなこと知ってる。私はすでにそれを言った。


注意:これが悪い考えだとか、スケーラビリティを制限するなどと言う前に、分散環境でスロットル、順次配信、およびリプレイ防止メカニズムも実装する予定なので、とにかくデータベースロギングが必要です。これが良いアイデアかどうかの議論を探しているのではなく、それをどのように実行できるかを探しています。


1
@Kev-いいえ、これはASP.NET MVCを使用して実装されたRESTfulサービスです
グレッグビーチ

IIS7とネイティブモジュールを使用して実行することはおそらく可能です-msdn.microsoft.com/en-us/library/ms694280.aspx
Daniel Crenna

これをなんとか実装できましたか?気になるだけですが、dbに書き込むためにバッファー戦略を採用しましたか?
systempuntoout 2015

1
興味深いプロジェクト...あなたが最終的にそれをやった場合、最終的な解決策はありますか?
PreguntonCojoneroCabrón

回答:


91

を確実に使用しIHttpModuleBeginRequestおよびEndRequestイベントを実装します。

「生」データのすべてが間に存在しているHttpRequestHttpResponseそれだけで、単一の生のフォーマットではありません、。Fiddlerスタイルのダンプを構築するために必要な部分を次に示します(生のHTTPにできるだけ近い):

request.HttpMethod + " " + request.RawUrl + " " + request.ServerVariables["SERVER_PROTOCOL"]
request.Headers // loop through these "key: value"
request.InputStream // make sure to reset the Position after reading or later reads may fail

応答について:

"HTTP/1.1 " + response.Status
response.Headers // loop through these "key: value"

応答ストリームを読み取ることができないため、出力ストリームにフィルターを追加してコピーをキャプチャする必要があることに注意してください

BeginRequest、応答フィルターを追加する必要があります。

HttpResponse response = HttpContext.Current.Response;
OutputFilterStream filter = new OutputFilterStream(response.Filter);
response.Filter = filter;

ストアfilterあなたがそれを得ることができますEndRequestハンドラ。で提案しHttpContext.Itemsます。その後、完全な応答データをで取得できfilter.ReadStream()ます。

次にOutputFilterStream、ストリームのラッパーとしてDecoratorパターンを使用して実装します。

/// <summary>
/// A stream which keeps an in-memory copy as it passes the bytes through
/// </summary>
public class OutputFilterStream : Stream
{
    private readonly Stream InnerStream;
    private readonly MemoryStream CopyStream;

    public OutputFilterStream(Stream inner)
    {
        this.InnerStream = inner;
        this.CopyStream = new MemoryStream();
    }

    public string ReadStream()
    {
        lock (this.InnerStream)
        {
            if (this.CopyStream.Length <= 0L ||
                !this.CopyStream.CanRead ||
                !this.CopyStream.CanSeek)
            {
                return String.Empty;
            }

            long pos = this.CopyStream.Position;
            this.CopyStream.Position = 0L;
            try
            {
                return new StreamReader(this.CopyStream).ReadToEnd();
            }
            finally
            {
                try
                {
                    this.CopyStream.Position = pos;
                }
                catch { }
            }
        }
    }


    public override bool CanRead
    {
        get { return this.InnerStream.CanRead; }
    }

    public override bool CanSeek
    {
        get { return this.InnerStream.CanSeek; }
    }

    public override bool CanWrite
    {
        get { return this.InnerStream.CanWrite; }
    }

    public override void Flush()
    {
        this.InnerStream.Flush();
    }

    public override long Length
    {
        get { return this.InnerStream.Length; }
    }

    public override long Position
    {
        get { return this.InnerStream.Position; }
        set { this.CopyStream.Position = this.InnerStream.Position = value; }
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        return this.InnerStream.Read(buffer, offset, count);
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        this.CopyStream.Seek(offset, origin);
        return this.InnerStream.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
        this.CopyStream.SetLength(value);
        this.InnerStream.SetLength(value);
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        this.CopyStream.Write(buffer, offset, count);
        this.InnerStream.Write(buffer, offset, count);
    }
}

1
素敵な答え。しかし、1つのコメント:「filter.ToString()で完全な応答データを取得できる」と述べました。-filter.ReadStream()を意味するのではないですか?(私はしません#cをvb.netで実装していますが、私はToStringメソッドIを実行する場合は単なる文字列としてクラス名を取得.ReadStreamは、所望の応答本文を返す。。
アダム

同意します。良い答えです。これをカスタムロガーのベースとして使用しましたが、一部のヘッダーが欠落しているという問題に遭遇しました。最も重要なのは、IIS圧縮を使用しているときに最終的な圧縮応答にアクセスできないことです。このために、関連する新しい質問(stackoverflow.com/questions/11084459/…)を開始しました。
Chris

2
mckameyは天才だと思います。優れた回避策を必要とせずに、インテリジェントソリューションを入手できるように、マイクロソフトに就職してもらえますか?
そろばん2013年

4
request.RawUrlがリクエスト検証例外をトリガーする可能性があることに注意してください。4.5では、request.Unvalidated.RawUrlを使用してこれを防止できます。4.0では、リフレクションを使用してRequest.SaveAs
Freek

1
@mckamey Application_BeginRequestとApplication_EndRequestを使用して自分のglobal.asaxにソリューションを実装しようとしていますが、EndRequestにどのコードを記述すればよいかわかりません。回答plzに例を提供できますか?
Jerome2606

48

次のHttpRequestの拡張メソッドは、フィドラーに貼り付けて再生できる文字列を作成します。

namespace System.Web
{
    using System.IO;

    /// <summary>
    /// Extension methods for HTTP Request.
    /// <remarks>
    /// See the HTTP 1.1 specification http://www.w3.org/Protocols/rfc2616/rfc2616.html
    /// for details of implementation decisions.
    /// </remarks>
    /// </summary>
    public static class HttpRequestExtensions
    {
        /// <summary>
        /// Dump the raw http request to a string. 
        /// </summary>
        /// <param name="request">The <see cref="HttpRequest"/> that should be dumped.       </param>
        /// <returns>The raw HTTP request.</returns>
        public static string ToRaw(this HttpRequest request)
        {
            StringWriter writer = new StringWriter();

            WriteStartLine(request, writer);
            WriteHeaders(request, writer);
            WriteBody(request, writer);

            return writer.ToString();
        }

        private static void WriteStartLine(HttpRequest request, StringWriter writer)
        {
            const string SPACE = " ";

            writer.Write(request.HttpMethod);
            writer.Write(SPACE + request.Url);
            writer.WriteLine(SPACE + request.ServerVariables["SERVER_PROTOCOL"]);
        }

        private static void WriteHeaders(HttpRequest request, StringWriter writer)
        {
            foreach (string key in request.Headers.AllKeys)
            {
                writer.WriteLine(string.Format("{0}: {1}", key, request.Headers[key]));
            }

            writer.WriteLine();
        }

        private static void WriteBody(HttpRequest request, StringWriter writer)
        {
            StreamReader reader = new StreamReader(request.InputStream);

            try
            {
                string body = reader.ReadToEnd();
                writer.WriteLine(body);
            }
            finally
            {
                reader.BaseStream.Position = 0;
            }
        }
    }
}

5
とても良いコードです!しかし、これをMVC 4で機能させるには、クラス名をHttpRequestBaseExtensionsに変更HttpRequestHttpRequestBase、すべての場所でに変更する必要がありました。
ドミトリー

35

ALL_RAWサーバー変数を使用して、リクエストで送信された元のHTTPヘッダーを取得してから、通常どおりにInputStreamを取得できます。

string originalHeader = HttpHandler.Request.ServerVariables["ALL_RAW"];

チェックアウト:http : //msdn.microsoft.com/en-us/library/ms524602%28VS.90%29.aspx


これも私にとってはうまくいきました。ハンドラ内にいる必要さえありませんでした。それでもページからアクセスできました。
象は

3
または、ASP.NETサーバーコンテキストでは、this.Request.ServerVariables ["ALL_RAW"];を使用します。
Peter Stegnar

Request.InputStreamからリクエストの本文を取得できません。毎回 ""が返されますが、ALL_RAWはリクエストヘッダーを返すのに適しているため、この答えは半分正解です。
ジャスティン

1
を使用HttpContext.Current.Requestして、MVCコントローラーやASPXページなどの外部で現在のコンテキストを取得することもできます。最初にnullでないことを確認してください;)
jocull

16

さて、私はプロジェクトに取り組んでおり、リクエストパラメータを使用してログを作成しました。

見てください:

public class LogAttribute : ActionFilterAttribute
{
    private void Log(string stageName, RouteData routeData, HttpContextBase httpContext)
    {
        //Use the request and route data objects to grab your data
        string userIP = httpContext.Request.UserHostAddress;
        string userName = httpContext.User.Identity.Name;
        string reqType = httpContext.Request.RequestType;
        string reqData = GetRequestData(httpContext);
        string controller = routeData["controller"];
        string action = routeData["action"];

        //TODO:Save data somewhere
    }

    //Aux method to grab request data
    private string GetRequestData(HttpContextBase context)
    {
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < context.Request.QueryString.Count; i++)
        {
            sb.AppendFormat("Key={0}, Value={1}<br/>", context.Request.QueryString.Keys[i], context.Request.QueryString[i]);
        }

        for (int i = 0; i < context.Request.Form.Count; i++)
        {
            sb.AppendFormat("Key={0}, Value={1}<br/>", context.Request.Form.Keys[i], context.Request.Form[i]);
        }

        return sb.ToString();
    }

コントローラクラスを装飾して完全にログに記録することができます。

[Log]
public class TermoController : Controller {...}

または、いくつかの個別のアクションメソッドのみをログに記録する

[Log]
public ActionResult LoggedAction(){...}

12

マネージコードで保持する必要がある理由は何ですか?

ホイールを再発明したくない場合は、IIS7でFailed Traceロギングを有効にできることに言及する価値があります。これは、ヘッダー、リクエストとレスポンスの本文、およびその他の多くのものをログに記録します。

失敗したトレースログ


それが失敗ではない場合はどうなりますか?
Sinaesthetic、2015

7
HTTP 200 OKでFailed Traceロギングを使用することもできるため、失敗以外のログを引き続き記録できます
JoelBellot

2
これは、最も簡単な解決策です。
Kehlan Krumme 2016

スタックトレース全体を表示するには、in GlobalConfiguration.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;の最後に追加する必要がありましたが、バージョンによって異なる場合があります。Register(..)WebApiConfig.cs
Evgeni Sergeev

8

McKAMEYのアプローチで行きました。ここに私が書いたモジュールがあります。これはあなたを始めて、うまくいけばあなたの時間を節約できるでしょう。ロガーをあなたのために働く何かと明らかに接続する必要があります:

public class CaptureTrafficModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += new EventHandler(context_BeginRequest);
        context.EndRequest += new EventHandler(context_EndRequest);
    }

    void context_BeginRequest(object sender, EventArgs e)
    {
        HttpApplication app = sender as HttpApplication;

        OutputFilterStream filter = new OutputFilterStream(app.Response.Filter);
        app.Response.Filter = filter;

        StringBuilder request = new StringBuilder();
        request.Append(app.Request.HttpMethod + " " + app.Request.Url);
        request.Append("\n");
        foreach (string key in app.Request.Headers.Keys)
        {
            request.Append(key);
            request.Append(": ");
            request.Append(app.Request.Headers[key]);
            request.Append("\n");
        }
        request.Append("\n");

        byte[] bytes = app.Request.BinaryRead(app.Request.ContentLength);
        if (bytes.Count() > 0)
        {
            request.Append(Encoding.ASCII.GetString(bytes));
        }
        app.Request.InputStream.Position = 0;

        Logger.Debug(request.ToString());
    }

    void context_EndRequest(object sender, EventArgs e)
    {
        HttpApplication app = sender as HttpApplication;
        Logger.Debug(((OutputFilterStream)app.Response.Filter).ReadStream());
    }

    private ILogger _logger;
    public ILogger Logger
    {
        get
        {
            if (_logger == null)
                _logger = new Log4NetLogger();
            return _logger;
        }
    }

    public void Dispose()
    {
        //Does nothing
    }
}

3
app.Response.Filterをストリーム以外に安全にキャストすることはできません。他のHttpModulesは、独自の応答フィルターをラップする場合があり、この場合、無効なキャスト例外が発生します。
Micah Zoltu 2013年

それはそうEncoding.UTF8ではありませんか、またはEncoding.Defaultリクエストストリームを読み取るときでしょうか?または単にStreamReader警告を
破棄して

5

OK、それで答えは「生データを取得できません。解析されたオブジェクトのプロパティからリクエスト/レスポンスを再構築する必要があります」のようです。まあ、私は復興のことをやった。


3
ServerVariables ["ALL_RAW"]に関するVineusのコメントを見ましたか?私はまだ試していませんが、クライアントから送信されたとおりの生のヘッダー情報を正確に返すように文書化されています。ドキュメントが間違っていることが判明し、再構築を行っている場合でも、ちょっと、無料で再構築します:-)
ジョナサンギルバート、

3

IHttpModuleを使用します

    namespace Intercepts
{
    class Interceptor : IHttpModule
    {
        private readonly InterceptorEngine engine = new InterceptorEngine();

        #region IHttpModule Members

        void IHttpModule.Dispose()
        {
        }

        void IHttpModule.Init(HttpApplication application)
        {
            application.EndRequest += new EventHandler(engine.Application_EndRequest);
        }
        #endregion
    }
}

    class InterceptorEngine
    {       
        internal void Application_EndRequest(object sender, EventArgs e)
        {
            HttpApplication application = (HttpApplication)sender;

            HttpResponse response = application.Context.Response;
            ProcessResponse(response.OutputStream);
        }

        private void ProcessResponse(Stream stream)
        {
            Log("Hello");
            StreamReader sr = new StreamReader(stream);
            string content = sr.ReadToEnd();
            Log(content);
        }

        private void Log(string line)
        {
            Debugger.Log(0, null, String.Format("{0}\n", line));
        }
    }

3
Alexと私自身の経験によれば、HttpResponse.OutputStreamから読み取ることができないので、ProcessResponseメソッドにログインするメソッドはおそらく機能しません。
ウィリアムグロス

2
ウィリアムは正しいです。HttpResponse.OutputStreamを読み取ることができません。HttpResponse.Filterを使用して、デフォルトの出力ストリームを独自のストリームに置き換えるソリューションを見つけました。
エリックファン

endurasoft.com/blog/post/…非同期Httpmoduleの方が良いですか?
PreguntonCojoneroCabrón

3

時折使用する場合、狭いコーナーを回避するために、以下のような粗雑なものはどうですか?

Public Function GetRawRequest() As String
    Dim str As String = ""
    Dim path As String = "C:\Temp\REQUEST_STREAM\A.txt"
    System.Web.HttpContext.Current.Request.SaveAs(path, True)
    str = System.IO.File.ReadAllText(path)
    Return str
End Function

1

これは、関数を使用して.NET 4.5の他の回答に記載されている方法DelegatingHandlerを使用しなくても実行できます。OutputFilterStream.CopyToAsync()

詳細はわかりませんが、応答ストリームを直接読み取ろうとしたときに発生するすべての問題が発生するわけではありません。

例:

public class LoggingHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        DoLoggingWithRequest(request);
        var response = await base.SendAsync(request, cancellationToken);
        await DoLoggingWithResponse(response);
        return response;
    }

    private async Task DologgingWithResponse(HttpResponseMessage response) {
        var stream = new MemoryStream();
        await response.Content.CopyToAsync(stream).ConfigureAwait(false);     
        DoLoggingWithResponseContent(Encoding.UTF8.GetString(stream.ToArray()));

        // The rest of this call, the implementation of the above method, 
        // and DoLoggingWithRequest is left as an exercise for the reader.
    }
}

0

マネージコードではないことはわかっていますが、ISAPIフィルターを提案します。自分のISAPIを維持する "喜び"を感じてから2年になりますが、ASP.Netが実行する前と後の両方で、これらすべてのものにアクセスできます。

http://msdn.microsoft.com/en-us/library/ms524610.aspx

HTTPModuleが必要なものに対して十分でない場合は、必要な詳細度でこれを行う管理された方法はないと思います。でもそれはやるのが面倒だ。




0

FigmentEngineに同意し、IHttpModule進むべき道のようです。

見てhttpworkerrequestreadentitybodyGetPreloadedEntityBody

これhttpworkerrequestを行うには、これを行う必要があります:

(HttpWorkerRequest)inApp.Context.GetType().GetProperty("WorkerRequest", bindingFlags).GetValue(inApp.Context, null);

どこinAppのHttpApplicationオブジェクトです。


1
私が求めた情報のほとんどがキャプチャされないため、回答は適切ではないとすでに述べました。この回答はどのように役立ちますか?
グレッグブナ

さらに説明してください、この回答はどのように役立ちますか?
PreguntonCojoneroCabrón

0

HttpRequestそしてHttpResponse事前MVCを持っていたGetInputStream()し、GetOutputStream()それがその目的のために使用することができます。MVCでそれらの部分を調べていないので、それらが使用可能かどうかはわかりませんが、アイデアかもしれません:)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.