diff --git a/src/libraries/System.Private.CoreLib/src/System/LocalAppContextSwitches.Common.cs b/src/libraries/Common/src/System/LocalAppContextSwitches.Common.cs similarity index 88% rename from src/libraries/System.Private.CoreLib/src/System/LocalAppContextSwitches.Common.cs rename to src/libraries/Common/src/System/LocalAppContextSwitches.Common.cs index a892b2162331d..5acaf03a87ee7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/LocalAppContextSwitches.Common.cs +++ b/src/libraries/Common/src/System/LocalAppContextSwitches.Common.cs @@ -11,6 +11,11 @@ namespace System // every framework assembly that implements any compatibility quirks. internal static partial class LocalAppContextSwitches { + // Returns value of given switch using provided cache. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool GetSwitchValue(string switchName, ref bool switchValue) => + AppContext.TryGetSwitch(switchName, out switchValue); + // Returns value of given switch using provided cache. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool GetCachedSwitchValue(string switchName, ref int cachedSwitchValue) @@ -24,7 +29,6 @@ internal static bool GetCachedSwitchValue(string switchName, ref int cachedSwitc private static bool GetCachedSwitchValueInternal(string switchName, ref int cachedSwitchValue) { - bool hasSwitch = AppContext.TryGetSwitch(switchName, out bool isSwitchEnabled); if (!hasSwitch) { diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj index 4619c9519c840..52f340c36ac42 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj @@ -5,7 +5,6 @@ $(NoWarn);SA1205 enable $(NetCoreAppCurrent);netstandard1.1;netstandard1.3;net45;net46;netstandard2.0;$(NetFrameworkCurrent) - true true @@ -20,6 +19,7 @@ $(DefineConstants);EVENTSOURCE_ENUMERATE_SUPPORT $(DefineConstants);ALLOW_PARTIALLY_TRUSTED_CALLERS;ENABLE_HTTP_HANDLER true + $(DefineConstants);W3C_DEFAULT_ID_FORMAT @@ -48,6 +48,12 @@ + + + + Common\System\LocalAppContextSwitches.Common.cs + + diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs index e3a7f04590694..ffb8ef8b89d80 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs @@ -51,7 +51,7 @@ public partial class Activity : IDisposable private static ActivityIdFormat s_defaultIdFormat; /// /// Normally if the ParentID is defined, the format of that is used to determine the - /// format used by the Activity. However if ForceDefaultFormat is set to true, the + /// format used by the Activity. However if ForceDefaultFormat is set to true, the /// ID format will always be the DefaultIdFormat even if the ParentID is define and is /// a different format. /// @@ -735,7 +735,13 @@ public static ActivityIdFormat DefaultIdFormat get { if (s_defaultIdFormat == ActivityIdFormat.Unknown) + { +#if W3C_DEFAULT_ID_FORMAT + s_defaultIdFormat = LocalAppContextSwitches.DefaultActivityIdFormatIsHierarchial ? ActivityIdFormat.Hierarchical : ActivityIdFormat.W3C; +#else s_defaultIdFormat = ActivityIdFormat.Hierarchical; +#endif // W3C_DEFAULT_ID_FORMAT + } return s_defaultIdFormat; } set diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/LocalAppContextSwitches.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/LocalAppContextSwitches.cs new file mode 100644 index 0000000000000..6a3ed6341f7fa --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/LocalAppContextSwitches.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +namespace System +{ + internal static partial class LocalAppContextSwitches + { + public static bool DefaultActivityIdFormatIsHierarchial { get; } = InitializeDefaultActivityIdFormat(); + + private static bool InitializeDefaultActivityIdFormat() + { + bool defaultActivityIdFormatIsHierarchial = false; + + if (!LocalAppContextSwitches.GetSwitchValue("System.Diagnostics.DefaultActivityIdFormatIsHierarchial", ref defaultActivityIdFormatIsHierarchial)) + { + string? switchValue = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_DIAGNOSTICS_DEFAULTACTIVITYIDFORMATISHIERARCHIAL"); + if (switchValue != null) + { + defaultActivityIdFormatIsHierarchial = IsTrueStringIgnoreCase(switchValue) || switchValue.Equals("1"); + } + } + + return defaultActivityIdFormatIsHierarchial; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsTrueStringIgnoreCase(string value) + { + return value.Length == 4 && + (value[0] == 't' || value[0] == 'T') && + (value[1] == 'r' || value[1] == 'R') && + (value[2] == 'u' || value[2] == 'U') && + (value[3] == 'e' || value[3] == 'E'); + } + } +} diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs index d79c701fe16fb..8e7c58f697918 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs @@ -188,32 +188,6 @@ public void ActivityIdOverflow() Assert.Equal('#', activity.Id[activity.Id.Length - 1]); } - /// - /// Tests overflow in Id generation when parentId has a single (root) node - /// - [Fact] - public void ActivityIdNonHierarchicalOverflow() - { - // find out Activity Id length on this platform in this AppDomain - Activity testActivity = new Activity("activity") - .Start(); - var expectedIdLength = testActivity.Id.Length; - testActivity.Stop(); - - // check that if parentId '|aaa...a' 1024 bytes long is set with single node (no dots or underscores in the Id) - // it causes overflow during Id generation, and new root Id is generated for the new Activity - var parentId = '|' + new string('a', 1022) + '.'; - - var activity = new Activity("activity") - .SetParentId(parentId) - .Start(); - - Assert.Equal(parentId, activity.ParentId); - - // With probability 1/MaxLong, Activity.Id length may be expectedIdLength + 1 - Assert.InRange(activity.Id.Length, expectedIdLength, expectedIdLength + 1); - Assert.DoesNotContain('#', activity.Id); - } /// /// Tests activity start and stop @@ -258,6 +232,7 @@ public void IdGenerationNoParent() public void IdGenerationInternalParent() { var parent = new Activity("parent"); + parent.SetIdFormat(ActivityIdFormat.Hierarchical); parent.Start(); var child1 = new Activity("child1"); var child2 = new Activity("child2"); @@ -535,11 +510,11 @@ public void ActivitySpanIdTests() /****** WC3 Format tests *****/ [Fact] - public void IdFormat_HierarchicalIsDefault() + public void IdFormat_W3CIsDefaultForNet5() { Activity activity = new Activity("activity1"); activity.Start(); - Assert.Equal(ActivityIdFormat.Hierarchical, activity.IdFormat); + Assert.Equal(PlatformDetection.IsNetCore ? ActivityIdFormat.W3C : ActivityIdFormat.Hierarchical, activity.IdFormat); } [Fact] @@ -617,6 +592,20 @@ public void IdFormat_W3CWhenDefaultIsW3C() }).Dispose(); } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void IdFormat_WithTheEnvironmentSwitch() + { + var psi = new ProcessStartInfo(); + psi.Environment.Add("DOTNET_SYSTEM_DIAGNOSTICS_DEFAULTACTIVITYIDFORMATISHIERARCHIAL", "true"); + + RemoteExecutor.Invoke(() => + { + Activity activity = new Activity("activity15"); + activity.Start(); + Assert.Equal(ActivityIdFormat.Hierarchical, activity.IdFormat); + }, new RemoteInvokeOptions() { StartInfo = psi }).Dispose(); + } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void IdFormat_HierarchicalWhenDefaultIsW3CButHierarchicalParentId() { @@ -633,13 +622,23 @@ public void IdFormat_HierarchicalWhenDefaultIsW3CButHierarchicalParentId() } [Fact] - public void IdFormat_ZeroTraceIdAndSpanIdWithHierarchicalFormat() + public void IdFormat_ZeroTraceIdAndSpanIdWithW3CFormat() { Activity activity = new Activity("activity"); activity.Start(); - Assert.Equal(ActivityIdFormat.Hierarchical, activity.IdFormat); - Assert.Equal("00000000000000000000000000000000", activity.TraceId.ToHexString()); - Assert.Equal("0000000000000000", activity.SpanId.ToHexString()); + + if (PlatformDetection.IsNetCore) + { + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.NotEqual("00000000000000000000000000000000", activity.TraceId.ToHexString()); + Assert.NotEqual("0000000000000000", activity.SpanId.ToHexString()); + } + else + { + Assert.Equal(ActivityIdFormat.Hierarchical, activity.IdFormat); + Assert.Equal("00000000000000000000000000000000", activity.TraceId.ToHexString()); + Assert.Equal("0000000000000000", activity.SpanId.ToHexString()); + } } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/ActivityTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/ActivityTests.cs new file mode 100644 index 0000000000000..e3b64f2f70f32 --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/ActivityTests.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using Xunit; + +namespace System.Diagnostics.Tests +{ + public class ActivityTests : IDisposable + { + [Fact] + public void ActivityIdNonHierarchicalOverflow() + { + // find out Activity Id length on this platform in this AppDomain + Activity testActivity = new Activity("activity") + .Start(); + var expectedIdLength = testActivity.Id.Length; + testActivity.Stop(); + + // check that if parentId '|aaa...a' 1024 bytes long is set with single node (no dots or underscores in the Id) + // it causes overflow during Id generation, and new root Id is generated for the new Activity + var parentId = '|' + new string('a', 1022) + '.'; + + var activity = new Activity("activity") + .SetParentId(parentId) + .Start(); + + Assert.Equal(parentId, activity.ParentId); + + // With probability 1/MaxLong, Activity.Id length may be expectedIdLength + 1 + Assert.InRange(activity.Id.Length, expectedIdLength, expectedIdLength + 1); + Assert.DoesNotContain('#', activity.Id); + } + + [Fact] + public void IdGenerationInternalParent() + { + var parent = new Activity("parent"); + parent.Start(); + var child1 = new Activity("child1"); + var child2 = new Activity("child2"); + //start 2 children in different execution contexts + Task.Run(() => child1.Start()).Wait(); + Task.Run(() => child2.Start()).Wait(); + + // In Debug builds of System.Diagnostics.DiagnosticSource, the child operation Id will be constructed as follows + // "|parent.RootId.-childCount.". + // This is for debugging purposes to know which operation the child Id is comming from. + // + // In Release builds of System.Diagnostics.DiagnosticSource, it will not contain the operation name to keep it simple and it will be as + // "|parent.RootId.childCount.". + + string child1DebugString = $"|{parent.RootId}.{child1.OperationName}-1."; + string child2DebugString = $"|{parent.RootId}.{child2.OperationName}-2."; + string child1ReleaseString = $"|{parent.RootId}.1."; + string child2ReleaseString = $"|{parent.RootId}.2."; + + AssertExtensions.AtLeastOneEquals(child1DebugString, child1ReleaseString, child1.Id); + AssertExtensions.AtLeastOneEquals(child2DebugString, child2ReleaseString, child2.Id); + + Assert.Equal(parent.RootId, child1.RootId); + Assert.Equal(parent.RootId, child2.RootId); + child1.Stop(); + child2.Stop(); + var child3 = new Activity("child3"); + child3.Start(); + + string child3DebugString = $"|{parent.RootId}.{child3.OperationName}-3."; + string child3ReleaseString = $"|{parent.RootId}.3."; + + AssertExtensions.AtLeastOneEquals(child3DebugString, child3ReleaseString, child3.Id); + + var grandChild = new Activity("grandChild"); + grandChild.Start(); + + child3DebugString = $"{child3.Id}{grandChild.OperationName}-1."; + child3ReleaseString = $"{child3.Id}1."; + + AssertExtensions.AtLeastOneEquals(child3DebugString, child3ReleaseString, grandChild.Id); + } + + [Fact] + public void IdFormat_HierarchicalIsDefault() + { + Activity activity = new Activity("activity1"); + activity.Start(); + Assert.Equal(ActivityIdFormat.Hierarchical, activity.IdFormat); + } + + [Fact] + public void IdFormat_ZeroTraceIdAndSpanIdWithHierarchicalFormat() + { + Activity activity = new Activity("activity"); + activity.Start(); + Assert.Equal(ActivityIdFormat.Hierarchical, activity.IdFormat); + Assert.Equal("00000000000000000000000000000000", activity.TraceId.ToHexString()); + Assert.Equal("0000000000000000", activity.SpanId.ToHexString()); + } + + public void Dispose() + { + Activity.Current = null; + } + } +} diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/System.Diagnostics.DiagnosticSource.Switches.Tests.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/System.Diagnostics.DiagnosticSource.Switches.Tests.csproj new file mode 100644 index 0000000000000..04a992f8cd81c --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/System.Diagnostics.DiagnosticSource.Switches.Tests.csproj @@ -0,0 +1,9 @@ + + + $(NetCoreAppCurrent) + true + + + + + \ No newline at end of file diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/runtimeconfig.template.json b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/runtimeconfig.template.json new file mode 100644 index 0000000000000..1b600a96bff58 --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TestWithConfigSwitches/runtimeconfig.template.json @@ -0,0 +1,5 @@ +{ + "configProperties": { + "System.Diagnostics.DefaultActivityIdFormatIsHierarchial": true + } +} \ No newline at end of file diff --git a/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj b/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj index 0291615e11c0b..7bb92b189a59c 100644 --- a/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj +++ b/src/libraries/System.Drawing.Common/src/System.Drawing.Common.csproj @@ -219,7 +219,7 @@ - diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index d9c25f17664ee..fcb68d7de40fe 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -403,7 +403,6 @@ - @@ -1084,6 +1083,9 @@ Common\SkipLocalsInit.cs + + Common\System\LocalAppContextSwitches.Common.cs + Common\System\HResults.cs diff --git a/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj b/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj index 833bf50383d10..1c9982edfc840 100644 --- a/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj +++ b/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj @@ -791,7 +791,7 @@ -