-
Notifications
You must be signed in to change notification settings - Fork 5k
[API Proposal]: pass custom tags for metrics enrichment in HttpClientHandler #86281
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Tagging subscribers to this area: @dotnet/ncl Issue DetailsBackground and motivationAs 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. API Proposalnamespace System.Net.Http;
public class HttpRequestMessage : IDisposable
{
public ICollection<string, object?> CustomMetricsTags { get; }
} See the prototype for more info about how the custom tags will be used. API Usagepublic class EnricherHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage req, CancellationToken cancellationToken)
{
KeyValuePair<string, object?> tag = new("org-name", req.Headers.GetValue("x-org-name"));
req.CustomMetricTags.Add(tag);
var response = await base.SendAsync(req, cancellationToken);
return response;
}
} Alternative Designs
RisksNot all handlers will support metrics. Whether the initial implementation would work only with
|
Typo in the proposal. I'm guessing
Adding custom metrics would be something for power users. Potential users:
|
Moving to 8.0 to track this as part of #84978, feel free to bump back to Future |
Note that we have a precedent for downstream libraries defining extension methods to help using well-known keys: |
I agree with James that the scenario for arbitrary key/value pairs is aimed at power users and SDK/library authors, not the general audience that uses HttpClient. I don't think it needs to be overtly discoverable as a top-level property would be. Defining a convention for Options key name could work for R9, though it feels quite hard to discover for anyone else and leaves the door open for mistakes or weird incompatibilities like different library authors assuming they can store their own uniquely typed ICollection in there. I think a nice middle ground would be defining either a property or an extension method on the HttpRequestOptions. It looks like code elsewhere assumes that HttpRequestOptions can be reliably copied by enumerating and copying all the dictionary keys (https://source.dot.net/#Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs,160) so unless you planned to change that (including where user code might do the same thing) then I think we'd want the property backed by a dictionary entry rather than a field. I think the suggestion is close to your option (2) above but I am suggesting that the property/extension method be defined on the Options type, not on HttpRequestMessage to reduce its visibility. If the implementation of MetricsHandler is going to be in System.Net.Http then I'd put the API there too, not in M.E.Http, because I don't see any benefit in limiting it that way. All of this is just a suggestion though. If networking team or BCL design review believe one of the other options is better then go for it. I'll sleep fine with any of options you proposed. Hope that helps! |
I had a same thought recently. Updated the proposal, thanks for the feedback! |
One concern that came up in todays discussion that the cost of using @noahfalk @JamesNK @dpk83 do you have any insights whether R9 or other middleware use-cases would come with enrichment handlers wired-in by default or in the majority of use-cases? |
This API needs to support multiple places adding tags:
It should have a way to add and remove tags. |
@JamesNK I have updated the proposal to support these scenarios. |
Look at how much simpler it is: public class EnricherHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var tags = request.Options.GetMetricsTags();
tags.Add(new KeyValuePair<string, object>(name, value));
return await base.SendAsync(request, cancellationToken);
}
} Almost as simple as if there were a collection property on public class EnricherHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.MetricsTags.Add(new KeyValuePair<string, object>(name, value));
return await base.SendAsync(request, cancellationToken);
}
} |
Then the method would be responsible for choosing the underlying collection type which I wanted to avoid, but I will reconsider this option. I wonder what do others think? |
Why do you want to avoid that? |
I wanted Since I received a lot of pushback on this approach, I updated the proposal to expose a single |
Generally looks good as proposed. Ideally, this method will apply these tags to all metrics that are produced with "the request" (duration, size, etc) but probably not with "the client" (total number of requests, etc). If it's just cherry-picking individual metrics then it probably shouldn't be public API, as 3rd party callers wouldn't know when they can/can't add things sensibly. namespace System.Net.Http;
// Consider a Metrics-specific HttpRequestOptionsMetricsExtensions
public static class HttpRequestOptionsExtensions
{
// Returns the value assigned to the well known key if it's present:
// HttpRequestOptionsKey<ICollection<KeyValuePair<string, object?>>>("CustomMetricsTags")
// Creates a new collection if it doesn't. Throws if a collection is present but readonly.
// Alternative name ideas: GetMetricsEnrichmentTags, GetEnrichmentTags
public static ICollection<KeyValuePair<string, object?>> GetCustomMetricsTags(this HttpRequestOptions options);
} |
We moved the extension method to a static namespace System.Net.Http.Metrics;
public sealed class HttpMetricsEnrichmentContext
{
public HttpRequestMessage Request { get; }
public HttpResponseMessage? Response { get; } // null when an exception is thrown by the innermost handler
public Exception? Exception { get; } // null when no exception is thrown
public void AddCustomTag(string name, object? value);
public static void AddCallback(HttpRequestOptions options, Action<HttpMetricsEnrichmentContext> callback);
} |
public static void AddCallback(HttpRequestOptions options, Action<HttpMetricsEnrichmentContext> callback); The add callback method is no longer an extension method. Part of the reason to choose I propose a small change to make the public static void AddCallback(HttpRequestMessage message, Action<HttpMetricsEnrichmentContext> callback); Reasons:
|
+1 to James' comment |
Removing API approved tag and putting back api-ready-for-review. Change to consider: #86281 (comment) I don't know the runtime API review system. Hopefully this is the right way to do it. This isn't urgent. We can proceed with the current approved API and easily change it based on feedback. As long as it is considered before .NET 8 locks down. |
@antonfirsov perhaps we can do the incremental change review over email? |
Email sent. If it doesn't work out, I will open a new issue, since I don't want #87319 to be blocked on this. |
Uh oh!
There was an error while loading. Please reload this page.
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 inHttpClientHandler
/SocketsHttpHandler
, therefore the value of exposing a property onHttpRequestMessage
would be questionable. Instead, we can pass the custom tags usingHttpRequestMessage.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 overHttpRequestOptions
using an internal key.The API should provide an ability inject tags based on
HttpResponseMessage
. Since the metrics implementation will live withinHttpClientHandler
/SocketsHttpHandler
, it would be too late to do this in aDelegatingHandler
. 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
API Usage
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.The text was updated successfully, but these errors were encountered: