Это мое сырым решение проблемы. Я до сих пор ищет лучшие решения ...
Включить 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);
}
}
}
Можете ли вы настроить прокси-сервер для приложения? # –
Возможно, я мог бы. Есть ли сквозная реализация прокси-сервера, который может фиксировать тайминги HTTP-запроса для .net? –