Skip to content

Instantly share code, notes, and snippets.

@JamesNK
Last active June 19, 2023 23:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JamesNK/59e1ab82f69cf25490a23383a6792c3d to your computer and use it in GitHub Desktop.
Save JamesNK/59e1ab82f69cf25490a23383a6792c3d to your computer and use it in GitHub Desktop.
System.Net.Http metrics enrichment with callbacks

System.Net.Http:

// Need to support adding multiple callbacks to support adding metrics from multiple places:
// 1. Creating HttpRequestMessage
// 2. During HTTP request pipeline in delegating handlers
// 3. In diaganostic event callbacks
public static class HttpOptionsExtensions
{
    public static void AddCustomMetricsTagsCallback(
       this HttpOptions options, Action<HttpCustomMetricsContext> callback);
       
    // GetCustomMetricsTags method is removed.
}

// A context type allows new arguments to be added in the future if required.
// There is an extra allocation, but it's small, once per-request, and only required if metrics are enabled.
public sealed class HttpCustomMetricsTagsContext
{
    public HttpRequestMessage Request { get; init; }
    public HttpResponseMessage Response { get; init; }

    // The tags collection could be TagList or ICollection<KeyValuePair<string, object?>
    public TagList Tags { get; init; }
}

Microsoft.Extensions.Http:

// Internally the client factory will just register an internal delegating handler.
// The delegating handler adds the registered callbacks to the HttpRequestMessage.
public static class HttpClientBuilderExtensions
{
    public static void AddCustomMetricsTagsCallback(
       this IHttpClientBuilder builder, Action<HttpCustomMetricsContext> context);
    public static void AddCustomMetricsTagsCallback(
       this IHttpClientBuilder builder, Action<IServiceProvider, HttpCustomMetricsContext> context);
}

Usage:

HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, "https://www.google.com");
message.Options.AddCustomMetricsTagsCallback(c =>
{
    var value = c.Response.Headers.GetValue("x-contoso-header");
    c.Tags.Add("contoso-header", value);
});

And internal custom handlers can add a callback like this:

public class DelegatingHandler(HttpMessageHandler inner) : base(inner)
{
    public Task<HttpResponseMessage> SendAsync(HttpRequestMessage message)
    {
        message.Options.AddCustomMetricsTagsCallback(c =>
        {
            var value = c.Request.Headers.GetValue("x-retry-count");
            c.Tags.Add("retry-count", value);
        });
        return base.SendAsync(message);
    }
}

And HttpClientFactory users can add callbacks like this:

services
    .AddHttpClient("GitHubClient")
    .ConfigureHttpClient(httpClient =>
    {
        httpClient.BaseAddress = new Uri("https://api.github.com");
    })
    .AddCustomMetricsTagsCallback(c =>
    {
        message.Options.AddCustomMetricsTagsCallback(c =>
        {
            var value = c.Request.Headers.GetValue("authorize");
            c.Tags.Add("request-authorized", value != null);
        });
    });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment