Skip to content

Commit 6f71b2e

Browse files
authored
Fix duplicate error.type on kestrel.connection.duration (#57561)
1 parent c4601ad commit 6f71b2e

File tree

2 files changed

+77
-11
lines changed

2 files changed

+77
-11
lines changed

src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelMetrics.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,15 @@ private void ConnectionStopCore(ConnectionMetricsContext metricsContext, Excepti
120120

121121
if (metricsContext.ConnectionDurationEnabled)
122122
{
123+
// Add custom tags for duration.
124+
if (customTags != null)
125+
{
126+
for (var i = 0; i < customTags.Count; i++)
127+
{
128+
tags.Add(customTags[i]);
129+
}
130+
}
131+
123132
// Check if there is an end reason on the context. For example, the connection could have been aborted by shutdown.
124133
if (metricsContext.ConnectionEndReason is { } reason && TryGetErrorType(reason, out var errorValue))
125134
{
@@ -130,15 +139,6 @@ private void ConnectionStopCore(ConnectionMetricsContext metricsContext, Excepti
130139
tags.TryAddTag(ErrorTypeAttributeName, exception.GetType().FullName);
131140
}
132141

133-
// Add custom tags for duration.
134-
if (customTags != null)
135-
{
136-
for (var i = 0; i < customTags.Count; i++)
137-
{
138-
tags.Add(customTags[i]);
139-
}
140-
}
141-
142142
var duration = Stopwatch.GetElapsedTime(startTimestamp, currentTimestamp);
143143
_connectionDuration.Record(duration.TotalSeconds, tags);
144144
}

src/Servers/Kestrel/test/Interop.FunctionalTests/Http2/Http2RequestTests.cs

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
using System.Net;
66
using System.Net.Http;
77
using System.Net.Http.Headers;
8+
using System.Net.Sockets;
89
using System.Security.Authentication;
910
using Microsoft.AspNetCore.Hosting;
1011
using Microsoft.AspNetCore.Http;
1112
using Microsoft.AspNetCore.Internal;
13+
using Microsoft.AspNetCore.InternalTesting;
1214
using Microsoft.AspNetCore.Server.Kestrel.Core;
1315
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
14-
using Microsoft.AspNetCore.InternalTesting;
1516
using Microsoft.Extensions.DependencyInjection;
16-
using Microsoft.Extensions.Diagnostics.Metrics;
1717
using Microsoft.Extensions.Diagnostics.Metrics.Testing;
1818
using Microsoft.Extensions.Hosting;
1919
using Microsoft.Extensions.Logging;
@@ -23,6 +23,72 @@ namespace Interop.FunctionalTests.Http2;
2323
[Collection(nameof(NoParallelCollection))]
2424
public class Http2RequestTests : LoggedTest
2525
{
26+
[Fact]
27+
public async Task InvalidHandshake_MetricsHasErrorType()
28+
{
29+
// Arrange
30+
var builder = CreateHostBuilder(
31+
c =>
32+
{
33+
return Task.CompletedTask;
34+
},
35+
protocol: HttpProtocols.Http2,
36+
plaintext: true);
37+
38+
using (var host = builder.Build())
39+
{
40+
var meterFactory = host.Services.GetRequiredService<IMeterFactory>();
41+
42+
// Use MeterListener for this test because we want to check that a single error.type tag is added.
43+
// MetricCollector can't be used for this because it stores tags in a dictionary and overwrites values.
44+
var measurementTcs = new TaskCompletionSource<Measurement<double>>();
45+
var meterListener = new MeterListener();
46+
meterListener.InstrumentPublished = (instrument, meterListener) =>
47+
{
48+
if (instrument.Meter.Scope == meterFactory &&
49+
instrument.Meter.Name == "Microsoft.AspNetCore.Server.Kestrel" &&
50+
instrument.Name == "kestrel.connection.duration")
51+
{
52+
meterListener.EnableMeasurementEvents(instrument);
53+
meterListener.SetMeasurementEventCallback<double>((Instrument instrument, double measurement, ReadOnlySpan<KeyValuePair<string, object>> tags, object state) =>
54+
{
55+
measurementTcs.SetResult(new Measurement<double>(measurement, tags));
56+
});
57+
}
58+
};
59+
meterListener.Start();
60+
61+
await host.StartAsync();
62+
var client = HttpHelpers.CreateClient(maxResponseHeadersLength: 1024);
63+
64+
// Act
65+
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
66+
socket.LingerState = new LingerOption(false, 0);
67+
68+
socket.Connect(IPAddress.Loopback, host.GetPort());
69+
socket.Send(new byte[1024 * 16]);
70+
71+
// Wait for measurement to be available.
72+
var measurement = await measurementTcs.Task.DefaultTimeout();
73+
74+
// Assert
75+
Assert.True(measurement.Value > 0);
76+
77+
var tags = measurement.Tags.ToArray();
78+
Assert.Equal("http", (string)tags.Single(t => t.Key == "network.protocol.name").Value);
79+
Assert.Equal("2", (string)tags.Single(t => t.Key == "network.protocol.version").Value);
80+
Assert.Equal("tcp", (string)tags.Single(t => t.Key == "network.transport").Value);
81+
Assert.Equal("ipv4", (string)tags.Single(t => t.Key == "network.type").Value);
82+
Assert.Equal("127.0.0.1", (string)tags.Single(t => t.Key == "server.address").Value);
83+
Assert.Equal(host.GetPort(), (int)tags.Single(t => t.Key == "server.port").Value);
84+
Assert.Equal("invalid_handshake", (string)tags.Single(t => t.Key == "error.type").Value);
85+
86+
socket.Close();
87+
88+
await host.StopAsync();
89+
}
90+
}
91+
2692
[Fact]
2793
public async Task GET_Metrics_HttpProtocolAndTlsSet()
2894
{

0 commit comments

Comments
 (0)