Skip to content

[API Proposal]: Pass Meter to HttpClientHandler and SocketsHttpHandler #86961

@antonfirsov

Description

@antonfirsov

Background and motivation

This is part of #84978. For testability, we need to enable passing a custom Meter to SocketsHttpHandler and HttpClientHandler. Since IMeterFactory lives in Microsoft.Extensions.Diagnostics.Abstractions, we cannot use it in System.Net.Http.

API Proposal

I'm proposing to expose a property to set the Meter instance directly.

namespace System.Net.Http;

public class HttpClientHandler
{
    public Meter Meter { get; set; } // = DefaultGlobalMeterForHttpClient;
}

public class SocketsHttpHandler
{
    public Meter Meter { get; set; } // = DefaultGlobalMeterForHttpClient;
}

The downside of this API is that it makes SocketsHttpHandler's and HttpClientHandler's responsibility to enforce the correct meter name in the property setter:

https://github.com/antonfirsov/runtime/blob/5abf04854dbb6c2fca458663a4affb580e727ec8/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs#L458-L468

Also, the ownership semantics are somewhat counterintuitive, since SocketsHttpHandler.Dispose() should not dispose it's Meter instance.

API Usage

using Meter meter = new Meter("System.Net.Http");
using HttpClient client = new(new HttpClientHandler()
{
    Meter = meter
});

// Use the client with the custom meter
// Because the recorder has the meter instance we care about, only values from this meter are captured.
var instrumentRecorder = new InstrumentRecorder<double>(meter, "http-client-request-duration");

// There is another InstrumentRecorder constructor that takes the meter name. If the meter name was used, "System.Net.Http",
// then all counter values with that meter name would be collected.
// var globalInstrumentRecorder = new InstrumentRecorder<double>(System.Net.Http", "http-client-request-duration");

// Make HTTP request.
var response = await client.GetAsync("https://www.bing.com");
var data = await response.Content.ReadAsBytesAsync();

// Assert recorded "http-client-request-duration" value and check the "status-code" tag.
Assert.Collection(instrumentRecorder.GetMeasurement(),
    m => Assert.Equals(200, (int)m.Tags.ToArray().Single(t => t.Name == "status-code"));

Alternative Designs

  • Instead of exposing the Meter directly, expose Func<MeterOptions, Meter>? instead.
  • Move IMeterFactory to System.Diagnostics.Metrics and expose IMeterFactory instead of exposing the Meter.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions