[HttpClient] Fix missing metric during network failures (#4098)

Co-authored-by: Cijo Thomas <cithomas@microsoft.com>
This commit is contained in:
Vishwesh Bankwar 2023-03-01 11:34:25 -08:00 committed by GitHub
parent 690f7e5bbc
commit 869dccee15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 66 additions and 56 deletions

View File

@ -2,6 +2,10 @@
## Unreleased
* Fixed an issue of missing `http.client.duration` metric data in case of
network failures (when response is not available).
([#4098](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4098))
## 1.0.0-rc9.14
Released 2023-Feb-24

View File

@ -28,6 +28,7 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation
internal const string OnStopEvent = "System.Net.Http.HttpRequestOut.Stop";
private readonly PropertyFetcher<HttpResponseMessage> stopResponseFetcher = new("Response");
private readonly PropertyFetcher<HttpRequestMessage> stopRequestFetcher = new("Request");
private readonly Histogram<double> httpClientDuration;
public HttpHandlerMetricsDiagnosticListener(string name, Meter meter)
@ -46,14 +47,11 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation
}
var activity = Activity.Current;
if (this.stopResponseFetcher.TryFetch(payload, out HttpResponseMessage response) && response != null)
if (this.stopRequestFetcher.TryFetch(payload, out HttpRequestMessage request) && request != null)
{
var request = response.RequestMessage;
TagList tags = default;
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpMethod, HttpTagHelper.GetNameForHttpMethod(request.Method)));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpScheme, request.RequestUri.Scheme));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpStatusCode, (int)response.StatusCode));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.Version)));
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeNetPeerName, request.RequestUri.Host));
@ -62,6 +60,11 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeNetPeerPort, request.RequestUri.Port));
}
if (this.stopResponseFetcher.TryFetch(payload, out HttpResponseMessage response) && response != null)
{
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpStatusCode, (int)response.StatusCode));
}
// We are relying here on HttpClient library to set duration before writing the stop event.
// https://github.com/dotnet/runtime/blob/90603686d314147017c8bbe1fa8965776ce607d0/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L178
// TODO: Follow up with .NET team if we can continue to rely on this behavior.

View File

@ -157,58 +157,59 @@ namespace OpenTelemetry.Instrumentation.Http.Tests
Assert.True(enrichWithExceptionCalled);
}
#if NETFRAMEWORK
Assert.Empty(requestMetrics);
#else
Assert.Single(requestMetrics);
var metric = requestMetrics[0];
Assert.NotNull(metric);
Assert.True(metric.MetricType == MetricType.Histogram);
var metricPoints = new List<MetricPoint>();
foreach (var p in metric.GetMetricPoints())
{
metricPoints.Add(p);
}
Assert.Single(metricPoints);
var metricPoint = metricPoints[0];
var count = metricPoint.GetHistogramCount();
var sum = metricPoint.GetHistogramSum();
Assert.Equal(1L, count);
Assert.Equal(activity.Duration.TotalMilliseconds, sum);
var attributes = new KeyValuePair<string, object>[metricPoint.Tags.Count];
int i = 0;
foreach (var tag in metricPoint.Tags)
{
attributes[i++] = tag;
}
var method = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpMethod, tc.Method);
var scheme = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpScheme, "http");
var statusCode = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpStatusCode, tc.ResponseCode == 0 ? 200 : tc.ResponseCode);
var flavor = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpFlavor, "2.0");
var hostName = new KeyValuePair<string, object>(SemanticConventions.AttributeNetPeerName, tc.ResponseExpected ? host : "sdlfaldfjalkdfjlkajdflkajlsdjf");
var portNumber = new KeyValuePair<string, object>(SemanticConventions.AttributeNetPeerPort, port);
Assert.Contains(hostName, attributes);
Assert.Contains(portNumber, attributes);
Assert.Contains(method, attributes);
Assert.Contains(scheme, attributes);
Assert.Contains(flavor, attributes);
if (tc.ResponseExpected)
{
#if NETFRAMEWORK
Assert.Empty(requestMetrics);
#else
Assert.Single(requestMetrics);
var metric = requestMetrics[0];
Assert.NotNull(metric);
Assert.True(metric.MetricType == MetricType.Histogram);
var metricPoints = new List<MetricPoint>();
foreach (var p in metric.GetMetricPoints())
{
metricPoints.Add(p);
}
Assert.Single(metricPoints);
var metricPoint = metricPoints[0];
var count = metricPoint.GetHistogramCount();
var sum = metricPoint.GetHistogramSum();
Assert.Equal(1L, count);
Assert.Equal(activity.Duration.TotalMilliseconds, sum);
var attributes = new KeyValuePair<string, object>[metricPoint.Tags.Count];
int i = 0;
foreach (var tag in metricPoint.Tags)
{
attributes[i++] = tag;
}
var method = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpMethod, tc.Method);
var scheme = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpScheme, "http");
var statusCode = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpStatusCode, tc.ResponseCode == 0 ? 200 : tc.ResponseCode);
var flavor = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpFlavor, "2.0");
var hostName = new KeyValuePair<string, object>(SemanticConventions.AttributeNetPeerName, host);
var portNumber = new KeyValuePair<string, object>(SemanticConventions.AttributeNetPeerPort, port);
Assert.Contains(method, attributes);
Assert.Contains(scheme, attributes);
Assert.Contains(statusCode, attributes);
Assert.Contains(flavor, attributes);
Assert.Contains(hostName, attributes);
Assert.Contains(portNumber, attributes);
Assert.Equal(6, attributes.Length);
#endif
}
else
{
Assert.Empty(requestMetrics);
Assert.DoesNotContain(statusCode, attributes);
Assert.Equal(5, attributes.Length);
}
#endif
}
[Fact]

