Skip to content

Commit e6afd50

Browse files
authored
Add System.Diagnostics.TextMapPropagator support (#33777)
* Initial prototype * Fix tests * Remove closure allocation * Resolve propagator from DI * Fix tests * Update WebHostBuilder.cs * s/TextMapPropagator/DistributedContextPropagator * Remove unused files * Remove unsafeblocks in hosting * HttpContext.Request.Headers can't be null
1 parent 8792f3a commit e6afd50

File tree

8 files changed

+51
-41
lines changed

8 files changed

+51
-41
lines changed

src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options
8787
services.TryAddSingleton(sp => new DiagnosticListener("Microsoft.AspNetCore"));
8888
services.TryAddSingleton<DiagnosticSource>(sp => sp.GetRequiredService<DiagnosticListener>());
8989
services.TryAddSingleton(sp => new ActivitySource("Microsoft.AspNetCore"));
90+
services.TryAddSingleton(DistributedContextPropagator.Current);
9091

9192
services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>();
9293
services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>();

src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,25 @@ namespace Microsoft.AspNetCore.Hosting
2121
{
2222
internal sealed partial class GenericWebHostService : IHostedService
2323
{
24-
public GenericWebHostService(
25-
IOptions<GenericWebHostServiceOptions> options,
26-
IServer server,
27-
ILoggerFactory loggerFactory,
28-
DiagnosticListener diagnosticListener,
29-
ActivitySource activitySource,
30-
IHttpContextFactory httpContextFactory,
31-
IApplicationBuilderFactory applicationBuilderFactory,
32-
IEnumerable<IStartupFilter> startupFilters,
33-
IConfiguration configuration,
34-
IWebHostEnvironment hostingEnvironment)
24+
public GenericWebHostService(IOptions<GenericWebHostServiceOptions> options,
25+
IServer server,
26+
ILoggerFactory loggerFactory,
27+
DiagnosticListener diagnosticListener,
28+
ActivitySource activitySource,
29+
DistributedContextPropagator propagator,
30+
IHttpContextFactory httpContextFactory,
31+
IApplicationBuilderFactory applicationBuilderFactory,
32+
IEnumerable<IStartupFilter> startupFilters,
33+
IConfiguration configuration,
34+
IWebHostEnvironment hostingEnvironment)
3535
{
3636
Options = options.Value;
3737
Server = server;
3838
Logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Hosting.Diagnostics");
3939
LifetimeLogger = loggerFactory.CreateLogger("Microsoft.Hosting.Lifetime");
4040
DiagnosticListener = diagnosticListener;
4141
ActivitySource = activitySource;
42+
Propagator = propagator;
4243
HttpContextFactory = httpContextFactory;
4344
ApplicationBuilderFactory = applicationBuilderFactory;
4445
StartupFilters = startupFilters;
@@ -53,6 +54,7 @@ public GenericWebHostService(
5354
public ILogger LifetimeLogger { get; }
5455
public DiagnosticListener DiagnosticListener { get; }
5556
public ActivitySource ActivitySource { get; }
57+
public DistributedContextPropagator Propagator { get; }
5658
public IHttpContextFactory HttpContextFactory { get; }
5759
public IApplicationBuilderFactory ApplicationBuilderFactory { get; }
5860
public IEnumerable<IStartupFilter> StartupFilters { get; }
@@ -116,7 +118,7 @@ public async Task StartAsync(CancellationToken cancellationToken)
116118
application = ErrorPageBuilder.BuildErrorPageApplication(HostingEnvironment.ContentRootFileProvider, Logger, showDetailedErrors, ex);
117119
}
118120

119-
var httpApplication = new HostingApplication(application, Logger, DiagnosticListener, ActivitySource, HttpContextFactory);
121+
var httpApplication = new HostingApplication(application, Logger, DiagnosticListener, ActivitySource, Propagator, HttpContextFactory);
120122

121123
await Server.StartAsync(httpApplication, cancellationToken);
122124

src/Hosting/Hosting/src/Internal/HostingApplication.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ public HostingApplication(
2424
ILogger logger,
2525
DiagnosticListener diagnosticSource,
2626
ActivitySource activitySource,
27+
DistributedContextPropagator propagator,
2728
IHttpContextFactory httpContextFactory)
2829
{
2930
_application = application;
30-
_diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource, activitySource);
31+
_diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource, activitySource, propagator);
3132
if (httpContextFactory is DefaultHttpContextFactory factory)
3233
{
3334
_defaultHttpContextFactory = factory;

src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,9 @@
77
using System.Diagnostics;
88
using System.Globalization;
99
using System.Runtime.CompilerServices;
10-
using System.Web;
1110
using Microsoft.AspNetCore.Http;
1211
using Microsoft.AspNetCore.Http.Features;
1312
using Microsoft.Extensions.Logging;
14-
using Microsoft.Extensions.Primitives;
15-
using Microsoft.Net.Http.Headers;
1613

1714
namespace Microsoft.AspNetCore.Hosting
1815
{
@@ -31,13 +28,19 @@ internal class HostingApplicationDiagnostics
3128

3229
private readonly ActivitySource _activitySource;
3330
private readonly DiagnosticListener _diagnosticListener;
31+
private readonly DistributedContextPropagator _propagator;
3432
private readonly ILogger _logger;
3533

36-
public HostingApplicationDiagnostics(ILogger logger, DiagnosticListener diagnosticListener, ActivitySource activitySource)
34+
public HostingApplicationDiagnostics(
35+
ILogger logger,
36+
DiagnosticListener diagnosticListener,
37+
ActivitySource activitySource,
38+
DistributedContextPropagator propagator)
3739
{
3840
_logger = logger;
3941
_diagnosticListener = diagnosticListener;
4042
_activitySource = activitySource;
43+
_propagator = propagator;
4144
}
4245

4346
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -279,38 +282,37 @@ private static void RecordRequestStartEventLog(HttpContext httpContext)
279282
{
280283
return null;
281284
}
282-
283285
var headers = httpContext.Request.Headers;
284-
var requestId = headers.TraceParent;
285-
if (requestId.Count == 0)
286-
{
287-
requestId = headers.RequestId;
288-
}
289-
290-
if (!StringValues.IsNullOrEmpty(requestId))
286+
_propagator.ExtractTraceIdAndState(headers,
287+
static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable<string>? fieldValues) =>
288+
{
289+
fieldValues = default;
290+
var headers = (IHeaderDictionary)carrier!;
291+
fieldValue = headers[fieldName];
292+
},
293+
out var requestId,
294+
out var traceState);
295+
296+
if (!string.IsNullOrEmpty(requestId))
291297
{
292298
activity.SetParentId(requestId);
293-
var traceState = headers.TraceState;
294-
if (traceState.Count > 0)
299+
if (!string.IsNullOrEmpty(traceState))
295300
{
296301
activity.TraceStateString = traceState;
297302
}
298-
299-
// We expect baggage to be empty by default
300-
// Only very advanced users will be using it in near future, we encourage them to keep baggage small (few items)
301-
var baggage = headers.GetCommaSeparatedValues(HeaderNames.Baggage);
302-
if (baggage.Length == 0)
303+
var baggage = _propagator.ExtractBaggage(headers, static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable<string>? fieldValues) =>
303304
{
304-
baggage = headers.GetCommaSeparatedValues(HeaderNames.CorrelationContext);
305-
}
305+
fieldValues = default;
306+
var headers = (IHeaderDictionary)carrier!;
307+
fieldValue = headers[fieldName];
308+
});
306309

307-
// AddBaggage adds items at the beginning of the list, so we need to add them in reverse to keep the same order as the client
308-
// An order could be important if baggage has two items with the same key (that is allowed by the contract)
309-
for (var i = baggage.Length - 1; i >= 0; i--)
310+
// Order could be important if baggage has two items with the same key (that is allowed by the contract)
311+
if (baggage is not null)
310312
{
311-
if (NameValueHeaderValue.TryParse(baggage[i], out var baggageItem))
313+
foreach (var baggageItem in baggage)
312314
{
313-
activity.AddBaggage(baggageItem.Name.ToString(), HttpUtility.UrlDecode(baggageItem.Value.ToString()));
315+
activity.AddBaggage(baggageItem.Key, baggageItem.Value);
314316
}
315317
}
316318
}

src/Hosting/Hosting/src/Internal/WebHost.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,9 @@ public async Task StartAsync(CancellationToken cancellationToken = default)
157157

158158
var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticListener>();
159159
var activitySource = _applicationServices.GetRequiredService<ActivitySource>();
160+
var propagator = _applicationServices.GetRequiredService<DistributedContextPropagator>();
160161
var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
161-
var hostingApp = new HostingApplication(application, _logger, diagnosticSource, activitySource, httpContextFactory);
162+
var hostingApp = new HostingApplication(application, _logger, diagnosticSource, activitySource, propagator, httpContextFactory);
162163
await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);
163164
_startedServer = true;
164165

src/Hosting/Hosting/src/WebHostBuilder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ private IServiceCollection BuildCommonServices(out AggregateException? hostingSt
293293
services.TryAddSingleton(sp => new DiagnosticListener("Microsoft.AspNetCore"));
294294
services.TryAddSingleton<DiagnosticSource>(sp => sp.GetRequiredService<DiagnosticListener>());
295295
services.TryAddSingleton(sp => new ActivitySource("Microsoft.AspNetCore"));
296+
services.TryAddSingleton(DistributedContextPropagator.Current);
296297

297298
services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
298299
services.AddTransient<IHttpContextFactory, DefaultHttpContextFactory>();

src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ public void ActivityBaggagePreservesItemsOrder()
397397
KeyValuePair.Create("Key1","value3")
398398
};
399399

400-
Assert.Equal(expectedBaggage, Activity.Current.Baggage);
400+
Assert.Equal(expectedBaggage, Activity.Current.Baggage.ToArray());
401401
}
402402

403403
[Fact]
@@ -552,6 +552,7 @@ private static HostingApplication CreateApplication(out FeatureCollection featur
552552
logger ?? new NullScopeLogger(),
553553
diagnosticListener ?? new NoopDiagnosticListener(),
554554
activitySource ?? new ActivitySource("Microsoft.AspNetCore"),
555+
DistributedContextPropagator.CreateDefaultPropagator(),
555556
httpContextFactory.Object);
556557

557558
return hostingApplication;

src/Hosting/Hosting/test/HostingApplicationTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ private static HostingApplication CreateApplication(IHttpContextFactory httpCont
196196
NullLogger.Instance,
197197
new DiagnosticListener("Microsoft.AspNetCore"),
198198
activitySource ?? new ActivitySource("Microsoft.AspNetCore"),
199+
DistributedContextPropagator.CreateDefaultPropagator(),
199200
httpContextFactory);
200201

201202
return hostingApplication;

0 commit comments

Comments
 (0)