ASP.NET MVC ve IIS7'de ham HTTP isteği / yanıtı günlüğe kaydediliyor


141

Bir web hizmeti (ASP.NET MVC kullanarak) yazıyorum ve destek amaçlı olarak, istekleri ve yanıtı ham, çevrimiçi olarak (örneğin HTTP dahil) mümkün olduğunca yakın günlüğe kaydetmek istiyoruz yöntem, yol, tüm üstbilgiler ve gövde) bir veritabanına ekleyin.

Emin olmadığım, bu verilerin en az 'karışık' şekilde nasıl elde edileceğidir. HttpRequestNesnenin tüm özelliklerini inceleyerek ve onlardan bir dize (ve benzer şekilde yanıt için) oluşturarak isteğin neye benzediğine inandığımı yeniden oluşturabilirim , ancak gerçekten gerçek istek / yanıt verilerini elde etmek istiyorum tel üzerinde gönderdi.

Filtreler, modüller vb. Gibi herhangi bir müdahale mekanizmasını kullanmaktan mutluluk duyuyorum ve çözüm IIS7'ye özgü olabilir. Ancak, yalnızca yönetilen kodda tutmayı tercih ederim.

Herhangi bir tavsiye?

Düzenleme: Ben notu HttpRequestbir sahiptir SaveAsdiske isteği kaydedebilirsiniz yöntemi ama bu halka (bu kaydetme izin vermez oldukça neden erişilemiyor iç yardımcı yöntemler bir yük sonucu iç durumundan isteği reconstructs kullanıcı tarafından sağlanan Akış bilmiyorum). Bu yüzden nesnelerden gelen istek / yanıt metnini yeniden oluşturmak için elimden geleni yapmam gerekecek gibi görünüyor ... inilti.

Düzenleme 2: Yöntem, yol, başlıklar vb. Dahil tüm isteği söylediğimi lütfen unutmayın . Mevcut yanıtlar sadece bu bilgileri içermeyen vücut akışlarına bakmaktadır.

Edit 3: Burada kimse soru okumuyor mu? Şimdiye kadar beş cevap ve henüz hiç kimse tel üzerinden tüm ham talebi almanın bir yolunu bile ima etmiyor. Evet, çıktı akışlarını ve üstbilgileri, URL'yi ve istek nesnesinden tüm bunları yakalayabileceğimi biliyorum. Soruda zaten söyledim, bakınız:

HttpRequest nesnesinin tüm özelliklerini inceleyerek ve onlardan bir dize (ve benzer şekilde yanıt için) oluşturarak isteğin neye benzediğine inandığımı yeniden oluşturabilirim ama gerçekten gerçek istek / yanıt verisini almak istiyorum telde gönderilir.

Tüm ham verileri (başlıklar, url, http yöntemi vb. Dahil) biliyorsanız, sadece geri alınamaz. Benzer şekilde, tüm bunları ham formatta nasıl elde edeceğinizi biliyorsanız (evet, yine de yeniden yapılandırmak zorunda kalmadan başlıkları, url, http yöntemini vb. Demek istiyorum), sorduğum budur, o zaman bu çok yararlı olacaktır. Ama bana HttpRequest/ HttpResponsenesnelerinden yeniden yapılandırabileceğimi söylemek faydalı değil. Bunu biliyorum. Bunu zaten söyledim.


Lütfen dikkat: Herkes bunun kötü bir fikir olduğunu söylemeye başlamadan veya ölçeklenebilirliği vb. Sınırlandırmaya başlamadan önce, dağıtılmış bir ortamda daraltma, sıralı dağıtım ve yeniden oynatma mekanizmalarını da uygulayacağız, bu nedenle veritabanı günlüğü yine de gereklidir. Bunun iyi bir fikir olup olmadığı üzerine bir tartışma aramıyorum, bunun nasıl yapılabileceğini arıyorum.


1
@Kev - Hayır, ASP.NET MVC kullanılarak uygulanan RESTful bir hizmettir
Greg Beech

IIS7 ve yerel bir modül kullanarak yapmak mümkündür - msdn.microsoft.com/en-us/library/ms694280.aspx
Daniel Crenna

Bunu uygulamayı başardın mı? Sadece merak ediyorum, db'ye yazmak için herhangi bir tampon strateji benimsediniz mi?
systempuntoout

1
İlginç bir proje ... sonuçta son çözüm ile mi?
PreguntonCojoneroCabrón

Yanıtlar:


91

Kesinlikle bir kullanın IHttpModuleve uygulayın BeginRequestve EndRequestolaylar.

