Skip to content

[API Proposal]: pass custom tags for metrics enrichment in HttpClientHandler #86281

Description

@antonfirsov

Latest update: switch to callback-based API to observe HttpResponseMessage for enrichment. Starting a new round of API discussion.

Background and motivation

As part of #84978, we need to enable enriching metrics logs with additional custom tags injected by the application/middleware. These tags need to be injected on per-request basis meaning that they should be provided in HttpRequestMessage. Enrichment is an advanced scenario, and metrics will be only implemented in HttpClientHandler/SocketsHttpHandler, therefore the value of exposing a property on HttpRequestMessage would be questionable. Instead, we can pass the custom tags using HttpRequestMessage.Options. The problem is that there are no good established practices to document and it's hard to discover discover it. Defining a key alone would also leave the door open to mistakes. As a compromise, I'm proposing an API that works via an extension method over HttpRequestOptions using an internal key.

The API should provide an ability inject tags based on HttpResponseMessage. Since the metrics implementation will live within HttpClientHandler/SocketsHttpHandler, it would be too late to do this in a DelegatingHandler. To handle this, the proposed API is passing user-provided callbacks to the metrics implementation which can the custom tags to be added for enrichment.

API Proposal

namespaceSystem.Net.Http.Metrics;publicsealedclassHttpMetricsEnrichmentContext{publicHttpRequestMessageRequest{get;}publicHttpResponseMessage?Response{get;}// null when an exception is thrown by the innermost handlerpublicException?Exception{get;}// null when no exception is thrownpublicvoidAddCustomTag(stringname,object?value);// Alternatively we can expose a collection to allow enumerating, removing in the future, but such functionality is not needed today.// public ICollection<KeyValuePair<string, object?>> CustomTags { get; }}// Or should it be MetricsHttpRequestOptionsExtensions? // runtime/libraries is not consistent on this, see HttpClientJsonExtensions vs OptionsServiceCollectionExtensionspublicstaticclassHttpRequestOptionsMetricsExtensions{publicstaticvoidAddMetricsEnrichmentCallback(thisHttpRequestOptionsoptions,Action<HttpMetricsEnrichmentContext>callback));}

API Usage

publicclassEnricherHandler:DelegatingHandler{protectedoverrideasyncTask<HttpResponseMessage>SendAsync(HttpRequestMessagerequest,CancellationTokencancellationToken){request.Options.AddMetricsEnrichmentCallback(static ctx =>{ctx.AddCustomTag("org-name",ctx.Request.Headers.GetValue("x-org-name"));ctx.AddCustomTag("result-category",GetResultCategory(ctx.Response.StatusCode));});returnawaitbase.SendAsync(request,cancellationToken);}privatestaticstringGetResultCategory(HttpStatusCodestatusCode)=>statusCode>=200&&statusCode<300?"Good":"Bad";}

Alternative Designs

Attempt to reduce heap allocations by working with value types

Note that the prerequisite for such an attempt to be successful is to resolve #83701, otherwise TagList would allocate in almost all R9 scenarios.

namespaceSystem.Net.Http.Metrics;publicreadonlystructHttpMetricsEnrichmentContext{publicreadonlyHttpRequestMessageRequest{get;}publicreadonlyHttpResponseMessage?Response{get;}publicreadonlyException?Exception{get;}}publicdelegatevoidHttpMetricsEnrichmentCallback(inHttpMetricsEnrichmentContextcontext,refTagListcustomTags);publicstaticclassHttpRequestOptionsMetricsExtensions{publicstaticvoidAddMetricsEnrichmentCallback(thisHttpRequestOptionsoptions,HttpMetricsEnrichmentCallbackcallback));}

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions