2015-07-26 2 views
0

My C# WebAPI поддерживает базовую базу данных (Couchbase) с использованием HTTP-запросов. Я не контролирую фактическую библиотеку, которая выполняет вызовы, поэтому я не могу просто просто от нее времени от кода, но я бы хотел сохранить тайминги вызовов в базу данных для целей SLA.Как автоматически профиль/время HttpWebRequests в определенном домене?

Есть ли способ перехватить HTTP-вызовы в определенном домене с помощью Net.Tracing или что-то еще и сохранить тайминги вызовов? Нечто похожее на вкладку «Сеть» в Chrome.

+0

Можете ли вы настроить прокси-сервер для приложения? # –

+0

Возможно, я мог бы. Есть ли сквозная реализация прокси-сервера, который может фиксировать тайминги HTTP-запроса для .net? –

ответ

0

Это мое сырым решение проблемы. Я до сих пор ищет лучшие решения ...

Включить system.net регистрацию в Web.config

<system.diagnostics> 
    <trace autoflush="true" /> 

    <sources> 
     <source name="System.Net" maxdatasize="1024"> 
      <listeners> 
       <add name="NetTimingParserListener"/> 
      </listeners> 
     </source> 
    </sources> 

    <sharedListeners> 
      <add name="NetTimingParserListener" type="..." /> 
    </sharedListeners> 

    <switches> 
     <add name="System.Net" value="Verbose" /> 
    </switches> 
</system.diagnostics> 

Приведенный выше конфигурации будет включить ведение подробного журнала, который я захватит с пользовательской трассировки слушателя. Этот прослушиватель трассировки будет анализировать файлы журнала «на лету» и попытаться сопоставить сетевые события & хранить тайминги.

Далее я создал объект для хранения коррелированных событий и расчета времени между ними.

/// <summary> 
/// Measure and store status and timings of a given Network request. 
/// </summary> 
public class RequestTrace 
{ 
    private Stopwatch _timer = new Stopwatch(); 

    /// <summary> 
    ///  Initializes a new instance of the <see cref="Object" /> class. 
    /// </summary> 
    public RequestTrace(string id, Uri url) 
    { 
     Id = new Stack<string>(); 
     Id.Push(id); 
     Url = url; 
     IsFaulted = false; 
    } 

    /// <summary> 
    /// Any Id's that are associated with this request. Such as 
    /// HttpWebRequest, Connection, and associated Streams. 
    /// </summary> 
    public Stack<string> Id { get; set; } 

    /// <summary> 
    /// The Url of the request being made. 
    /// </summary> 
    public Uri Url { get; private set; } 

    /// <summary> 
    /// Time in ms for setting up the connection. 
    /// </summary> 
    public long ConnectionSetupTime { get; private set; } 

    /// <summary> 
    /// Time to first downloaded byte. Includes sending request headers, 
    /// body and server processing time. 
    /// </summary> 
    public long WaitingTime { get; private set; } 

    /// <summary> 
    /// Time in ms spent downloading the response. 
    /// </summary> 
    public long DownloadTime { get; private set; } 

    /// <summary> 
    /// True if the request encounters an error. 
    /// </summary> 
    public bool IsFaulted { get; private set; } 

    /// <summary> 
    /// Call this method when the request begins connecting to the server. 
    /// </summary> 
    public void StartConnection() 
    { 
     _timer.Start(); 
    } 

    /// <summary> 
    /// Call this method when the requst successfuly connects to the server. Otherwise, fall <see cref="Faulted"/>. 
    /// </summary> 
    public void StopConnection() 
    { 
     _timer.Stop(); 
     ConnectionSetupTime = _timer.ElapsedMilliseconds; 
     _timer.Reset(); 
    } 

    /// <summary> 
    /// Call this method after connecting to the server. 
    /// </summary> 
    public void StartWaiting() 
    { 
     _timer.Start(); 
    } 


    /// <summary> 
    /// Call this method after receiving the first byte of the HTTP server 
    /// response. 
    /// </summary> 
    public void StopWaiting() 
    { 
     _timer.Stop(); 
     WaitingTime = _timer.ElapsedMilliseconds; 
     _timer.Reset(); 
    } 

    /// <summary> 
    /// Call this method after receiving the first byte of the HTTP reponse. 
    /// </summary> 
    public void StartDownloadTime() 
    { 
     _timer.Start(); 
    } 

    /// <summary> 
    /// Call this method after the response is completely received. 
    /// </summary> 
    public void StopDownloadTime() 
    { 
     _timer.Stop(); 
     DownloadTime = _timer.ElapsedMilliseconds; 

     _timer = null; 
    } 

    /// <summary> 
    /// Call this method if an Exception occurs. 
    /// </summary> 
    public void Faulted() 
    { 
     DownloadTime = 0; 
     WaitingTime = 0; 
     ConnectionSetupTime = 0; 
     IsFaulted = true; 

     if (_timer.IsRunning) 
     { 
      _timer.Stop(); 
     } 
     _timer = null; 
    } 

    /// <summary> 
    ///  Returns a string that represents the current object. 
    /// </summary> 
    /// <returns> 
    ///  A string that represents the current object. 
    /// </returns> 
    public override string ToString() 
    { 
     return IsFaulted 
      ? String.Format("Request to node `{0}` - Exception", Url.DnsSafeHost) 
      : String.Format("Request to node `{0}` - Connect: {1}ms - Wait: {2}ms - Download: {3}ms", Url.DnsSafeHost, 
       ConnectionSetupTime, WaitingTime, DownloadTime); 
    } 
} 

У системы System.Net действительно нет единого идентификатора, который коррелирует с тем же запросом. Вы можете использовать идентификатор потока, но это будет быстро разрушаться, поэтому мне нужно было отслеживать множество разных объектов (HttpWebRequest, Connection, ConnectStream и т. Д.) И следовать, поскольку они связаны друг с другом в журнале. Я не знаю каких-либо встроенных типов .NET, которые позволяют иметь несколько ключей, сопоставленных с одним значением, поэтому я создал эту грубую коллекцию для своих целей.

/// <summary> 
/// Specialized collection that associates multiple keys with a single item. 
/// 
/// WARNING: Not production quality because it does not react well to dupliate or missing keys. 
/// </summary> 
public class RequestTraceCollection 
{ 
    /// <summary> 
    /// Internal dictionary for doing lookups. 
    /// </summary> 
    private readonly Dictionary<string, RequestTrace> _dictionary = new Dictionary<string, RequestTrace>(); 

    /// <summary> 
    /// Retrieve an item by <paramref name="key"/>. 
    /// </summary> 
    /// <param name="key">Any of the keys associated with an item</param> 
    public RequestTrace this[string key] 
    { 
     get { return _dictionary[key]; } 
    } 

    /// <summary> 
    /// Add an <paramref name="item"/> to the collection. The item must 
    /// have at least one string in the Id array. 
    /// </summary> 
    /// <param name="item">A RequestTrace object.</param> 
    public void Add(RequestTrace item) 
    { 
     _dictionary.Add(item.Id.Peek(), item); 
    } 

    /// <summary> 
    /// Given an <paramref name="item"/> in the collection, add another key 
    /// that it can be looked up by. 
    /// </summary> 
    /// <param name="item">Item that exists in the collection</param> 
    /// <param name="key">New key alias</param> 
    public void AddAliasKey(RequestTrace item, string key) 
    { 
     item.Id.Push(key); 
     _dictionary.Add(key, item); 
    } 

    /// <summary> 
    /// Remove an <paramref name="item"/> from the collection along with any 
    /// of its key aliases. 
    /// </summary> 
    /// <param name="item">Item to be removed</param> 
    public void Remove(RequestTrace item) 
    { 
     while (item.Id.Count > 0) 
     { 
      var key = item.Id.Pop(); 
      _dictionary.Remove(key); 
     } 
    } 
} 

И, наконец, речь шла о создании пользовательского TraceListener и анализе зарегистрированных сообщений.

public class HttpWebRequestTraceListener : TraceListener 
{ 
    private readonly RequestTraceCollection _activeTraces = new RequestTraceCollection(); 

    private readonly Regex _associatedConnection = 
     new Regex(@"^\[\d+\] Associating (Connection#\d+) with (HttpWebRequest#\d+)", 
      RegexOptions.Compiled & RegexOptions.IgnoreCase & RegexOptions.Singleline); 

    private readonly Regex _connected = new Regex(@"^\[\d+\] (ConnectStream#\d+) - Sending headers", 
     RegexOptions.Compiled & RegexOptions.IgnoreCase & RegexOptions.Singleline); 

    private readonly Regex _newRequest = 
     new Regex(@"^\[\d+\] (HttpWebRequest#\d+)::HttpWebRequest\(([http|https].+)\)", 
      RegexOptions.Compiled & RegexOptions.IgnoreCase & RegexOptions.Singleline); 

    private readonly Regex _requestException = new Regex(@"^\[\d+\] Exception in (HttpWebRequestm#\d+)::", 
     RegexOptions.Compiled & RegexOptions.IgnoreCase & RegexOptions.Singleline); 