Tüm "ham" veriler arasında bulunur HttpRequestve HttpResponseyalnızca tek bir ham biçimde değildir. Fiddler tarzı dökümleri oluşturmak için gerekli olan kısımlar (yaklaşık olarak ham HTTP'ye olduğu kadar yakın):

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

Yanıt için:

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

O Not Eğer yanıt akışı okuyamaz sen Çıktı akışına bir filtre eklemek ve bir kopyasını yakalamak zorunda.

Bölümünde, BeginRequestbir yanıt filtresi eklemeniz gerekecek:

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

Mağaza filterİçinde binebilirsek EndRequestişleyicisi. Ben öneririm HttpContext.Items. Daha sonra yanıt verilerinin tamamını alabilirsiniz filter.ReadStream().

Ardından OutputFilterStreamDekoratör desenini bir akışın etrafına sarıcı olarak kullanarak uygulayın :

/// <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
Güzel cevap. Yine de bir yorum: "filter.ToString () içinde yanıt verilerinin tamamını alabilirsiniz" dediniz. - filter.ReadStream () demek istemiyor musunuz? (Ben c # vb.net içinde uygulamak ama ben ToString Sadece bir dize olarak sınıf adını almak .ReadStream istenen tepki gövdesini döndürür çalıştırırsanız..
Adam

Kabul ediyorum, iyi cevap. Özel bir günlükçü için temel olarak kullandım ama şimdi bazı üstbilgilerin eksik olduğu bir sorunla karşılaştım ve en önemlisi IIS sıkıştırmasını kullanırken son sıkıştırılmış yanıta erişemiyorum. Bunun için yeni bir ilgili soru ( stackoverflow.com/questions/11084459/… ) başlattım .
Chris

2
Bence mckamey bir dahi. Microsoft için işe gidebilir misiniz, böylece mükemmel geçici çözümlere ihtiyaç duymak yerine akıllı çözümler elde edebiliriz?
Abaküs

4
Request.RawUrl bir istek doğrulama istisnasını tetikleyebilir. Bunu önlemek için 4.5'te request.Unvalidated.RawUrl kullanabilirsiniz. 4.0'da
Request'i

1
@mckamey Çözümünüzü Application_BeginRequest ve Application_EndRequest ile global.asax'ımda uygulamaya çalışıyorum, ancak EndRequest'ta hangi kodu yazmam gerektiğinden emin değilim, cevap plz'nize bir örnek verebilir misiniz?
Jerome2606

48

HttpRequest'te aşağıdaki genişletme yöntemi, kemancıya yapıştırılabilen ve yeniden oynatılabilen bir dize oluşturur.

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
Çok iyi kod! Ama bunun için MVC 4 I ile çalışmak için sınıf adını değiştirmek zorunda HttpRequestBaseExtensionsve değişime HttpRequestkarşı HttpRequestBaseher yerde.
Dmitry

35

İstekle gönderilen orijinal HTTP başlıklarını almak için ALL_RAW sunucu değişkenini kullanabilirsiniz, daha sonra her zamanki gibi InputStream'i alabilirsiniz:

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

çıkış: http://msdn.microsoft.com/tr-tr/library/ms524602%28VS.90%29.aspx


Bu benim için de işe yaradı. Bir işleyicide olmasına bile gerek yoktu. Yine de sayfadan erişebildim.
Helephant

3
Veya ASP.NET sunucusu bağlamında şunu kullanın: this.Request.ServerVariables ["ALL_RAW"];
Peter Stegnar

İstek gövdesini Request.InputStream öğesinden alamıyorum, her zaman benim için "" döndürüyor, ancak ALL_RAW istek üstbilgilerini döndürmek için harika çalışıyor, bu nedenle bu cevap doğru.
Justin

1
Ayrıca kullanabilirsiniz HttpContext.Current.Requestsadece emin ilk boş değil yapmak ... MVC denetleyicileri, ASPX sayfaları, vb mevcut bağlam dışında kapmak için;)
jocull

16

Ben bir proje üzerinde çalışıyorum ve belki de çok derin olmayan, istek parametrelerini kullanarak bir günlük yaptım:

Bir göz at:

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();
    }

Denetleyiciler sınıfınızı tamamen günlüğe kaydetmek için dekore edebilirsiniz:

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

veya yalnızca bazı bireysel işlem yöntemlerini günlüğe kaydetme

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

12

Yönetilen kodda tutmanız için herhangi bir neden var mı?

Tekerleği yeniden icat etmek istemiyorsanız, IIS7'de Başarısız İzleme günlüğünü etkinleştirebileceğinizi belirtmek gerekir . Bu, başlıkları, istek ve yanıt gövdesini ve diğer birçok şeyi günlüğe kaydeder.

İzleme Günlüğü Başarısız


Ya bir başarısızlık değilse?
Sinaesthetic

7
Başarısız İzleme günlüğünü HTTP 200 OK ile de kullanabilirsiniz, böylece hatalar hala kaydedilebilir
JoelBellot

