ASP.NET MVC 및 IIS7에서 원시 HTTP 요청 / 응답 로깅


141

ASP.NET MVC를 사용하여 웹 서비스를 작성 중이며 지원 목적으로 가능한 원시 원시 형식 (예 : HTTP 포함)에 최대한 가깝게 요청 및 응답을 기록 할 수 있기를 원합니다. 메소드, 경로, 모든 헤더 및 본문)을 데이터베이스에 삽입하십시오.

내가 확실하지 않은 것은이 데이터를 최소한 '혼돈 된'방법으로 유지하는 방법입니다. HttpRequest객체 의 모든 속성을 검사하고 해당 객체에서 문자열을 작성 하여 요청에 대한 응답을 재구성 할 수는 있지만 실제 요청 / 응답 데이터를 잡고 싶습니다. 와이어로 보냈습니다.

필터, 모듈 등과 같은 차단 메커니즘을 사용하게되어 기쁩니다.이 솔루션은 IIS7에만 해당 될 수 있습니다. 그러나 관리 코드로만 유지하고 싶습니다.

어떤 추천?

편집 : I 노트 HttpRequest가 SaveAs디스크에 요청을 저장할 수있는 방법을하지만 공개적으로 (이에 저장 허용하지 않습니다 꽤 이유에 액세스 할 수 없습니다 내부 헬퍼 메소드의 부하를 사용하여 내부 상태의 요청을 재구성 사용자가 제공하는 스트림 모르겠습니다). 따라서 객체의 요청 / 응답 텍스트를 재구성하기 위해 최선을 다해야하는 것처럼 보이기 시작했습니다 ... 신음.

편집 2 : 메소드, 경로, 헤더 등을 포함한 전체 요청을 말했음을 유의하십시오 . 현재 응답은이 정보가 포함되지 않은 본문 스트림 만보 고합니다.

편집 3 : 아무도이 주변에서 질문을 읽지 않습니까? 지금까지 다섯 가지 답변이 있었지만 아직 전체 온-더-와이어 요청을 얻는 방법을 암시하는 사람은 없습니다. 예, 출력 스트림과 헤더, URL 및 요청 객체에서 URL 및 모든 내용을 캡처 할 수 있다는 것을 알고 있습니다. 나는 이미 질문에서 말했다 :

HttpRequest 객체의 모든 속성을 검사하고 그로부터 문자열을 작성하여 요청에 대한 응답을 재구성 할 수는 있지만 실제 요청 / 응답 데이터를 잡고 싶습니다. 유선으로 전송됩니다.

당신이 알고있는 경우에 전체 간단히 검색 할 수 없습니다 (등 헤더, URL, HTTP 방법 포함) 원시 데이터를 그 아는 것이 유용 할 것이다. 마찬가지로 모든 형식을 원시 형식으로 가져 오는 방법을 알고 있다면 (예, 헤더, URL, http 메소드 등을 포함하지 않고), 내가 요청한 것이므로 매우 유용합니다. 그러나 HttpRequest/ HttpResponse객체 에서 재구성 할 수 있다고 말하는 것은 유용하지 않습니다. 나도 알아 나는 이미 말했다.


참고 : 누군가 이것이 나쁜 생각이라고 말하거나 확장 성 등을 제한하기 전에 분산 환경에서 조절, 순차적 전달 및 재생 방지 메커니즘을 구현하므로 데이터베이스 로깅이 필요합니다. 나는 이것이 좋은 아이디어인지에 대한 토론을 찾고 있지 않다. 나는 그것이 어떻게 이루어질 수 있는지 찾고있다.


1
@Kev-아니요. ASP.NET MVC를 사용하여 구현 된 RESTful 서비스
— Greg Beech

IIS7과 네이티브 모듈을 사용하는 것이 가능할 것입니다 -msdn.microsoft.com/en-us/library/ms694280.aspx
— Daniel Crenna

이것을 구현 했습니까? 궁금한 점은 db에 쓸 버퍼 전략을 채택 했습니까?
— systempuntoout

1
재미있는 프로젝트 .. 최종 솔루션으로 끝내면?
— PreguntonCojoneroCabrón

답변:


91

를 사용 IHttpModule하고 BeginRequestand EndRequest이벤트를 구현하십시오 .

은 "원시"모든 데이터는 사이에 존재 HttpRequest하고 HttpResponse그것은 단지 하나의 원시 형식이 아닙니다. 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().

그런 다음 OutputFilterStreamDecorator 패턴을 스트림 주위의 래퍼로 사용하여 구현 하십시오.