    private readonly Regex _responseAssociated = 
     new Regex(@"^\[\d+\] Associating (HttpWebRequest#\d+) with (ConnectStream#\d+)", 
      RegexOptions.Compiled & RegexOptions.IgnoreCase & RegexOptions.Singleline); 

    private readonly Regex _responseComplete = new Regex(@"^\[\d+\] Exiting (ConnectStream#\d+)::Close\(\)", 
     RegexOptions.Compiled & RegexOptions.IgnoreCase & RegexOptions.Singleline); 

    private readonly Regex _responseStarted = new Regex(@"^\[\d+\] (Connection#\d+) - Received status line: (.*)", 
     RegexOptions.Compiled & RegexOptions.IgnoreCase & RegexOptions.Singleline); 

    /// <summary> 
    ///  When overridden in a derived class, writes the specified 
    ///  <paramref name="message" /> to the listener you create in the derived 
    ///  class. 
    /// </summary> 
    /// <param name="message">A message to write.</param> 
    public override void Write(string message) 
    { 
     // Do nothing here 
    } 

    /// <summary> 
    /// Parse the message being logged by System.Net and store relevant event information. 
    /// </summary> 
    /// <param name="message">A message to write.</param> 
    public override void WriteLine(string message) 
    { 
     var newRequestMatch = _newRequest.Match(message); 
     if (newRequestMatch.Success) 
     { 
      var requestTrace = new RequestTrace(newRequestMatch.Groups[1].Value, 
       new Uri(newRequestMatch.Groups[2].Value)); 
      requestTrace.StartConnection(); 

      _activeTraces.Add(requestTrace); 

      return; 
     } 

     var associatedConnectionMatch = _associatedConnection.Match(message); 
     if (associatedConnectionMatch.Success) 
     { 
      var requestTrace = _activeTraces[associatedConnectionMatch.Groups[2].Value]; 
      _activeTraces.AddAliasKey(requestTrace, associatedConnectionMatch.Groups[1].Value); 

      return; 
     } 

     var connectedMatch = _connected.Match(message); 
     if (connectedMatch.Success) 
     { 
      var requestTrace = _activeTraces[connectedMatch.Groups[1].Value]; 
      requestTrace.StopConnection(); 
      requestTrace.StartWaiting(); 

      return; 
     } 

     var responseStartedMatch = _responseStarted.Match(message); 
     if (responseStartedMatch.Success) 
     { 
      var requestTrace = _activeTraces[responseStartedMatch.Groups[1].Value]; 
      requestTrace.StopWaiting(); 
      requestTrace.StartDownloadTime(); 

      return; 
     } 

     var responseAssociatedMatch = _responseAssociated.Match(message); 
     if (responseAssociatedMatch.Success) 
     { 
      var requestTrace = _activeTraces[responseAssociatedMatch.Groups[1].Value]; 

      _activeTraces.AddAliasKey(requestTrace, responseAssociatedMatch.Groups[2].Value); 

      return; 
     } 

     var responseCompleteMatch = _responseComplete.Match(message); 
     if (responseCompleteMatch.Success) 
     { 
      var requestTrace = _activeTraces[responseCompleteMatch.Groups[1].Value]; 
      requestTrace.StopDownloadTime(); 

      _activeTraces.Remove(requestTrace); 

      // TODO: At this point the request is done, use this time to store & forward this log entry 
      Debug.WriteLine(requestTrace); 

      return; 
     } 

     var faultedMatch = _requestException.Match(message); 
     if (faultedMatch.Success) 
     { 
      var requestTrace = _activeTraces[responseCompleteMatch.Groups[1].Value]; 
      requestTrace.Faulted(); 

      _activeTraces.Remove(requestTrace); 

      // TODO: At this point the request is done, use this time to store & forward this log entry 
      Debug.WriteLine(requestTrace); 
     } 
    } 
} 
-1

Я считаю, что вы ищете это действие фильтр, вы можете увидеть пример этого на официальном сайте asp.net по адресу:

http://www.asp.net/mvc/overview/older-versions-1/controllers-and-routing/understanding-action-filters-cs

+0

Это не будет работать в моем случае использования. Я хочу профилировать один или несколько WebRequests, которые я делаю из своего WebAPI, не оценивать эффективность общего действия. –

+0

Я думаю, что есть другие варианты для мониторинга запроса через обработчики http и ввода его в ваш web.config, чтобы он правильно перехватывал каждый запрос. Но я не думаю, что это тот маршрут, по которому вы тоже захотите. Я использовал инструменты, такие как NewRelic или некоторые другие инструменты типа DevOps, которые помогут вам понять эту информацию и, возможно, некоторые вещи, о которых вы не думали. – Jonathan

Смежные вопросы