C# 如何将HttpWebRequests自动评测/计时到特定域?
我的C WebAPI使用HTTP请求与后端数据库Couchbase对话。我无法控制执行调用的实际库,因此我不能简单地从代码中计时,但我希望将调用的计时保存到数据库中,以用于SLA目的C# 如何将HttpWebRequests自动评测/计时到特定域?,c#,asp.net,asp.net-web-api,C#,Asp.net,Asp.net Web Api,我的C WebAPI使用HTTP请求与后端数据库Couchbase对话。我无法控制执行调用的实际库,因此我不能简单地从代码中计时,但我希望将调用的计时保存到数据库中,以用于SLA目的 /// <summary> /// Specialized collection that associates multiple keys with a single item. /// /// WARNING: Not production quality because it does not
/// <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);
}
}
}
有没有一种方法可以使用Net.Tracing或其他方法截获对特定域的HTTP调用,并保存调用的计时?类似于Chrome的“网络”选项卡提供的功能。我相信您正在寻找的是一个动作过滤器,您可以在官方asp.net网站上看到它的示例,网址为:
这是我对这个问题的粗略解决办法。我仍然在寻找更好的解决方案 在Web.config中启用系统.Net日志记录
<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实际上没有一个与同一请求相关的ID。您可以使用线程ID,但它会很快崩溃,因此我需要跟踪许多不同的对象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);
}
}
}
您能够为应用程序配置要使用的代理吗?我可能可以。是否有一个能够捕获.net http请求时间的代理的直通实现?在我的用例中,这是行不通的。我想分析我从WebAPI发出的一个或多个WebRequests,而不是衡量整个操作的性能。我认为还有其他选项可以通过http处理程序监视请求,并将其注入web.config,以便它正确地拦截每个请求。但我认为你也不想走这条路。我使用过一些工具,比如NewRelic或其他一些DevOps类型的工具,它们可以让您深入了解这些信息,可能还有一些您没有想到的事情。