diff --git a/src/Hosting/Hosting/src/Internal/ActivityExtensions.cs b/src/Hosting/Hosting/src/Internal/ActivityExtensions.cs new file mode 100644 index 000000000000..0391346cf97b --- /dev/null +++ b/src/Hosting/Hosting/src/Internal/ActivityExtensions.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + /// + /// Helpers for getting the right values from Activity no matter the format (w3c or hierarchical) + /// + internal static class ActivityExtensions + { + public static string GetSpanId(this Activity activity) + { + return activity.IdFormat switch + { + ActivityIdFormat.Hierarchical => activity.Id, + ActivityIdFormat.W3C => activity.SpanId.ToHexString(), + _ => null, + } ?? string.Empty; + } + + public static string GetTraceId(this Activity activity) + { + return activity.IdFormat switch + { + ActivityIdFormat.Hierarchical => activity.RootId, + ActivityIdFormat.W3C => activity.TraceId.ToHexString(), + _ => null, + } ?? string.Empty; + } + + public static string GetParentId(this Activity activity) + { + return activity.IdFormat switch + { + ActivityIdFormat.Hierarchical => activity.ParentId, + ActivityIdFormat.W3C => activity.ParentSpanId.ToHexString(), + _ => null, + } ?? string.Empty; + } + } +} diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index b983aa397dbc..c278a867784e 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -68,7 +68,7 @@ public void BeginRequest(HttpContext httpContext, ref HostingApplication.Context // Scope may be relevant for a different level of logging, so we always create it // see: https://github.com/aspnet/Hosting/pull/944 // Scope can be null if logging is not on. - context.Scope = _logger.RequestScope(httpContext, context.Activity.Id); + context.Scope = _logger.RequestScope(httpContext, context.Activity); if (_logger.IsEnabled(LogLevel.Information)) { diff --git a/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs b/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs index 48e5db98183a..72140480ab39 100644 --- a/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs +++ b/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Reflection; using Microsoft.AspNetCore.Http; @@ -13,9 +14,9 @@ namespace Microsoft.AspNetCore.Hosting.Internal { internal static class HostingLoggerExtensions { - public static IDisposable RequestScope(this ILogger logger, HttpContext httpContext, string activityId) + public static IDisposable RequestScope(this ILogger logger, HttpContext httpContext, Activity activity) { - return logger.BeginScope(new HostingLogScope(httpContext, activityId)); + return logger.BeginScope(new HostingLogScope(httpContext, activity)); } public static void ApplicationError(this ILogger logger, Exception exception) @@ -96,7 +97,7 @@ private class HostingLogScope : IReadOnlyList> { private readonly string _path; private readonly string _traceIdentifier; - private readonly string _activityId; + private readonly Activity _activity; private string _cachedToString; @@ -104,7 +105,7 @@ public int Count { get { - return 3; + return 5; } } @@ -122,20 +123,29 @@ public KeyValuePair this[int index] } else if (index == 2) { - return new KeyValuePair("ActivityId", _activityId); + return new KeyValuePair("SpanId", _activity.GetSpanId()); + } + else if (index == 3) + { + return new KeyValuePair("TraceId", _activity.GetTraceId()); + } + else if (index == 4) + { + return new KeyValuePair("ParentId", _activity.GetParentId()); } throw new ArgumentOutOfRangeException(nameof(index)); } } - public HostingLogScope(HttpContext httpContext, string activityId) + public HostingLogScope(HttpContext httpContext, Activity activity) { _traceIdentifier = httpContext.TraceIdentifier; _path = (httpContext.Request.PathBase.HasValue ? httpContext.Request.PathBase + httpContext.Request.Path : httpContext.Request.Path).ToString(); - _activityId = activityId; + + _activity = activity; } public override string ToString() @@ -144,10 +154,12 @@ public override string ToString() { _cachedToString = string.Format( CultureInfo.InvariantCulture, - "RequestPath:{0} RequestId:{1}, ActivityId:{2}", + "RequestPath:{0} RequestId:{1}, SpanId:{2}, TraceId:{3}, ParentId:{4}", _path, _traceIdentifier, - _activityId); + _activity.GetSpanId(), + _activity.GetTraceId(), + _activity.GetParentId()); } return _cachedToString; diff --git a/src/Hosting/Hosting/test/HostingApplicationTests.cs b/src/Hosting/Hosting/test/HostingApplicationTests.cs index 7454d738ef73..d799d984da46 100644 --- a/src/Hosting/Hosting/test/HostingApplicationTests.cs +++ b/src/Hosting/Hosting/test/HostingApplicationTests.cs @@ -42,7 +42,7 @@ public void CreateContextWithDisabledLoggerDoesNotCreateActivity() } [Fact] - public void CreateContextWithEnabledLoggerCreatesActivityAndSetsActivityIdInScope() + public void CreateContextWithEnabledLoggerCreatesActivityAndSetsActivityInScope() { // Arrange var logger = new LoggerWithScopes(isEnabled: true); @@ -53,7 +53,36 @@ public void CreateContextWithEnabledLoggerCreatesActivityAndSetsActivityIdInScop Assert.Single(logger.Scopes); var pairs = ((IReadOnlyList>)logger.Scopes[0]).ToDictionary(p => p.Key, p => p.Value); - Assert.Equal(Activity.Current.Id, pairs["ActivityId"].ToString()); + Assert.Equal(Activity.Current.Id, pairs["SpanId"].ToString()); + Assert.Equal(Activity.Current.RootId, pairs["TraceId"].ToString()); + Assert.Equal(string.Empty, pairs["ParentId"]?.ToString()); + } + + [Fact] + public void CreateContextWithEnabledLoggerAndRequestIdCreatesActivityAndSetsActivityInScope() + { + // Arrange + + // Generate an id we can use for the request id header (in the correct format) + var activity = new Activity("IncomingRequest"); + activity.Start(); + var id = activity.Id; + activity.Stop(); + + var logger = new LoggerWithScopes(isEnabled: true); + var hostingApplication = CreateApplication(out var features, logger: logger, configure: context => + { + context.Request.Headers["Request-Id"] = id; + }); + + // Act + var context = hostingApplication.CreateContext(features); + + Assert.Single(logger.Scopes); + var pairs = ((IReadOnlyList>)logger.Scopes[0]).ToDictionary(p => p.Key, p => p.Value); + Assert.Equal(Activity.Current.Id, pairs["SpanId"].ToString()); + Assert.Equal(Activity.Current.RootId, pairs["TraceId"].ToString()); + Assert.Equal(id, pairs["ParentId"].ToString()); } [Fact] @@ -90,10 +119,6 @@ public void ActivityStopDoesNotFireIfNoListenerAttachedForStart() // Act var context = hostingApplication.CreateContext(features); - Assert.Single(logger.Scopes); - var pairs = ((IReadOnlyList>)logger.Scopes[0]).ToDictionary(p => p.Key, p => p.Value); - Assert.Equal(Activity.Current.Id, pairs["ActivityId"].ToString()); - hostingApplication.DisposeContext(context, exception: null); Assert.False(startFired); @@ -398,13 +423,15 @@ private static void AssertProperty(object o, string name) } private static HostingApplication CreateApplication(out FeatureCollection features, - DiagnosticListener diagnosticSource = null, ILogger logger = null) + DiagnosticListener diagnosticSource = null, ILogger logger = null, Action configure = null) { var httpContextFactory = new Mock(); features = new FeatureCollection(); features.Set(new HttpRequestFeature()); - httpContextFactory.Setup(s => s.Create(It.IsAny())).Returns(new DefaultHttpContext(features)); + var context = new DefaultHttpContext(features); + configure?.Invoke(context); + httpContextFactory.Setup(s => s.Create(It.IsAny())).Returns(context); httpContextFactory.Setup(s => s.Dispose(It.IsAny())); var hostingApplication = new HostingApplication( @@ -453,7 +480,7 @@ public IDisposable BeginScope(TState state) public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { - + } private class Scope : IDisposable