/// <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
좋은 대답입니다. 그러나 한 의견은 "filter.ToString ()에 전체 응답 데이터를 가져올 수 있습니다"라고 말했습니다. -filter.ReadStream ()을 의미하지 않습니까? (C #이 아닌 vb.net에서 구현하고 있지만 ToString을 실행하면 클래스 이름을 문자열로 가져옵니다. .ReadStream은 원하는 응답 본문을 반환합니다.
— Adam

나는 좋은 대답에 동의합니다. 나는 그것을 사용자 정의 로거의 기초로 사용했지만 이제는 일부 헤더가 누락되어 IIS 압축을 사용할 때 가장 중요한 압축 최종 응답에 액세스 할 수없는 문제가 발생했습니다. 이에 대한 새로운 관련 질문 ( stackoverflow.com/questions/11084459/… )을 시작했습니다.
— Chris

2
나는 mckamey는 천재라고 생각합니다. 뛰어난 해결 방법이 필요하지 않고 지능적인 솔루션을 얻을 수 있도록 Microsoft를 위해 일할 수 있습니까?
— Abacus

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을 변경 하고 모든 곳에서 변경 HttpRequest해야 HttpRequestBase했습니다.
— Dmitry

35

ALL_RAW 서버 변수를 사용하여 요청과 함께 전송 된 원래 HTTP 헤더를 가져온 다음 평소와 같이 InputStream을 얻을 수 있습니다.

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

체크 아웃 : http://msdn.microsoft.com/en-us/library/ms524602%28VS.90%29.aspx


이것은 나에게도 효과가있었습니다. 핸들러에있을 필요조차 없었습니다. 여전히 페이지에서 액세스 할 수있었습니다.
— Helephant

3
또는 ASP.NET 서버 컨텍스트에서 다음을 사용하십시오. this.Request.ServerVariables [ "ALL_RAW"];
— Peter Stegnar

Request.InputStream에서 요청 본문을 가져올 수 없으며 매번 ""를 반환하지만 ALL_RAW는 요청 헤더를 반환하는 데 효과적 이므로이 답변은 절반입니다.
— Justin

1
HttpContext.Current.RequestMVC 컨트롤러, 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

7
HTTP 200 OK와 함께 Failed Trace 로깅을 사용할 수 있으므로 비 실패도 계속 기록 될 수 있습니다.
— JoelBellot

2
이것이 가장 간단한 솔루션입니다.
— Kehlan Krumme

전체 스택 추적을 참조하기 위해서는 추가 할 필요가 있었다 GlobalConfiguration.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;말 Register(..)에 WebApiConfig.cs,하지만이 버전 사이에 차이가있을 수 있습니다.
— Evgeni Sergeev

8

나는 McKAMEY의 접근 방식으로 갔다. 다음은 내가 작성한 모듈로 시작하고 시간을 절약 할 수 있도록 도와줍니다. Logger를 분명히 작동시키는 것으로 연결해야합니다.

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

그것은해야하지 Encoding.UTF8어쩌면, 또는 Encoding.Default요청 스트림을 읽을 때? 아니면 그냥 사용 StreamReader( 처분 사항에 유의을 )
— drzaus

5

대답은 "원시 데이터를 얻을 수 없으며 구문 분석 된 객체의 속성에서 요청 / 응답을 재구성해야합니다"라는 대답입니다. 잘, 나는 재건을했다.


3
ServerVariables [ "ALL_RAW"]에 대한 Vineus의 의견이 보입니까? 아직 직접 시도하지는 않았지만 클라이언트가 보낸 그대로 원시 헤더 정보를 반환하도록 문서화되어 있습니다. 문서가 잘못된 것으로 판명되어 재구성을하고있는 경우에도 자유 재건 :-)
— Jonathan Gilbert

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 메서드에 로깅하는 방법이 작동하지 않을 수 있습니다.
— William Gross

2
윌리엄이 맞아 HttpResponse.OutputStream을 읽을 수 없습니다. HttpResponse.Filter를 사용하고 기본 출력 스트림을 자신의 것으로 바꾸는 솔루션을 찾습니다.
— Eric Fan


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를 유지 관리하는 "즐거움"을 얻은 지 몇 년이 지났지 만 ASP.Net이 수행 한 전후에이 모든 것들에 액세스 할 수 있습니다.

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

HTTPModule이 필요한만큼 충분하지 않으면 필요한 양의 세부 사항으로이를 수행하는 관리 방법이 없다고 생각합니다. 그래도 고통 스러울 것입니다.



0

응용 프로그램 외부에서이 작업을 수행하는 것이 가장 좋습니다. 이와 같은 작업을 수행하기 위해 리버스 프록시를 설정할 수 있습니다. 리버스 프록시는 기본적으로 서버 룸에 있으며 웹 서버와 클라이언트 사이에있는 웹 서버입니다. http://en.wikipedia.org/wiki/Reverse_proxy 참조


0

FigmentEngine에 동의하십시오 IHttpModule.

에 봐 httpworkerrequest, readentitybody그리고 GetPreloadedEntityBody.

이를 수행하려면 httpworkerrequest다음을 수행하십시오.

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

inApphttpapplication 객체는 어디에 있습니까 ?


1
나는 이미 답변이 내가 요청한 대부분의 정보를 캡처하지 않기 때문에 적합하지 않다고 말했다. 이 답변이 어떤 식으로 도움이 되나요?
— Greg Beech

이 답변이 어떤 식으로 도움이 되나요?
— PreguntonCojoneroCabrón

0

HttpRequest그리고 HttpResponseMVC가 있었었다 사전 GetInputStream()과 GetOutputStream()그가 그 목적을 위해 사용될 수 있습니다. MVC에서 해당 부분을 보지 않았으므로 사용 가능한지 확실하지 않지만 아이디어 일 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.