2
Bu açık ara en basit çözüm.
Kehlan Krumme

Bütün yığın izleme görmek, eklenmesi gerekmiştir GlobalConfiguration.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;sonunda Register(..)in WebApiConfig.cs, ancak bu alternatifler arasında değişebilir.
Evgeni Sergeev

8

McKAMEY'in yaklaşımıyla gittim. İşte size başlamak ve umarım biraz zaman kazandıracak yazdığım bir modül. Logger'ı sizin için çalışan bir şeye açıkça takmanız gerekir:

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
Bir akışı dışında hiçbir şeye güvenli bir şekilde app.Response.Filter uygulayamazsınız. Diğer HttpModules yanıt filtrenizi kendileriyle kaplayabilir ve bu durumda geçersiz bir döküm istisnası alırsınız.
Micah Zoltu

Olmamalı mı Encoding.UTF8, yoksa Encoding.Defaultistek akışını okurken mi? Veya sadece bir StreamReader(
uyarma

5

Tamam, öyle görünüyor ki cevap "ham verileri alamıyorsunuz, ayrıştırılmış nesnelerin özelliklerinden istek / yanıtı yeniden yapılandırmanız gerekiyor". Ah, yeniden yapılanma işini yaptım.


3
Vineus'un ServerVariables ["ALL_RAW"] hakkındaki yorumunu gördünüz mü? Henüz kendim denemedim, ancak ham başlık bilgilerini tam olarak istemci tarafından gönderildiği gibi döndürmek için belgelenmiştir. Belge yanlış olsa bile ve bu bir yeniden yapılanma yapıyor, hey, ücretsiz yeniden yapılanma :-)
Jonathan Gilbert

3

bir IHttpModule kullanın :

    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 ve kendi deneyimime göre, HttpResponse.OutputStream'den okuyabileceğinizi düşünmüyorum, bu nedenle ProcessResponse yönteminde oturum açma yönteminiz muhtemelen çalışmaz.
William Gross

2
William haklı. HttpResponse.OutputStream okunamıyor. HttpResponse.Filter kullanmak ve varsayılan çıkış akışı kendi ile değiştirmek için bir çözüm bulmak.
Eric Fan

endurasoft.com/blog/post/… async Httpmodule daha iyi?
57

3

ara sıra kullanmak için, sıkı bir köşeyi aşmak için, aşağıdaki gibi kaba bir şeye ne dersiniz?

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

Bir de gerçekleştirebilirsiniz DelegatingHandlerkullanmadan OutputFilterkullanarak .NET 4.5 diğer cevaplar belirtilen Stream.CopyToAsync()işlevi.

Ayrıntılardan emin değilim, ancak doğrudan yanıt akışını okumaya çalıştığınızda meydana gelen tüm kötü şeyleri tetiklemiyor.

Misal:

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

Yönetilen kod olmadığını biliyorum, ama bir ISAPI filtresi önereceğim. Kendi ISAPI'mi korumanın "zevkini" yaşadığımdan beri birkaç yıl geçirdim, ancak hatırladığım kadarıyla, ASP.Net'in yapmasından önce ve sonra tüm bu şeylere erişebilirsiniz.

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

Bir HTTPModule ihtiyacınız olan şey için yeterince iyi değilse, gerekli miktarda ayrıntıyla bunu yapmanın herhangi bir yönetilen yolu olduğunu düşünmüyorum. Yine de bunu yapmak bir acı olacak.



0

Bunu uygulamanızın dışında yapmak en iyisi olabilir. Bu gibi şeyleri (ve çok daha fazlasını) yapmak için bir ters proxy ayarlayabilirsiniz. Ters proxy temel olarak sunucu odanızda bulunan ve web sunucularınız ile istemci arasında duran bir web sunucusudur. Bkz. Http://en.wikipedia.org/wiki/Reverse_proxy


0

FigmentEngine ile aynı fikirde, IHttpModulegidilecek yol gibi görünüyor.

Bakın httpworkerrequest, readentitybodyve GetPreloadedEntityBody.

Bunu elde etmek httpworkerrequestiçin bunu yapmanız gerekir:

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

inApphttpapplication nesnesi nerede .


1
Zaten cevabın uygun olmadığını söyledim çünkü istediğim bilgilerin çoğunu yakalamıyor. Bu cevap herhangi bir şekilde nasıl yardımcı olur?
Greg Beech

Daha fazla açıklama, Bu cevap herhangi bir şekilde nasıl yardımcı olur?
PreguntonCojoneroCabrón

0

HttpRequestve HttpResponseMVC öncesi bu amaç için kullanılabilecek bir GetInputStream()ve GetOutputStream()daha fazlasına sahipti. MVC bu bölüme bakmadım bu yüzden availavle olduğundan emin değilim ama bir fikir olabilir :)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.