View File

@ -90,35 +90,37 @@
{
"name": "Call that cannot resolve DNS will be reported as error span",
"method": "GET",
"url": "https://sdlfaldfjalkdfjlkajdflkajlsdjf.sdlkjafsdjfalfadslkf.com/",
"url": "http://sdlfaldfjalkdfjlkajdflkajlsdjf:{port}/",
"spanName": "HTTP GET",
"spanStatus": "Error",
"spanStatusHasDescription": true,
"responseExpected": false,
"recordException": false,
"spanAttributes": {
"http.scheme": "https",
"http.scheme": "http",
"http.method": "GET",
"net.peer.name": "sdlfaldfjalkdfjlkajdflkajlsdjf.sdlkjafsdjfalfadslkf.com",
"net.peer.name": "sdlfaldfjalkdfjlkajdflkajlsdjf",
"net.peer.port": "{port}",
"http.flavor": "{flavor}",
"http.url": "https://sdlfaldfjalkdfjlkajdflkajlsdjf.sdlkjafsdjfalfadslkf.com/"
"http.url": "http://sdlfaldfjalkdfjlkajdflkajlsdjf:{port}/"
}
},
{
"name": "Call that cannot resolve DNS will be reported as error span. And Records exception",
"method": "GET",
"url": "https://sdlfaldfjalkdfjlkajdflkajlsdjf.sdlkjafsdjfalfadslkf.com/",
"url": "http://sdlfaldfjalkdfjlkajdflkajlsdjf:{port}/",
"spanName": "HTTP GET",
"spanStatus": "Error",
"spanStatusHasDescription": true,
"responseExpected": false,
"recordException": true,
"spanAttributes": {
"http.scheme": "https",
"http.scheme": "http",
"http.method": "GET",
"net.peer.name": "sdlfaldfjalkdfjlkajdflkajlsdjf.sdlkjafsdjfalfadslkf.com",
"net.peer.name": "sdlfaldfjalkdfjlkajdflkajlsdjf",
"net.peer.port": "{port}",
"http.flavor": "{flavor}",
"http.url": "https://sdlfaldfjalkdfjlkajdflkajlsdjf.sdlkjafsdjfalfadslkf.com/"
"http.url": "http://sdlfaldfjalkdfjlkajdflkajlsdjf:{port}/"
}
},
{