C# 如何将HttpWebRequests自动评测/计时到特定域?

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

我的C WebAPI使用HTTP请求与后端数据库Couchbase对话。我无法控制执行调用的实际库,因此我不能简单地从代码中计时,但我希望将调用的计时保存到数据库中,以用于SLA目的

/// <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类型的工具,它们可以让您深入了解这些信息,可能还有一些您没有想到的事情。