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


141

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

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

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

어떤 추천?

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

편집 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.