diff --git a/tracer/missing-nullability-files.csv b/tracer/missing-nullability-files.csv index fb6d4823eb59..fc32fd2d4afc 100644 --- a/tracer/missing-nullability-files.csv +++ b/tracer/missing-nullability-files.csv @@ -24,8 +24,6 @@ src/Datadog.Trace/OSPlatformName.cs src/Datadog.Trace/ProcessArchitecture.cs src/Datadog.Trace/PropagationErrorTagValues.cs src/Datadog.Trace/ReadOnlySpanContext.cs -src/Datadog.Trace/SamplingPriority.cs -src/Datadog.Trace/SamplingPriorityValues.cs src/Datadog.Trace/Scope.cs src/Datadog.Trace/Scope.IScope.cs src/Datadog.Trace/Span.cs @@ -85,7 +83,6 @@ src/Datadog.Trace/Ci/IEvent.cs src/Datadog.Trace/ClrProfiler/AutomaticTracer.cs src/Datadog.Trace/ClrProfiler/CallTargetKind.cs src/Datadog.Trace/ClrProfiler/ClrNames.cs -src/Datadog.Trace/ClrProfiler/CommonTracer.cs src/Datadog.Trace/ClrProfiler/DistributedTracer.cs src/Datadog.Trace/ClrProfiler/IAutomaticTracer.cs src/Datadog.Trace/ClrProfiler/ICommonTracer.cs @@ -198,20 +195,14 @@ src/Datadog.Trace/RuntimeMetrics/PerformanceCounterWrapper.cs src/Datadog.Trace/RuntimeMetrics/RuntimeEventListener.cs src/Datadog.Trace/RuntimeMetrics/RuntimeMetricsWriter.cs src/Datadog.Trace/RuntimeMetrics/Timing.cs -src/Datadog.Trace/Sampling/CustomSamplingRule.cs -src/Datadog.Trace/Sampling/DefaultSamplingRule.cs -src/Datadog.Trace/Sampling/GlobalSamplingRule.cs src/Datadog.Trace/Sampling/IRateLimiter.cs -src/Datadog.Trace/Sampling/ISamplingRule.cs src/Datadog.Trace/Sampling/ISpanSamplingRule.cs -src/Datadog.Trace/Sampling/ITraceSampler.cs src/Datadog.Trace/Sampling/OverheadController.cs src/Datadog.Trace/Sampling/RateLimiter.cs src/Datadog.Trace/Sampling/SamplingMechanism.cs src/Datadog.Trace/Sampling/SpanRateLimiter.cs src/Datadog.Trace/Sampling/SpanSamplingRule.cs src/Datadog.Trace/Sampling/TracerRateLimiter.cs -src/Datadog.Trace/Sampling/TraceSampler.cs src/Datadog.Trace/ServiceFabric/ServiceRemotingConstants.cs src/Datadog.Trace/Tagging/AerospikeTags.cs src/Datadog.Trace/Tagging/AspNetCoreEndpointTags.cs diff --git a/tracer/src/Datadog.Trace/Agent/MessagePack/SpanMessagePackFormatter.cs b/tracer/src/Datadog.Trace/Agent/MessagePack/SpanMessagePackFormatter.cs index 1b1f23dca72d..66dccc2dd058 100644 --- a/tracer/src/Datadog.Trace/Agent/MessagePack/SpanMessagePackFormatter.cs +++ b/tracer/src/Datadog.Trace/Agent/MessagePack/SpanMessagePackFormatter.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Runtime.CompilerServices; using Datadog.Trace.ExtensionMethods; using Datadog.Trace.Processors; @@ -228,19 +227,24 @@ private int Serialize(ref byte[] bytes, int offset, in SpanModel spanModel) private int WriteSpanLink(ref byte[] bytes, int offset, in SpanModel spanModel) { int originalOffset = offset; + offset += MessagePackBinary.WriteStringBytes(ref bytes, offset, _spanLinkBytes); offset += MessagePackBinary.WriteArrayHeader(ref bytes, offset, spanModel.Span.SpanLinks.Count); + foreach (var spanLink in spanModel.Span.SpanLinks) { var context = spanLink.Context; var samplingPriority = context.TraceContext?.SamplingPriority ?? context.SamplingPriority; + var traceFlags = samplingPriority switch { null => 0u, // not set > 0 => 1u + (1u << 31), // keep <= 0 => 1u << 31, // drop }; + var len = 3; + // check to serialize tracestate if (context.IsRemote) { diff --git a/tracer/src/Datadog.Trace/Agent/MessagePack/TraceChunkModel.cs b/tracer/src/Datadog.Trace/Agent/MessagePack/TraceChunkModel.cs index e7dce93f5c49..4f53674dc628 100644 --- a/tracer/src/Datadog.Trace/Agent/MessagePack/TraceChunkModel.cs +++ b/tracer/src/Datadog.Trace/Agent/MessagePack/TraceChunkModel.cs @@ -10,7 +10,6 @@ using System.Threading; using Datadog.Trace.Configuration; using Datadog.Trace.Tagging; -using Datadog.Trace.Util; namespace Datadog.Trace.Agent.MessagePack; @@ -62,7 +61,7 @@ internal readonly struct TraceChunkModel /// The spans that will be within this . /// Optional sampling priority to override the sampling priority. public TraceChunkModel(in ArraySegment spans, int? samplingPriority = null) - : this(spans, GetTraceContext(spans), samplingPriority) + : this(spans, TraceContext.GetTraceContext(spans), samplingPriority) { // since all we have is an array of spans, use the trace context from the first span // to get the other values we need (sampling priority, origin, trace tags, etc) for now. @@ -78,19 +77,29 @@ private TraceChunkModel(in ArraySegment spans, TraceContext? traceContext, if (traceContext is not null) { - DefaultServiceName = traceContext.Tracer?.DefaultServiceName; SamplingPriority ??= traceContext.SamplingPriority; + Environment = traceContext.Environment; ServiceVersion = traceContext.ServiceVersion; Origin = traceContext.Origin; Tags = traceContext.Tags; - IsRunningInAzureAppService = traceContext.Tracer?.Settings?.IsRunningInAzureAppService ?? false; - AzureAppServiceSettings = traceContext.Tracer?.Settings?.AzureAppServiceMetadata ?? null; - if (traceContext.Tracer?.GitMetadataTagsProvider?.TryExtractGitMetadata(out var gitMetadata) == true && - gitMetadata != GitMetadata.Empty) + + if (traceContext.Tracer is { } tracer) { - GitRepositoryUrl = gitMetadata.RepositoryUrl; - GitCommitSha = gitMetadata.CommitSha; + DefaultServiceName = tracer.DefaultServiceName; + + if (tracer.Settings is { } settings) + { + IsRunningInAzureAppService = settings.IsRunningInAzureAppService; + AzureAppServiceSettings = settings.AzureAppServiceMetadata ?? null; + } + + if (tracer.GitMetadataTagsProvider?.TryExtractGitMetadata(out var gitMetadata) == true && + gitMetadata != GitMetadata.Empty) + { + GitRepositoryUrl = gitMetadata.RepositoryUrl; + GitCommitSha = gitMetadata.CommitSha; + } } } } @@ -121,16 +130,6 @@ internal TraceChunkModel(in ArraySegment spans, Span? localRootSpan) // used in tests internal bool HashSetInitialized => _hashSet?.IsValueCreated == true && _hashSet.Value.Count > 0; - private static TraceContext? GetTraceContext(in ArraySegment spans) - { - if (spans.Count > 0) - { - return spans.Array![spans.Offset].Context.TraceContext; - } - - return null; - } - public SpanModel GetSpanModel(int spanIndex) { if (spanIndex >= _spans.Count) diff --git a/tracer/src/Datadog.Trace/Ci/Test.cs b/tracer/src/Datadog.Trace/Ci/Test.cs index 9877f741fd82..53383b42fdcb 100644 --- a/tracer/src/Datadog.Trace/Ci/Test.cs +++ b/tracer/src/Datadog.Trace/Ci/Test.cs @@ -51,7 +51,7 @@ internal Test(TestSuite suite, string name, DateTimeOffset? startDate, TraceId t scope.Span.Type = SpanTypes.Test; scope.Span.ResourceName = $"{suite.Name}.{name}"; - scope.Span.Context.TraceContext.SetSamplingPriority((int)SamplingPriority.AutoKeep, SamplingMechanism.Manual); + scope.Span.Context.TraceContext.SetSamplingPriority(SamplingPriorityValues.AutoKeep, SamplingMechanism.Manual); scope.Span.Context.TraceContext.Origin = TestTags.CIAppTestOriginName; TelemetryFactory.Metrics.RecordCountSpanCreated(MetricTags.IntegrationName.CiAppManual); diff --git a/tracer/src/Datadog.Trace/Ci/TestModule.cs b/tracer/src/Datadog.Trace/Ci/TestModule.cs index b3441bc513eb..5ab33ae8f408 100644 --- a/tracer/src/Datadog.Trace/Ci/TestModule.cs +++ b/tracer/src/Datadog.Trace/Ci/TestModule.cs @@ -172,7 +172,7 @@ internal TestModule(string name, string? framework, string? frameworkVersion, Da span.Type = SpanTypes.TestModule; span.ResourceName = name; - span.Context.TraceContext.SetSamplingPriority((int)SamplingPriority.AutoKeep); + span.Context.TraceContext.SetSamplingPriority(SamplingPriorityValues.AutoKeep); span.Context.TraceContext.Origin = TestTags.CIAppTestOriginName; tags.ModuleId = span.SpanId; diff --git a/tracer/src/Datadog.Trace/Ci/TestSession.cs b/tracer/src/Datadog.Trace/Ci/TestSession.cs index f72bad32bd4a..f2ba5acce8f8 100644 --- a/tracer/src/Datadog.Trace/Ci/TestSession.cs +++ b/tracer/src/Datadog.Trace/Ci/TestSession.cs @@ -61,7 +61,7 @@ private TestSession(string? command, string? workingDirectory, string? framework span.Type = SpanTypes.TestSession; span.ResourceName = $"{span.OperationName}.{command}"; - span.Context.TraceContext.SetSamplingPriority((int)SamplingPriority.AutoKeep); + span.Context.TraceContext.SetSamplingPriority(SamplingPriorityValues.AutoKeep); span.Context.TraceContext.Origin = TestTags.CIAppTestOriginName; tags.SessionId = span.SpanId; diff --git a/tracer/src/Datadog.Trace/Ci/TestSuite.cs b/tracer/src/Datadog.Trace/Ci/TestSuite.cs index 5b7add69624a..9e0f9c2ac209 100644 --- a/tracer/src/Datadog.Trace/Ci/TestSuite.cs +++ b/tracer/src/Datadog.Trace/Ci/TestSuite.cs @@ -38,7 +38,7 @@ internal TestSuite(TestModule module, string name, DateTimeOffset? startDate) span.Type = SpanTypes.TestSuite; span.ResourceName = name; - span.Context.TraceContext.SetSamplingPriority((int)SamplingPriority.AutoKeep); + span.Context.TraceContext.SetSamplingPriority(SamplingPriorityValues.AutoKeep); span.Context.TraceContext.Origin = TestTags.CIAppTestOriginName; tags.SuiteId = span.SpanId; diff --git a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Grpc/GrpcLegacy/Client/GrpcLegacyClientCommon.cs b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Grpc/GrpcLegacy/Client/GrpcLegacyClientCommon.cs index 0dc2961f7548..5b09a9c4b43b 100644 --- a/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Grpc/GrpcLegacy/Client/GrpcLegacyClientCommon.cs +++ b/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Grpc/GrpcLegacy/Client/GrpcLegacyClientCommon.cs @@ -81,9 +81,9 @@ internal static class GrpcLegacyClientCommon span.SetHeaderTags(requestMetadataWrapper, tracer.Settings.GrpcTagsInternal, GrpcCommon.RequestMetadataTagPrefix); scope = tracer.ActivateSpan(span); - if (setSamplingPriority && existingSpanContext?.SamplingPriority is not null) + if (setSamplingPriority && existingSpanContext?.SamplingPriority is { } samplingPriority) { - span.Context.TraceContext?.SetSamplingPriority(existingSpanContext.SamplingPriority.Value); + span.Context.TraceContext?.SetSamplingPriority(samplingPriority); } GrpcCommon.RecordFinalStatus(span, receivedStatus.StatusCode, receivedStatus.Detail, receivedStatus.DebugException); @@ -242,10 +242,10 @@ private static Span CreateInactiveSpan(Tracer tracer, string? methodFullName) var operationName = tracer.CurrentTraceSettings.Schema.Client.GetOperationNameForProtocol("grpc"); var serviceName = tracer.CurrentTraceSettings.Schema.Client.GetServiceName(component: "grpc-client"); var tags = tracer.CurrentTraceSettings.Schema.Client.CreateGrpcClientTags(); - var span = tracer.StartSpan(operationName, tags, serviceName: serviceName, addToTraceContext: false); tags.SetAnalyticsSampleRate(IntegrationId.Grpc, tracer.Settings, enabledWithGlobalSetting: false); tracer.CurrentTraceSettings.Schema.RemapPeerService(tags); + var span = tracer.StartSpan(operationName, tags, serviceName: serviceName, addToTraceContext: false); span.Type = SpanTypes.Grpc; span.ResourceName = methodFullName; diff --git a/tracer/src/Datadog.Trace/ClrProfiler/CommonTracer.cs b/tracer/src/Datadog.Trace/ClrProfiler/CommonTracer.cs index 0c61cc67d1af..b4defa7f7298 100644 --- a/tracer/src/Datadog.Trace/ClrProfiler/CommonTracer.cs +++ b/tracer/src/Datadog.Trace/ClrProfiler/CommonTracer.cs @@ -3,7 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // -using Datadog.Trace.Sampling; +#nullable enable namespace Datadog.Trace.ClrProfiler { diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.AzureAppService.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.AzureAppService.cs index e7c055d72caa..f2959e5234d5 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.AzureAppService.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.AzureAppService.cs @@ -67,7 +67,7 @@ internal class AzureAppService internal const string OperatingSystemKey = "WEBSITE_OS"; /// - /// Used to force the loader to start the tracer agent (in case automatic instrumentation is disabled) + /// Used to force the loader to start the trace agent (in case automatic instrumentation is disabled) /// public const string AasEnableCustomTracing = "DD_AAS_ENABLE_CUSTOM_TRACING"; diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/DynamicConfigConfigurationSource.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/DynamicConfigConfigurationSource.cs index a6d86baa8e20..9cf530e4dd3b 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/DynamicConfigConfigurationSource.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/DynamicConfigConfigurationSource.cs @@ -15,7 +15,7 @@ namespace Datadog.Trace.Configuration.ConfigurationSources { internal class DynamicConfigConfigurationSource : JsonConfigurationSource { - private static readonly IReadOnlyDictionary Mapping = new Dictionary + private static readonly Dictionary Mapping = new() { { ConfigurationKeys.TraceEnabled, "tracing_enabled" }, // { ConfigurationKeys.DebugEnabled, "tracing_debug" }, @@ -48,17 +48,17 @@ internal DynamicConfigConfigurationSource(string json, ConfigurationOrigins orig return jobject; } - private static IDictionary ReadHeaderTags(JToken token) + private static Dictionary ReadHeaderTags(JToken token) { return ((JArray)token).ToDictionary(t => t["header"]!.Value()!, t => t["tag_name"]!.Value()!); } - private static IDictionary ReadServiceMapping(JToken token) + private static Dictionary ReadServiceMapping(JToken token) { return ((JArray)token).ToDictionary(t => t["from_key"]!.Value()!, t => t["to_name"]!.Value()!); } - private static IDictionary ReadGlobalTags(JToken token) + private static Dictionary ReadGlobalTags(JToken token) { var result = new Dictionary(); diff --git a/tracer/src/Datadog.Trace/Configuration/PerTraceSettings.cs b/tracer/src/Datadog.Trace/Configuration/PerTraceSettings.cs index 2deff37388b4..a0ac5202a0e1 100644 --- a/tracer/src/Datadog.Trace/Configuration/PerTraceSettings.cs +++ b/tracer/src/Datadog.Trace/Configuration/PerTraceSettings.cs @@ -52,16 +52,5 @@ internal string GetServiceName(Tracer tracer, string serviceName) return finalServiceName; } - - internal bool TryGetServiceName(string key, out string? serviceName) - { - if (ServiceNames.TryGetValue(key, out serviceName)) - { - return true; - } - - serviceName = null; - return false; - } } } diff --git a/tracer/src/Datadog.Trace/Propagators/B3MultipleHeaderContextPropagator.cs b/tracer/src/Datadog.Trace/Propagators/B3MultipleHeaderContextPropagator.cs index edfd12bf738b..cdecbb961a80 100644 --- a/tracer/src/Datadog.Trace/Propagators/B3MultipleHeaderContextPropagator.cs +++ b/tracer/src/Datadog.Trace/Propagators/B3MultipleHeaderContextPropagator.cs @@ -73,9 +73,11 @@ public bool TryExtract(TCarrier carrier, TCarrierGette internal static void CreateHeaders(SpanContext context, out string traceId, out string spanId, out string sampled) { - var samplingPriority = context.TraceContext?.SamplingPriority ?? context.SamplingPriority; - sampled = samplingPriority > 0 ? "1" : "0"; + // BUG: we should fall back to SamplingPriorityValues.AutoKeep + var samplingPriority = context.TraceContext?.SamplingPriority ?? + context.SamplingPriority; // should never happen in production, but some tests rely on this + sampled = SamplingPriorityValues.IsKeep(samplingPriority) ? "1" : "0"; traceId = context.RawTraceId; spanId = context.RawSpanId; } diff --git a/tracer/src/Datadog.Trace/Propagators/B3SingleHeaderContextPropagator.cs b/tracer/src/Datadog.Trace/Propagators/B3SingleHeaderContextPropagator.cs index 66921bb5ec1d..2b09e16eec0c 100644 --- a/tracer/src/Datadog.Trace/Propagators/B3SingleHeaderContextPropagator.cs +++ b/tracer/src/Datadog.Trace/Propagators/B3SingleHeaderContextPropagator.cs @@ -139,8 +139,11 @@ public bool TryExtract(TCarrier carrier, TCarrierGette internal static string CreateHeader(SpanContext context) { - var samplingPriority = context.TraceContext?.SamplingPriority ?? context.SamplingPriority; - var sampled = samplingPriority > 0 ? "1" : "0"; + // BUG: we should fall back to SamplingPriorityValues.AutoKeep + var samplingPriority = context.TraceContext?.SamplingPriority ?? + context.SamplingPriority; // should never happen in production, but some tests rely on this + + var sampled = SamplingPriorityValues.IsKeep(samplingPriority) ? "1" : "0"; #if NET6_0_OR_GREATER return string.Create(null, stackalloc char[128], $"{context.RawTraceId}-{context.RawSpanId}-{sampled}"); diff --git a/tracer/src/Datadog.Trace/Propagators/DatadogContextPropagator.cs b/tracer/src/Datadog.Trace/Propagators/DatadogContextPropagator.cs index e3337a4545ad..b83567720d8d 100644 --- a/tracer/src/Datadog.Trace/Propagators/DatadogContextPropagator.cs +++ b/tracer/src/Datadog.Trace/Propagators/DatadogContextPropagator.cs @@ -36,19 +36,12 @@ public void Inject(SpanContext context, TCarrier carri carrierSetter.Set(carrier, HttpHeaderNames.Origin, context.Origin); } - var samplingPriority = context.TraceContext?.SamplingPriority ?? context.SamplingPriority; + var samplingPriority = context.TraceContext?.SamplingPriority ?? + context.SamplingPriority; // should never happen in production, but some tests rely on this if (samplingPriority != null) { - var samplingPriorityString = samplingPriority.Value switch - { - -1 => "-1", - 0 => "0", - 1 => "1", - 2 => "2", - _ => samplingPriority.Value.ToString(invariantCulture) - }; - + var samplingPriorityString = SamplingPriorityValues.ToString(samplingPriority); carrierSetter.Set(carrier, HttpHeaderNames.SamplingPriority, samplingPriorityString); } diff --git a/tracer/src/Datadog.Trace/Propagators/SpanContextPropagator.cs b/tracer/src/Datadog.Trace/Propagators/SpanContextPropagator.cs index a4c7000b52f7..486e16269fd7 100644 --- a/tracer/src/Datadog.Trace/Propagators/SpanContextPropagator.cs +++ b/tracer/src/Datadog.Trace/Propagators/SpanContextPropagator.cs @@ -11,7 +11,6 @@ using System.Linq; using Datadog.Trace.ExtensionMethods; using Datadog.Trace.Headers; -using Datadog.Trace.Util; namespace Datadog.Trace.Propagators { diff --git a/tracer/src/Datadog.Trace/Propagators/W3CTraceContextPropagator.cs b/tracer/src/Datadog.Trace/Propagators/W3CTraceContextPropagator.cs index 27f408f00394..cf6bb3104285 100644 --- a/tracer/src/Datadog.Trace/Propagators/W3CTraceContextPropagator.cs +++ b/tracer/src/Datadog.Trace/Propagators/W3CTraceContextPropagator.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.Runtime.CompilerServices; using System.Text; using Datadog.Trace.SourceGenerators; @@ -119,6 +118,7 @@ public void Inject(SpanContext context, TCarrier carri where TCarrierSetter : struct, ICarrierSetter { TelemetryFactory.Metrics.RecordCountContextHeaderStyleInjected(MetricTags.ContextHeaderStyle.TraceContext); + var traceparent = CreateTraceParentHeader(context); carrierSetter.Set(carrier, TraceParentHeaderName, traceparent); @@ -132,8 +132,12 @@ public void Inject(SpanContext context, TCarrier carri internal static string CreateTraceParentHeader(SpanContext context) { - var samplingPriority = context.TraceContext?.SamplingPriority ?? context.SamplingPriority ?? SamplingPriorityValues.AutoKeep; - var sampled = samplingPriority > 0 ? "01" : "00"; + var samplingPriority = context.TraceContext?.SamplingPriority ?? + context.SamplingPriority ?? // should never happen in production, but some tests rely on this + SamplingPriorityValues.AutoKeep; // fallback + + var sampled = SamplingPriorityValues.IsKeep(samplingPriority) ? "01" : "00"; + #if NET6_0_OR_GREATER return string.Create(null, stackalloc char[128], $"00-{context.RawTraceId}-{context.RawSpanId}-{sampled}"); #else @@ -150,11 +154,9 @@ internal static string CreateTraceStateHeader(SpanContext context) sb.Append("dd="); // sampling priority ("s:") - var samplingPriority = SamplingPriorityToString(context.TraceContext?.SamplingPriority); - - if (samplingPriority != null) + if (context.TraceContext?.SamplingPriority is { } samplingPriority) { - sb.Append("s:").Append(samplingPriority).Append(TraceStateDatadogPairsSeparator); + sb.Append("s:").Append(SamplingPriorityValues.ToString(samplingPriority)).Append(TraceStateDatadogPairsSeparator); } // origin ("o:") @@ -563,20 +565,6 @@ internal static void SplitTraceStateValues(string header, out string? ddValues, } } - [return: NotNullIfNotNull("samplingPriority")] - private static string? SamplingPriorityToString(int? samplingPriority) - { - return samplingPriority switch - { - 2 => "2", - 1 => "1", - 0 => "0", - -1 => "-1", - null => null, - not null => samplingPriority.Value.ToString(CultureInfo.InvariantCulture) - }; - } - #if NETCOREAPP private static int? SamplingPriorityToInt32(ReadOnlySpan samplingPriority) { diff --git a/tracer/src/Datadog.Trace/RemoteConfigurationManagement/RcmCapabilitiesIndices.cs b/tracer/src/Datadog.Trace/RemoteConfigurationManagement/RcmCapabilitiesIndices.cs index f3293e7c35a2..01a56dcdae2f 100644 --- a/tracer/src/Datadog.Trace/RemoteConfigurationManagement/RcmCapabilitiesIndices.cs +++ b/tracer/src/Datadog.Trace/RemoteConfigurationManagement/RcmCapabilitiesIndices.cs @@ -49,6 +49,26 @@ internal static class RcmCapabilitiesIndices public static readonly BigInteger ApmTracingTracingEnabled = Create(19); + public static readonly BigInteger ApmTracingDataStreamsEnabled = Create(20); + + public static readonly BigInteger AsmRaspSqli = Create(21); + + public static readonly BigInteger AsmRaspLfi = Create(22); + + public static readonly BigInteger AsmRaspSsrf = Create(23); + + public static readonly BigInteger AsmRaspShi = Create(24); + + public static readonly BigInteger AsmRaspXxe = Create(25); + + public static readonly BigInteger AsmRaspRce = Create(26); + + public static readonly BigInteger AsmRaspNosqli = Create(27); + + public static readonly BigInteger AsmRaspXss = Create(28); + + public static readonly BigInteger ApmTracingSampleRules = Create(29); + private static BigInteger Create(int index) => new(1UL << index); } } diff --git a/tracer/src/Datadog.Trace/Sampling/DefaultSamplingRule.cs b/tracer/src/Datadog.Trace/Sampling/AgentSamplingRule.cs similarity index 85% rename from tracer/src/Datadog.Trace/Sampling/DefaultSamplingRule.cs rename to tracer/src/Datadog.Trace/Sampling/AgentSamplingRule.cs index ae7b79b05563..753d7f923f83 100644 --- a/tracer/src/Datadog.Trace/Sampling/DefaultSamplingRule.cs +++ b/tracer/src/Datadog.Trace/Sampling/AgentSamplingRule.cs @@ -1,8 +1,10 @@ -// +// // Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // +#nullable enable + using System; using System.Collections.Generic; using Datadog.Trace.Logging; @@ -13,16 +15,14 @@ namespace Datadog.Trace.Sampling { // These "default" sampling rule contains the mapping of service/env names to sampling rates. // These rates are received in http responses from the trace agent after we send a trace payload. - internal class DefaultSamplingRule : ISamplingRule + internal class AgentSamplingRule : ISamplingRule { private const string DefaultKey = "service:,env:"; - private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(); + private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(); private Dictionary _sampleRates = new(); private float? _defaultSamplingRate; - public string RuleName => "default-rule"; - // if there are no rules, this normally means we haven't sent any payloads to the Agent yet (aka cold start), so the mechanism is "Default". // if there are rules, there should always be at least one match (the fallback "service:,env:") and the mechanism is "AgentRate". public int SamplingMechanism => _sampleRates.Count == 0 && _defaultSamplingRate == null ? @@ -34,10 +34,7 @@ internal class DefaultSamplingRule : ISamplingRule /// public int Priority => int.MinValue; - public bool IsMatch(Span span) - { - return true; - } + public bool IsMatch(Span span) => true; public float GetSamplingRate(Span span) { @@ -49,18 +46,20 @@ public float GetSamplingRate(Span span) // either we don't have sampling rate from the agent yet (cold start), // or the only rate we received is for "service:,env:", which is not added to _sampleRates defaultRate = _defaultSamplingRate ?? 1; - SetSamplingAgentDecision(span, defaultRate); // Keep it to ease investigations + + // add the _dd.agent_psr tag even if we didn't use rates from the agent (to ease investigations) + SetSamplingRateTag(span, defaultRate); return defaultRate; } - var env = span.Context.TraceContext.Environment; + var env = span.Context.TraceContext.Environment ?? string.Empty; var service = span.ServiceName; var key = new SampleRateKey(service, env); if (_sampleRates.TryGetValue(key, out var sampleRate)) { - SetSamplingAgentDecision(span, sampleRate); + SetSamplingRateTag(span, sampleRate); return sampleRate; } @@ -69,11 +68,12 @@ public float GetSamplingRate(Span span) Log.Debug("Could not establish sample rate for trace {TraceId}. Using default rate instead: {Rate}", span.Context.RawTraceId, _defaultSamplingRate); } + // no match by service/env, you default rate defaultRate = _defaultSamplingRate ?? 1; - SetSamplingAgentDecision(span, defaultRate); + SetSamplingRateTag(span, defaultRate); return defaultRate; - static void SetSamplingAgentDecision(Span span, float sampleRate) + static void SetSamplingRateTag(Span span, float sampleRate) { if (span.Tags is CommonTags commonTags) { @@ -88,7 +88,7 @@ static void SetSamplingAgentDecision(Span span, float sampleRate) public void SetDefaultSampleRates(IReadOnlyDictionary sampleRates) { - if (sampleRates is null || sampleRates.Count == 0) + if (sampleRates is not { Count: > 0 }) { Log.Debug("sampling rates received from the agent are empty"); return; @@ -122,10 +122,15 @@ public void SetDefaultSampleRates(IReadOnlyDictionary sampleRates _sampleRates = rates; } + public override string ToString() + { + return "AgentSamplingRates"; + } + private readonly struct SampleRateKey : IEquatable { - private static readonly char[] PartSeparator = new[] { ',' }; - private static readonly char[] ValueSeparator = new[] { ':' }; + private static readonly char[] PartSeparator = [',']; + private static readonly char[] ValueSeparator = [':']; private readonly string _service; private readonly string _env; @@ -169,7 +174,7 @@ public bool Equals(SampleRateKey other) return _service == other._service && _env == other._env; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is SampleRateKey other && Equals(other); } diff --git a/tracer/src/Datadog.Trace/Sampling/CustomSamplingRule.cs b/tracer/src/Datadog.Trace/Sampling/CustomSamplingRule.cs index ad6fdf455a6a..762e027df88d 100644 --- a/tracer/src/Datadog.Trace/Sampling/CustomSamplingRule.cs +++ b/tracer/src/Datadog.Trace/Sampling/CustomSamplingRule.cs @@ -3,6 +3,8 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // +#nullable enable + using System; using System.Collections.Generic; using System.Text.RegularExpressions; @@ -19,25 +21,23 @@ internal class CustomSamplingRule : ISamplingRule private readonly bool _alwaysMatch; // TODO consider moving toward these https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Text/SimpleRegex.cs - private readonly Regex _serviceNameRegex; - private readonly Regex _operationNameRegex; - private readonly Regex _resourceNameRegex; - private readonly List> _tagRegexes; + private readonly Regex? _serviceNameRegex; + private readonly Regex? _operationNameRegex; + private readonly Regex? _resourceNameRegex; + private readonly List>? _tagRegexes; private bool _regexTimedOut; public CustomSamplingRule( float rate, - string ruleName, string patternFormat, - string serviceNamePattern, - string operationNamePattern, - string resourceNamePattern, - ICollection> tagPatterns, + string? serviceNamePattern, + string? operationNamePattern, + string? resourceNamePattern, + ICollection>? tagPatterns, TimeSpan timeout) { _samplingRate = rate; - RuleName = ruleName; _serviceNameRegex = RegexBuilder.Build(serviceNamePattern, patternFormat, timeout); _operationNameRegex = RegexBuilder.Build(operationNamePattern, patternFormat, timeout); @@ -54,15 +54,13 @@ _resourceNameRegex is null && } } - public string RuleName { get; } - public int SamplingMechanism => Datadog.Trace.Sampling.SamplingMechanism.TraceSamplingRule; /// - /// Gets or sets the priority of the rule. + /// Gets the priority of the rule. /// Configuration rules will default to 1 as a priority and rely on order of specification. /// - public int Priority { get; protected set; } = 1; + public int Priority => 1; public static IEnumerable BuildFromConfigurationString(string configuration, string patternFormat, TimeSpan timeout) { @@ -71,23 +69,19 @@ public static IEnumerable BuildFromConfigurationString(strin if (!string.IsNullOrWhiteSpace(configuration) && JsonConvert.DeserializeObject>(configuration) is { Count: > 0 } rules) { - var index = 0; var samplingRules = new List(rules.Count); foreach (var r in rules) { - index++; // Used to create a readable rule name if one is not specified - samplingRules.Add( new CustomSamplingRule( - r.SampleRate, - r.RuleName ?? $"config-rule-{index}", - patternFormat, - r.Service, - r.OperationName, - r.Resource, - r.Tags, - timeout)); + rate: r.SampleRate, + patternFormat: patternFormat, + serviceNamePattern: r.Service, + operationNamePattern: r.OperationName, + resourceNamePattern: r.Resource, + tagPatterns: r.Tags, + timeout: timeout)); } return samplingRules; @@ -135,27 +129,31 @@ public float GetSamplingRate(Span span) return _samplingRate; } - [Serializable] - private class CustomRuleConfig + public override string ToString() { - [JsonProperty(PropertyName = "rule_name")] - public string RuleName { get; set; } + // later this will return different values depending on the rule's provenance: + // local, customer (remote), or dynamic (remote) + return "LocalSamplingRule"; + } + // ReSharper disable once ClassNeverInstantiated.Local + private class CustomRuleConfig + { [JsonRequired] [JsonProperty(PropertyName = "sample_rate")] public float SampleRate { get; set; } [JsonProperty(PropertyName = "name")] - public string OperationName { get; set; } + public string? OperationName { get; set; } [JsonProperty(PropertyName = "service")] - public string Service { get; set; } + public string? Service { get; set; } [JsonProperty(PropertyName = "resource")] - public string Resource { get; set; } + public string? Resource { get; set; } [JsonProperty(PropertyName = "tags")] - public Dictionary Tags { get; set; } + public Dictionary? Tags { get; set; } } } } diff --git a/tracer/src/Datadog.Trace/Sampling/GlobalSamplingRule.cs b/tracer/src/Datadog.Trace/Sampling/GlobalSamplingRule.cs index 5dbf84e6c90e..1a6b84f99b88 100644 --- a/tracer/src/Datadog.Trace/Sampling/GlobalSamplingRule.cs +++ b/tracer/src/Datadog.Trace/Sampling/GlobalSamplingRule.cs @@ -3,6 +3,8 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // +#nullable enable + using Datadog.Trace.Logging; namespace Datadog.Trace.Sampling @@ -18,8 +20,6 @@ public GlobalSamplingRule(float rate) _globalRate = rate; } - public string RuleName => "global-rate-rule"; - /// /// Gets the priority which is one beneath custom rules. /// @@ -27,16 +27,19 @@ public GlobalSamplingRule(float rate) public int SamplingMechanism => Datadog.Trace.Sampling.SamplingMechanism.TraceSamplingRule; - public bool IsMatch(Span span) - { - return true; - } + public bool IsMatch(Span span) => true; public float GetSamplingRate(Span span) { Log.Debug("Using the global sampling rate: {Rate}", _globalRate); + span.SetMetric(Metrics.SamplingRuleDecision, _globalRate); return _globalRate; } + + public override string ToString() + { + return "GlobalSamplingRate"; + } } } diff --git a/tracer/src/Datadog.Trace/Sampling/ISamplingRule.cs b/tracer/src/Datadog.Trace/Sampling/ISamplingRule.cs index 3bc995dab7ad..2a772a2fa0a0 100644 --- a/tracer/src/Datadog.Trace/Sampling/ISamplingRule.cs +++ b/tracer/src/Datadog.Trace/Sampling/ISamplingRule.cs @@ -3,16 +3,12 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // +#nullable enable + namespace Datadog.Trace.Sampling { internal interface ISamplingRule { - /// - /// Gets the rule name. - /// Used for debugging purposes mostly. - /// - string RuleName { get; } - /// /// Gets the rule priority. /// Higher number means higher priority. diff --git a/tracer/src/Datadog.Trace/Sampling/ITraceSampler.cs b/tracer/src/Datadog.Trace/Sampling/ITraceSampler.cs index 797138f4fb79..ae19d435f79d 100644 --- a/tracer/src/Datadog.Trace/Sampling/ITraceSampler.cs +++ b/tracer/src/Datadog.Trace/Sampling/ITraceSampler.cs @@ -3,6 +3,8 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // +#nullable enable + using System.Collections.Generic; namespace Datadog.Trace.Sampling diff --git a/tracer/src/Datadog.Trace/Sampling/RegexBuilder.cs b/tracer/src/Datadog.Trace/Sampling/RegexBuilder.cs index d95d442e0249..a0943cae0254 100644 --- a/tracer/src/Datadog.Trace/Sampling/RegexBuilder.cs +++ b/tracer/src/Datadog.Trace/Sampling/RegexBuilder.cs @@ -64,7 +64,7 @@ internal static class RegexBuilder } } - public static List> Build(ICollection> patterns, string format, TimeSpan timeout) + public static List> Build(ICollection>? patterns, string format, TimeSpan timeout) { if (patterns is { Count: > 0 }) { diff --git a/tracer/src/Datadog.Trace/Sampling/SamplingDecision.cs b/tracer/src/Datadog.Trace/Sampling/SamplingDecision.cs index 6862426fb02e..4c3e61eea437 100644 --- a/tracer/src/Datadog.Trace/Sampling/SamplingDecision.cs +++ b/tracer/src/Datadog.Trace/Sampling/SamplingDecision.cs @@ -14,7 +14,7 @@ internal readonly struct SamplingDecision /// or no sampling rules match. For example, this value is used if the tracer has not yet /// received any sampling rates from agent and there are no configured sampling rates. /// - public static SamplingDecision Default = new(SamplingPriorityValues.AutoKeep, SamplingMechanism.Default); + public static SamplingDecision Default = new(SamplingPriorityValues.Default, SamplingMechanism.Default); public readonly int Priority; diff --git a/tracer/src/Datadog.Trace/Sampling/SamplingMechanism.cs b/tracer/src/Datadog.Trace/Sampling/SamplingMechanism.cs index 9f7eee63c90c..4372eada56ef 100644 --- a/tracer/src/Datadog.Trace/Sampling/SamplingMechanism.cs +++ b/tracer/src/Datadog.Trace/Sampling/SamplingMechanism.cs @@ -3,6 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // +using System; using System.Globalization; namespace Datadog.Trace.Sampling; @@ -32,6 +33,7 @@ internal static class SamplingMechanism /// and (1). /// (Reserved for future use.) /// + [Obsolete("This value is reserved for future use.")] public const int RemoteRateAuto = 2; /// @@ -61,12 +63,14 @@ internal static class SamplingMechanism /// and (2). /// (Reserved for future use.) /// + [Obsolete("This value is reserved for future use.")] public const int RemoteRateUser = 6; /// /// A sampling decision was made using a sampling rule configured remotely by Datadog. /// (Reserved for future use.) /// + [Obsolete("This value is reserved for future use.")] public const int RemoteRateDatadog = 7; /// @@ -75,14 +79,38 @@ internal static class SamplingMechanism /// public const int SpanSamplingRule = 8; + /// + /// A sampling decision was made using the OTLP-compatible probabilistic sampling in the Agent. + /// + [Obsolete("This value is used in the trace agent, not in tracing libraries.")] + public const int OtlpIngestProbabilisticSampling = 9; + + /// + /// Traces coming from spark/databricks workload tracing + /// + public const int DataJobsMonitoring = 10; + + /// + /// A sampling decision was made using a trace sampling rule that was configured remotely by the user + /// and sent via remote configuration (RCM). + /// The available sampling priorities are (-1) + /// and (2). + /// + public const int RemoteUserSamplingRule = 11; + + /// + /// A sampling decision was made using a trace sampling rule that was computed remotely by Datadog + /// and sent via remote configuration (RCM). + /// The available sampling priorities are (-1) + /// and (2). + /// + public const int RemoteAdaptiveSamplingRule = 12; + public static string GetTagValue(int mechanism) { - // set the sampling mechanism trace tag - // * only set tag if priority is AUTO_KEEP (1) or USER_KEEP (2) - // * do not overwrite an existing value - // * don't set tag if sampling mechanism is unknown (null) - // * the "-" prefix is a left-over separator from a previous iteration of this feature (not a typo or a negative sign) - + // the "-" prefix is a left-over separator from a previous iteration of this feature + // (not a negative sign) +#pragma warning disable CS0618 // Type or member is obsolete return mechanism switch { Default => "-0", @@ -94,7 +122,14 @@ public static string GetTagValue(int mechanism) RemoteRateUser => "-6", RemoteRateDatadog => "-7", SpanSamplingRule => "-8", + OtlpIngestProbabilisticSampling => "-9", + DataJobsMonitoring => "-10", + RemoteUserSamplingRule => "-11", + RemoteAdaptiveSamplingRule => "-12", + + // forwards-compatibility for future values _ => $"-{mechanism.ToString(CultureInfo.InvariantCulture)}" }; +#pragma warning restore CS0618 // Type or member is obsolete } } diff --git a/tracer/src/Datadog.Trace/Sampling/TraceSampler.cs b/tracer/src/Datadog.Trace/Sampling/TraceSampler.cs index a8567e385d6d..65fdadc28f42 100644 --- a/tracer/src/Datadog.Trace/Sampling/TraceSampler.cs +++ b/tracer/src/Datadog.Trace/Sampling/TraceSampler.cs @@ -3,6 +3,8 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // +#nullable enable + using System.Collections.Generic; using Datadog.Trace.Logging; using Datadog.Trace.Util; @@ -15,7 +17,7 @@ internal class TraceSampler : ITraceSampler private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(); private readonly IRateLimiter _limiter; - private readonly DefaultSamplingRule _defaultRule = new(); + private readonly AgentSamplingRule _defaultRule = new(); private readonly List _rules = new(); public TraceSampler(IRateLimiter limiter) @@ -42,8 +44,8 @@ public SamplingDecision MakeSamplingDecision(Span span) if (Log.IsEnabled(LogEventLevel.Debug)) { Log.Debug( - "Matched on rule {RuleName}. Applying rate of {Rate} to trace id {TraceId}", - rule.RuleName, + "Matched on rule {Rule}. Applying rate of {Rate} to trace id {TraceId}", + rule, sampleRate, span.Context.RawTraceId); } diff --git a/tracer/src/Datadog.Trace/SamplingPriority.cs b/tracer/src/Datadog.Trace/SamplingPriority.cs index ca1870ba43eb..3b6804d3dc5a 100644 --- a/tracer/src/Datadog.Trace/SamplingPriority.cs +++ b/tracer/src/Datadog.Trace/SamplingPriority.cs @@ -3,6 +3,8 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // +#nullable enable + namespace Datadog.Trace { /// diff --git a/tracer/src/Datadog.Trace/SamplingPriorityValues.cs b/tracer/src/Datadog.Trace/SamplingPriorityValues.cs index f9cc710c0664..3fa6cdf53574 100644 --- a/tracer/src/Datadog.Trace/SamplingPriorityValues.cs +++ b/tracer/src/Datadog.Trace/SamplingPriorityValues.cs @@ -3,10 +3,22 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // +#nullable enable + +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + namespace Datadog.Trace { internal static class SamplingPriorityValues { + /// + /// The default sampling priority used when there is no sampler available + /// or no sampling rules match. For example, this value is used if the tracer has not yet + /// received any sampling rates from agent and there are no configured sampling rates. + /// + public const int Default = AutoKeep; + /// /// Trace should be dropped (not sampled). /// Sampling decision made explicitly by user through @@ -32,5 +44,23 @@ internal static class SamplingPriorityValues /// code or configuration (e.g. the rules sampler). /// public const int UserKeep = 2; + + [return: NotNullIfNotNull("samplingPriority")] + internal static string? ToString(int? samplingPriority) + { + return samplingPriority switch + { + 2 => "2", + 1 => "1", + 0 => "0", + -1 => "-1", + null => null, + { } value => value.ToString(CultureInfo.InvariantCulture) + }; + } + + internal static bool IsKeep(int? samplingPriority) => samplingPriority > 0; + + internal static bool IsDrop(int? samplingPriority) => samplingPriority <= 0; } } diff --git a/tracer/src/Datadog.Trace/Span.cs b/tracer/src/Datadog.Trace/Span.cs index 68a10346a583..3f4a08636ed3 100644 --- a/tracer/src/Datadog.Trace/Span.cs +++ b/tracer/src/Datadog.Trace/Span.cs @@ -4,20 +4,16 @@ // using System; -using System.Collections; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Runtime.CompilerServices; using System.Threading; -using System.Threading.Tasks; using Datadog.Trace.Debugger.ExceptionAutoInstrumentation; using Datadog.Trace.ExtensionMethods; using Datadog.Trace.Logging; using Datadog.Trace.Sampling; using Datadog.Trace.Tagging; using Datadog.Trace.Telemetry; -using Datadog.Trace.Telemetry.Metrics; using Datadog.Trace.Util; using Datadog.Trace.Vendors.Serilog.Events; @@ -176,11 +172,14 @@ public override string ToString() sb.AppendLine($"OperationName: {OperationName}"); sb.AppendLine($"Resource: {ResourceName}"); sb.AppendLine($"Type: {Type}"); - sb.AppendLine($"Start: {StartTime.ToString("O")}"); + sb.AppendLine($"Start: {StartTime:O}"); sb.AppendLine($"Duration: {Duration}"); - sb.AppendLine($"End: {StartTime.Add(Duration).ToString("O")}"); + sb.AppendLine($"End: {StartTime.Add(Duration):O}"); sb.AppendLine($"Error: {Error}"); - sb.AppendLine($"TraceSamplingPriority: {(Context.TraceContext.SamplingPriority?.ToString(CultureInfo.InvariantCulture) ?? "not set")}"); + + var samplingPriority = Context.TraceContext?.SamplingPriority; + sb.AppendLine($"TraceSamplingPriority: {SamplingPriorityValues.ToString(samplingPriority) ?? "not set"}"); + sb.AppendLine($"Meta: {Tags}"); return StringBuilderCache.GetStringAndRelease(sb); @@ -421,7 +420,7 @@ internal void SetExceptionTags(Exception exception) // for AggregateException, use the first inner exception until we can support multiple errors. // there will be only one error in most cases, and even if there are more and we lose // the other ones, it's still better than the generic "one or more errors occurred" message. - if (exception is AggregateException aggregateException && aggregateException.InnerExceptions.Count > 0) + if (exception is AggregateException { InnerExceptions.Count: > 0 } aggregateException) { exception = aggregateException.InnerExceptions[0]; } @@ -452,21 +451,15 @@ internal string GetTag(string key) { // since we don't expose a public API for getting trace-level attributes yet, // allow retrieval through any span in the trace - switch (key) + return key switch { - case Trace.Tags.SamplingPriority: - return Context.TraceContext?.SamplingPriority?.ToString(); - case Trace.Tags.Env: - return Context.TraceContext?.Environment; - case Trace.Tags.Version: - return Context.TraceContext?.ServiceVersion; - case Trace.Tags.Origin: - return Context.TraceContext?.Origin; - case Trace.Tags.TraceId: - return Context.RawTraceId; - default: - return Tags.GetTag(key); - } + Trace.Tags.SamplingPriority => SamplingPriorityValues.ToString(Context.TraceContext?.SamplingPriority), + Trace.Tags.Env => Context.TraceContext?.Environment, + Trace.Tags.Version => Context.TraceContext?.ServiceVersion, + Trace.Tags.Origin => Context.TraceContext?.Origin, + Trace.Tags.TraceId => Context.RawTraceId, + _ => Tags.GetTag(key) + }; } internal void Finish(TimeSpan duration) diff --git a/tracer/src/Datadog.Trace/TraceContext.cs b/tracer/src/Datadog.Trace/TraceContext.cs index d7cb699ff190..cf1e24049bde 100644 --- a/tracer/src/Datadog.Trace/TraceContext.cs +++ b/tracer/src/Datadog.Trace/TraceContext.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Threading; using Datadog.Trace.AppSec; using Datadog.Trace.ClrProfiler; @@ -35,26 +34,25 @@ internal class TraceContext private ArrayBuilder _spans; private int _openSpans; private int? _samplingPriority; - // _rootSpan was chosen at some point to be used as the key for a lock that protects + + // _rootSpan was chosen in #4125 to be the lock that protects // * _spans // * _openSpans // although it's a nullable field, the _rootSpan must always be set before operations on - // _spans & _samplingPriority take place, so it's okay to use it as a lock key + // _spans take place, so it's okay to use it as a lock key // even though we need to override the nullable warnings in some places. - // The reason _rootSpan was chosen is unknown, we're assuming it's to avoid - // allocation a separate field for the lock + // The reason _rootSpan was chosen is to avoid + // allocating a separate object for the lock. private Span? _rootSpan; public TraceContext(IDatadogTracer tracer, TraceTagCollection? tags = null) { CurrentTraceSettings = tracer.PerTraceSettings; - var settings = tracer.Settings; - // TODO: Environment, ServiceVersion, GitCommitSha, and GitRepositoryUrl are stored on the TraceContext // even though they likely won't change for the lifetime of the process. We should consider moving them // elsewhere to reduce the memory usage. - if (settings is not null) + if (tracer.Settings is { } settings) { // these could be set from DD_ENV/DD_VERSION or from DD_TAGS Environment = settings.EnvironmentInternal; @@ -69,7 +67,6 @@ public TraceContext(IDatadogTracer tracer, TraceTagCollection? tags = null) public Span? RootSpan { get => _rootSpan; - private set => _rootSpan = value; } public TraceClock Clock => _clock; @@ -81,7 +78,6 @@ public Span? RootSpan /// /// Gets the collection of trace-level tags. /// - [NotNull] public TraceTagCollection Tags { get; } /// @@ -110,6 +106,11 @@ public int? SamplingPriority /// internal IastRequestContext? IastRequestContext => _iastRequestContext; + internal static TraceContext? GetTraceContext(in ArraySegment spans) => + spans.Count > 0 ? + spans.Array![spans.Offset].Context.TraceContext : + null; + internal void AddWafSecurityEvents(IReadOnlyCollection events) { if (Volatile.Read(ref _appSecRequestContext) is null) @@ -155,7 +156,7 @@ public void CloseSpan(Span span) ArraySegment spansToWrite = default; // Propagate the resource name to the profiler for root web spans - if (span.IsRootSpan && span.Type == SpanTypes.Web) + if (span is { IsRootSpan: true, Type: SpanTypes.Web }) { Profiler.Instance.ContextTracker.SetEndpoint(span.RootSpanId, span.ResourceName); @@ -173,10 +174,7 @@ public void CloseSpan(Span span) } } - if (_appSecRequestContext != null) - { - _appSecRequestContext.CloseWebSpan(Tags); - } + _appSecRequestContext?.CloseWebSpan(Tags); } if (!string.Equals(span.ServiceName, Tracer.DefaultServiceName, StringComparison.OrdinalIgnoreCase)) @@ -256,6 +254,7 @@ public void SetSamplingPriority(int? priority, int? mechanism = null, bool notif if (priority > 0 && mechanism != null) { + // add the tag once, but never overwrite an existing tag Tags.TryAddTag(tagName, SamplingMechanism.GetTagValue(mechanism.Value)); } else if (priority <= 0) @@ -277,11 +276,11 @@ private void RunSpanSampler(ArraySegment spans) return; } - if (spans.Array![spans.Offset].Context.TraceContext?.SamplingPriority <= 0) + if (SamplingPriorityValues.IsDrop(SamplingPriority)) { for (int i = 0; i < spans.Count; i++) { - CurrentTraceSettings.SpanSampler.MakeSamplingDecision(spans.Array[i + spans.Offset]); + CurrentTraceSettings.SpanSampler.MakeSamplingDecision(spans.Array![i + spans.Offset]); } } } diff --git a/tracer/src/Datadog.Trace/TracerManager.cs b/tracer/src/Datadog.Trace/TracerManager.cs index 866ed7872b99..6a553c63309b 100644 --- a/tracer/src/Datadog.Trace/TracerManager.cs +++ b/tracer/src/Datadog.Trace/TracerManager.cs @@ -27,7 +27,6 @@ using Datadog.Trace.RuntimeMetrics; using Datadog.Trace.Sampling; using Datadog.Trace.Telemetry; -using Datadog.Trace.Util; using Datadog.Trace.Util.Http; using Datadog.Trace.Vendors.Newtonsoft.Json; using Datadog.Trace.Vendors.StatsdClient; @@ -162,7 +161,7 @@ public static TracerManager Instance public RuntimeMetricsWriter RuntimeMetrics { get; } - public PerTraceSettings PerTraceSettings { get; set; } + public PerTraceSettings PerTraceSettings { get; } /// /// Replaces the global settings. This affects all instances diff --git a/tracer/src/Datadog.Trace/Util/SamplingHelpers.cs b/tracer/src/Datadog.Trace/Util/SamplingHelpers.cs index f29323a5eed4..a9127e3e7231 100644 --- a/tracer/src/Datadog.Trace/Util/SamplingHelpers.cs +++ b/tracer/src/Datadog.Trace/Util/SamplingHelpers.cs @@ -35,7 +35,10 @@ internal static bool SampleByRate(TraceId traceId, double rate) => internal static bool SampleByRate(ulong id, double rate) => ((id * KnuthFactor) % Modulo) <= (rate * Modulo); - internal static bool IsKeptBySamplingPriority(ArraySegment trace) => - trace.Array![trace.Offset].Context.TraceContext?.SamplingPriority > 0; + internal static bool IsKeptBySamplingPriority(ArraySegment trace) + { + var traceContext = TraceContext.GetTraceContext(trace); + return SamplingPriorityValues.IsKeep(traceContext?.SamplingPriority); + } } } diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/WebRequestTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/WebRequestTests.cs index 9ec9597f29b7..489a839ee6e1 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/WebRequestTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/WebRequestTests.cs @@ -87,10 +87,10 @@ private async Task RunTest(string metadataSchemaVersion) using (var agent = EnvironmentHelper.GetMockAgent()) using (ProcessResult processResult = await RunSampleAndWaitForExit(agent, arguments: $"Port={httpPort}")) { - var allSpans = agent.WaitForSpans(expectedAllSpansCount).OrderBy(s => s.Start); + var allSpans = agent.WaitForSpans(expectedAllSpansCount).OrderBy(s => s.Start).ToList(); allSpans.Should().OnlyHaveUniqueItems(s => new { s.SpanId, s.TraceId }); - var spans = allSpans.Where(s => s.Type == SpanTypes.Http); + var spans = allSpans.Where(s => s.Type == SpanTypes.Http).ToList(); spans.Should().HaveCount(expectedSpanCount); ValidateIntegrationSpans(spans, metadataSchemaVersion, expectedServiceName: clientSpanServiceName, isExternalSpan); diff --git a/tracer/test/Datadog.Trace.Tests/Agent/AgentWriterTests.cs b/tracer/test/Datadog.Trace.Tests/Agent/AgentWriterTests.cs index 02e5252ab3f8..5cff43797306 100644 --- a/tracer/test/Datadog.Trace.Tests/Agent/AgentWriterTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Agent/AgentWriterTests.cs @@ -49,7 +49,7 @@ public async Task SpanSampling_CanComputeStats_ShouldNotSend_WhenSpanSamplingDoe var spanContext = new SpanContext(null, traceContext, "service"); var span = new Span(spanContext, DateTimeOffset.UtcNow) { OperationName = "operation" }; traceContext.AddSpan(span); - traceContext.SetSamplingPriority(new SamplingDecision(SamplingPriorityValues.UserReject, SamplingMechanism.Manual)); + traceContext.SetSamplingPriority(SamplingPriorityValues.UserReject, SamplingMechanism.Manual); span.Finish(); // triggers the span sampler to run var traceChunk = new ArraySegment(new[] { span }); @@ -77,7 +77,7 @@ public async Task SpanSampling_ShouldSend_SingleMatchedSpan_WhenStatsDrops() var spanContext = new SpanContext(null, traceContext, "service"); var span = new Span(spanContext, DateTimeOffset.UtcNow) { OperationName = "operation" }; traceContext.AddSpan(span); - traceContext.SetSamplingPriority(new SamplingDecision(SamplingPriorityValues.UserReject, SamplingMechanism.Manual)); + traceContext.SetSamplingPriority(SamplingPriorityValues.UserReject, SamplingMechanism.Manual); span.Finish(); var traceChunk = new ArraySegment(new[] { span }); var expectedData1 = Vendors.MessagePack.MessagePackSerializer.Serialize(new TraceChunkModel(traceChunk, SamplingPriorityValues.UserKeep), SpanFormatterResolver.Instance); @@ -105,7 +105,7 @@ public async Task SpanSampling_ShouldSend_MultipleMatchedSpans_WhenStatsDrops() var tracer = new Tracer(settings, agent, sampler: null, scopeManager: null, statsd: null); var traceContext = new TraceContext(tracer); - traceContext.SetSamplingPriority(new SamplingDecision(SamplingPriorityValues.UserReject, SamplingMechanism.Manual)); + traceContext.SetSamplingPriority(SamplingPriorityValues.UserReject, SamplingMechanism.Manual); var rootSpanContext = new SpanContext(null, traceContext, "service"); var rootSpan = new Span(rootSpanContext, DateTimeOffset.UtcNow) { OperationName = "operation" }; var keptChildSpan = new Span(new SpanContext(rootSpanContext, traceContext, "service"), DateTimeOffset.UtcNow) { OperationName = "operation" }; @@ -141,7 +141,7 @@ public async Task SpanSampling_ShouldSend_MultipleMatchedSpans_WhenStatsDropsOne var tracer = new Tracer(settings, agent, sampler: null, scopeManager: null, statsd: null); var traceContext = new TraceContext(tracer); - traceContext.SetSamplingPriority(new SamplingDecision(SamplingPriorityValues.UserReject, SamplingMechanism.Manual)); + traceContext.SetSamplingPriority(SamplingPriorityValues.UserReject, SamplingMechanism.Manual); var rootSpanContext = new SpanContext(null, traceContext, "service"); var rootSpan = new Span(rootSpanContext, DateTimeOffset.UtcNow) { OperationName = "operation" }; var droppedChildSpan = new Span(new SpanContext(rootSpanContext, traceContext, "service"), DateTimeOffset.UtcNow) { OperationName = "drop_me" }; diff --git a/tracer/test/Datadog.Trace.Tests/DistributedTracer/CommonTracerTests.cs b/tracer/test/Datadog.Trace.Tests/DistributedTracer/CommonTracerTests.cs index 4e8c175fad48..bbe967fce816 100644 --- a/tracer/test/Datadog.Trace.Tests/DistributedTracer/CommonTracerTests.cs +++ b/tracer/test/Datadog.Trace.Tests/DistributedTracer/CommonTracerTests.cs @@ -26,7 +26,9 @@ public void SetSamplingPriority() commonTracer.SetSamplingPriority(expectedSamplingPriority); - scope.Span.Context.TraceContext.SamplingPriority.Should().Be(expectedSamplingPriority, "SetSamplingPriority should have successfully set the active trace sampling priority"); + scope.Span.Context.TraceContext.SamplingPriority + .Should() + .Be(expectedSamplingPriority, "SetSamplingPriority should have successfully set the active trace sampling priority"); } private class CommonTracerImpl : CommonTracer diff --git a/tracer/test/Datadog.Trace.Tests/DatadogHttpClientTests.cs b/tracer/test/Datadog.Trace.Tests/HttpOverStreams/DatadogHttpClientTests.cs similarity index 99% rename from tracer/test/Datadog.Trace.Tests/DatadogHttpClientTests.cs rename to tracer/test/Datadog.Trace.Tests/HttpOverStreams/DatadogHttpClientTests.cs index 49030d1cfd2c..d99561d7021d 100644 --- a/tracer/test/Datadog.Trace.Tests/DatadogHttpClientTests.cs +++ b/tracer/test/Datadog.Trace.Tests/HttpOverStreams/DatadogHttpClientTests.cs @@ -10,12 +10,11 @@ using System.Threading.Tasks; using Datadog.Trace.HttpOverStreams; using Datadog.Trace.HttpOverStreams.HttpContent; -using Datadog.Trace.Tests.HttpOverStreams; using Datadog.Trace.Util; using FluentAssertions; using Xunit; -namespace Datadog.Trace.Tests +namespace Datadog.Trace.Tests.HttpOverStreams { public class DatadogHttpClientTests { diff --git a/tracer/test/Datadog.Trace.Tests/LambdaRequestBuilderTests.cs b/tracer/test/Datadog.Trace.Tests/LambdaRequestBuilderTests.cs index fc90e0180705..2797a0f82d89 100644 --- a/tracer/test/Datadog.Trace.Tests/LambdaRequestBuilderTests.cs +++ b/tracer/test/Datadog.Trace.Tests/LambdaRequestBuilderTests.cs @@ -20,9 +20,10 @@ public class LambdaRequestBuilderTests public async Task TestGetEndInvocationRequestWithError() { await using var tracer = TracerHelper.CreateWithFakeAgent(); - var scope = LambdaCommon.CreatePlaceholderScope(tracer, null, null); + var scope = LambdaCommon.CreatePlaceholderScope(tracer, traceId: null, samplingPriority: null); + ILambdaExtensionRequest requestBuilder = new LambdaRequestBuilder(); - var request = requestBuilder.GetEndInvocationRequest(scope, true); + var request = requestBuilder.GetEndInvocationRequest(scope, isError: true); request.Headers.Get("x-datadog-invocation-error").Should().Be("true"); request.Headers.Get("x-datadog-tracing-enabled").Should().Be("false"); request.Headers.Get("x-datadog-sampling-priority").Should().Be("1"); @@ -34,9 +35,10 @@ public async Task TestGetEndInvocationRequestWithError() public async Task TestGetEndInvocationRequestWithoutError() { await using var tracer = TracerHelper.CreateWithFakeAgent(); - var scope = LambdaCommon.CreatePlaceholderScope(tracer, null, null); + var scope = LambdaCommon.CreatePlaceholderScope(tracer, traceId: null, samplingPriority: null); + ILambdaExtensionRequest requestBuilder = new LambdaRequestBuilder(); - var request = requestBuilder.GetEndInvocationRequest(scope, false); + var request = requestBuilder.GetEndInvocationRequest(scope, isError: false); request.Headers.Get("x-datadog-invocation-error").Should().BeNull(); request.Headers.Get("x-datadog-tracing-enabled").Should().Be("false"); request.Headers.Get("x-datadog-sampling-priority").Should().Be("1"); @@ -48,9 +50,10 @@ public async Task TestGetEndInvocationRequestWithoutError() public async Task TestGetEndInvocationRequestWithScope() { await using var tracer = TracerHelper.CreateWithFakeAgent(); - var scope = LambdaCommon.CreatePlaceholderScope(tracer, "1234", "-1"); + var scope = LambdaCommon.CreatePlaceholderScope(tracer, traceId: "1234", samplingPriority: "-1"); + ILambdaExtensionRequest requestBuilder = new LambdaRequestBuilder(); - var request = requestBuilder.GetEndInvocationRequest(scope, false); + var request = requestBuilder.GetEndInvocationRequest(scope, isError: false); request.Headers.Get("x-datadog-invocation-error").Should().BeNull(); request.Headers.Get("x-datadog-tracing-enabled").Should().Be("false"); request.Headers.Get("x-datadog-sampling-priority").Should().Be("-1"); @@ -62,7 +65,7 @@ public async Task TestGetEndInvocationRequestWithScope() public void TestGetEndInvocationRequestWithoutScope() { ILambdaExtensionRequest requestBuilder = new LambdaRequestBuilder(); - var request = requestBuilder.GetEndInvocationRequest(null, false); + var request = requestBuilder.GetEndInvocationRequest(scope: null, isError: false); request.Headers.Get("x-datadog-invocation-error").Should().BeNull(); request.Headers.Get("x-datadog-tracing-enabled").Should().Be("false"); request.Headers.Get("x-datadog-sampling-priority").Should().BeNull(); @@ -74,10 +77,11 @@ public void TestGetEndInvocationRequestWithoutScope() public async Task TestGetEndInvocationRequestWithErrorTags() { await using var tracer = TracerHelper.CreateWithFakeAgent(); - var scope = LambdaCommon.CreatePlaceholderScope(tracer, null, null); + var scope = LambdaCommon.CreatePlaceholderScope(tracer, traceId: null, samplingPriority: null); scope.Span.SetTag("error.msg", "Exception"); scope.Span.SetTag("error.type", "Exception"); scope.Span.SetTag("error.stack", "everything is " + System.Environment.NewLine + "fine"); + ILambdaExtensionRequest requestBuilder = new LambdaRequestBuilder(); var request = requestBuilder.GetEndInvocationRequest(scope, true); request.Headers.Get("x-datadog-invocation-error").Should().NotBeNull(); @@ -95,6 +99,7 @@ public async Task TestGetEndInvocationRequestWithoutErrorTags() { await using var tracer = TracerHelper.CreateWithFakeAgent(); var scope = LambdaCommon.CreatePlaceholderScope(tracer, null, null); + ILambdaExtensionRequest requestBuilder = new LambdaRequestBuilder(); var request = requestBuilder.GetEndInvocationRequest(scope, true); request.Headers.Get("x-datadog-invocation-error").Should().NotBeNull(); diff --git a/tracer/test/Datadog.Trace.Tests/Propagators/B3MultipleHeadersPropagatorTests.cs b/tracer/test/Datadog.Trace.Tests/Propagators/B3MultipleHeadersPropagatorTests.cs index 5246a188b780..ca126149a42c 100644 --- a/tracer/test/Datadog.Trace.Tests/Propagators/B3MultipleHeadersPropagatorTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Propagators/B3MultipleHeadersPropagatorTests.cs @@ -3,7 +3,6 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // -using System.Reflection; using Datadog.Trace.Headers; using Datadog.Trace.Propagators; using FluentAssertions; @@ -19,8 +18,8 @@ public class B3MultipleHeadersPropagatorTests static B3MultipleHeadersPropagatorTests() { B3Propagator = SpanContextPropagatorFactory.GetSpanContextPropagator( - new[] { ContextPropagationHeaderStyle.B3MultipleHeaders }, - new[] { ContextPropagationHeaderStyle.B3MultipleHeaders }, + [ContextPropagationHeaderStyle.B3MultipleHeaders], + [ContextPropagationHeaderStyle.B3MultipleHeaders], false); } @@ -40,19 +39,24 @@ public void Inject_IHeadersCollection() headers.Verify(h => h.Set("x-b3-sampled", "1"), Times.Once()); headers.VerifyNoOtherCalls(); - // Extract sampling from trace context + // Extract default (no sampler) sampling from trace context var newContext = new SpanContext(parent: null, new TraceContext(Mock.Of()), serviceName: null, traceId, spanId); var newHeaders = new Mock(); + B3Propagator.Inject(newContext, newHeaders.Object); + newHeaders.Verify(h => h.Set("x-b3-traceid", "0123456789abcdef1122334455667788"), Times.Once()); newHeaders.Verify(h => h.Set("x-b3-spanid", "000000003ade68b1"), Times.Once()); + // BUG: we should default to KEEP if there's no sampler, but this never happens in real life, will fix later newHeaders.Verify(h => h.Set("x-b3-sampled", "0"), Times.Once()); newHeaders.VerifyNoOtherCalls(); - var traceContextSamplingField = typeof(TraceContext).GetField("_samplingPriority", BindingFlags.Instance | BindingFlags.NonPublic); - traceContextSamplingField!.SetValue(newContext.TraceContext, SamplingPriorityValues.UserKeep); + // override sampling decision + newContext.TraceContext.SetSamplingPriority(SamplingPriorityValues.UserKeep); newHeaders = new Mock(); + B3Propagator.Inject(newContext, newHeaders.Object); + newHeaders.Verify(h => h.Set("x-b3-traceid", "0123456789abcdef1122334455667788"), Times.Once()); newHeaders.Verify(h => h.Set("x-b3-spanid", "000000003ade68b1"), Times.Once()); newHeaders.Verify(h => h.Set("x-b3-sampled", "1"), Times.Once()); @@ -77,7 +81,7 @@ public void Inject_CarrierAndDelegate() headers.Verify(h => h.Set("x-b3-sampled", "1"), Times.Once()); headers.VerifyNoOtherCalls(); - // Extract sampling from trace context + // Extract default (no sampler) sampling from trace context var newContext = new SpanContext(parent: null, new TraceContext(Mock.Of()), serviceName: null, traceId, spanId); var newHeaders = new Mock(); @@ -85,11 +89,12 @@ public void Inject_CarrierAndDelegate() newHeaders.Verify(h => h.Set("x-b3-traceid", "0123456789abcdef1122334455667788"), Times.Once()); newHeaders.Verify(h => h.Set("x-b3-spanid", "000000003ade68b1"), Times.Once()); + // BUG: we should default to KEEP if there's no sampler, but this never happens in real life, will fix later newHeaders.Verify(h => h.Set("x-b3-sampled", "0"), Times.Once()); newHeaders.VerifyNoOtherCalls(); - var traceContextSamplingField = typeof(TraceContext).GetField("_samplingPriority", BindingFlags.Instance | BindingFlags.NonPublic); - traceContextSamplingField!.SetValue(newContext.TraceContext, SamplingPriorityValues.UserKeep); + // override sampling decision + newContext.TraceContext.SetSamplingPriority(SamplingPriorityValues.UserKeep); newHeaders = new Mock(); B3Propagator.Inject(newContext, newHeaders.Object, (carrier, name, value) => carrier.Set(name, value)); diff --git a/tracer/test/Datadog.Trace.Tests/Propagators/B3SingleHeaderPropagatorTests.cs b/tracer/test/Datadog.Trace.Tests/Propagators/B3SingleHeaderPropagatorTests.cs index 625bd28d13c2..242c2b57ccb2 100644 --- a/tracer/test/Datadog.Trace.Tests/Propagators/B3SingleHeaderPropagatorTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Propagators/B3SingleHeaderPropagatorTests.cs @@ -3,7 +3,6 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // -using System.Reflection; using Datadog.Trace.Headers; using Datadog.Trace.Propagators; using FluentAssertions; @@ -19,8 +18,8 @@ public class B3SingleHeaderPropagatorTests static B3SingleHeaderPropagatorTests() { B3Propagator = SpanContextPropagatorFactory.GetSpanContextPropagator( - new[] { ContextPropagationHeaderStyle.B3SingleHeader }, - new[] { ContextPropagationHeaderStyle.B3SingleHeader }, + [ContextPropagationHeaderStyle.B3SingleHeader], + [ContextPropagationHeaderStyle.B3SingleHeader], false); } @@ -29,8 +28,7 @@ public void Inject_IHeadersCollection() { var traceId = new TraceId(0x0123456789abcdef, 0x1122334455667788); const ulong spanId = 987654321; - const int samplingPriority = SamplingPriorityValues.UserKeep; - var context = new SpanContext(traceId, spanId, samplingPriority, serviceName: null, origin: null); + var context = new SpanContext(traceId, spanId, SamplingPriorityValues.UserKeep, serviceName: null, origin: null); var headers = new Mock(); B3Propagator.Inject(context, headers.Object); @@ -38,13 +36,15 @@ public void Inject_IHeadersCollection() headers.Verify(h => h.Set("b3", "0123456789abcdef1122334455667788-000000003ade68b1-1"), Times.Once()); headers.VerifyNoOtherCalls(); - // Extract sampling from trace context + // Extract default (no sampler) sampling from trace context var newContext = new SpanContext(parent: null, new TraceContext(Mock.Of()), serviceName: null, traceId, spanId); var newHeaders = new Mock(); B3Propagator.Inject(newContext, newHeaders.Object); + // BUG: we should default to KEEP if there's no sampler, but this never happens in real life, will fix later newHeaders.Verify(h => h.Set("b3", "0123456789abcdef1122334455667788-000000003ade68b1-0"), Times.Once()); newHeaders.VerifyNoOtherCalls(); + // override sampling decision newContext.TraceContext.SetSamplingPriority(SamplingPriorityValues.UserKeep); newHeaders = new Mock(); B3Propagator.Inject(newContext, newHeaders.Object); @@ -68,13 +68,15 @@ public void Inject_CarrierAndDelegate() headers.Verify(h => h.Set("b3", "000000000000000000000000075bcd15-000000003ade68b1-1"), Times.Once()); headers.VerifyNoOtherCalls(); - // Extract sampling from trace context + // Extract default (no sampler) sampling from trace context var newContext = new SpanContext(parent: null, new TraceContext(Mock.Of()), serviceName: null, traceId, spanId); var newHeaders = new Mock(); B3Propagator.Inject(newContext, newHeaders.Object, (carrier, name, value) => carrier.Set(name, value)); + // BUG: we should default to KEEP if there's no sampler, but this never happens in real life, will fix later newHeaders.Verify(h => h.Set("b3", "000000000000000000000000075bcd15-000000003ade68b1-0"), Times.Once()); newHeaders.VerifyNoOtherCalls(); + // override sampling decision newContext.TraceContext.SetSamplingPriority(SamplingPriorityValues.UserKeep); newHeaders = new Mock(); B3Propagator.Inject(newContext, newHeaders.Object, (carrier, name, value) => carrier.Set(name, value)); diff --git a/tracer/test/Datadog.Trace.Tests/Propagators/W3CTraceContextPropagatorTests.cs b/tracer/test/Datadog.Trace.Tests/Propagators/W3CTraceContextPropagatorTests.cs index d6503e9066eb..ed0e8635472a 100644 --- a/tracer/test/Datadog.Trace.Tests/Propagators/W3CTraceContextPropagatorTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Propagators/W3CTraceContextPropagatorTests.cs @@ -127,7 +127,7 @@ public void CreateTraceStateHeader_WithPublicPropagatedTags() public void CreateTraceStateHeader_With128Bit_TraceId() { var traceContext = new TraceContext(Mock.Of()); - traceContext.SetSamplingPriority(2); + traceContext.SetSamplingPriority(SamplingPriorityValues.UserKeep); var traceId = new TraceId(0x1234567890abcdef, 0x1122334455667788); var spanContext = new SpanContext(parent: SpanContext.None, traceContext, serviceName: null, traceId: traceId, spanId: 2); diff --git a/tracer/test/Datadog.Trace.Tests/Sampling/DefaultSamplingRuleTests.cs b/tracer/test/Datadog.Trace.Tests/Sampling/AgentSamplingRuleTests.cs similarity index 92% rename from tracer/test/Datadog.Trace.Tests/Sampling/DefaultSamplingRuleTests.cs rename to tracer/test/Datadog.Trace.Tests/Sampling/AgentSamplingRuleTests.cs index f8802452753a..9fd28e97a72d 100644 --- a/tracer/test/Datadog.Trace.Tests/Sampling/DefaultSamplingRuleTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Sampling/AgentSamplingRuleTests.cs @@ -1,4 +1,4 @@ -// +// // Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // @@ -13,7 +13,7 @@ namespace Datadog.Trace.Tests.Sampling { - public class DefaultSamplingRuleTests + public class AgentSamplingRuleTests { [Theory] // Value returned by the agent per default @@ -37,7 +37,7 @@ public async Task KeyParsing(string key, string expectedService, string expected scope.Span.Context.TraceContext.Environment = expectedEnv; // create sampling rule - var rule = new DefaultSamplingRule(); + var rule = new AgentSamplingRule(); rule.SetDefaultSampleRates(new Dictionary { { key, .5f } }); // assert that the sampling rate applied to the span is correct @@ -45,13 +45,13 @@ public async Task KeyParsing(string key, string expectedService, string expected } [Fact] - public async Task DefaultSamplingRuleIsApplied() + public async Task SamplingRulesAreApplied() { const string configuredService = "NiceService"; const string configuredEnv = "BeautifulEnv"; const string unconfiguredService = "RogueService"; - var rule = new DefaultSamplingRule(); + var rule = new AgentSamplingRule(); var settings = new TracerSettings(); await using var tracer = TracerHelper.CreateWithFakeAgent(settings); diff --git a/tracer/test/Datadog.Trace.Tests/Sampling/CustomSamplingRuleRegexTests.cs b/tracer/test/Datadog.Trace.Tests/Sampling/CustomSamplingRuleRegexTests.cs index 872a91869f4f..a1b658fd07d8 100644 --- a/tracer/test/Datadog.Trace.Tests/Sampling/CustomSamplingRuleRegexTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Sampling/CustomSamplingRuleRegexTests.cs @@ -62,8 +62,8 @@ public void Constructs_With_ResourceName() var nonMatchingSpan = new Span(new SpanContext(1, 1, serviceName: "foo"), DateTimeOffset.Now) { ResourceName = "/api/v2/user/123" }; VerifyRate(rule, 0.3f); - VerifySingleRule(rule, matchingSpan, isMatch: true); - VerifySingleRule(rule, nonMatchingSpan, isMatch: false); + VerifySingleRule(rule, matchingSpan, expected: true); + VerifySingleRule(rule, nonMatchingSpan, expected: false); } [Fact] @@ -83,9 +83,9 @@ public void Constructs_With_Tags() nonMatchingSpan2.SetTag("http.method", "POST"); VerifyRate(rule, 0.3f); - VerifySingleRule(rule, matchingSpan, isMatch: true); - VerifySingleRule(rule, nonMatchingSpan1, isMatch: false); - VerifySingleRule(rule, nonMatchingSpan2, isMatch: false); + VerifySingleRule(rule, matchingSpan, expected: true); + VerifySingleRule(rule, nonMatchingSpan1, expected: false); + VerifySingleRule(rule, nonMatchingSpan2, expected: false); } [Fact] @@ -173,15 +173,15 @@ private static void VerifyRate(ISamplingRule rule, float expectedRate) rule.GetSamplingRate(TestSpans.CartCheckoutSpan).Should().Be(expectedRate); } - private static void VerifySingleRule(string config, Span span, bool isMatch) + private static void VerifySingleRule(string config, Span span, bool expected) { var rule = CustomSamplingRule.BuildFromConfigurationString(config, SamplingRulesFormat.Regex, Timeout).Single(); - VerifySingleRule(rule, span, isMatch); + VerifySingleRule(rule, span, expected); } - private static void VerifySingleRule(ISamplingRule rule, Span span, bool isMatch) + private static void VerifySingleRule(ISamplingRule rule, Span span, bool expected) { - Assert.Equal(rule.IsMatch(span), isMatch); + rule.IsMatch(span).Should().Be(expected); } } } diff --git a/tracer/test/Datadog.Trace.Tests/Sampling/TraceSamplerTests.cs b/tracer/test/Datadog.Trace.Tests/Sampling/TraceSamplerTests.cs index e7d2265e3121..a1ec8087c94d 100644 --- a/tracer/test/Datadog.Trace.Tests/Sampling/TraceSamplerTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Sampling/TraceSamplerTests.cs @@ -46,7 +46,6 @@ public async Task RateLimiter_Denies_All_Traces() sampler.RegisterRule( new CustomSamplingRule( rate: 1, - ruleName: "Allow_all", patternFormat: SamplingRulesFormat.Regex, serviceNamePattern: ".*", operationNamePattern: ".*", @@ -70,7 +69,6 @@ public async Task Keep_Everything_Rule() sampler.RegisterRule( new CustomSamplingRule( rate: 1, - ruleName: "Allow_all", patternFormat: SamplingRulesFormat.Regex, serviceNamePattern: ".*", operationNamePattern: ".*", @@ -94,7 +92,6 @@ public async Task Keep_Nothing_Rule() sampler.RegisterRule( new CustomSamplingRule( rate: 0, - ruleName: "Allow_nothing", patternFormat: SamplingRulesFormat.Regex, serviceNamePattern: ".*", operationNamePattern: ".*", @@ -118,7 +115,6 @@ public async Task Keep_Half_Rule() sampler.RegisterRule( new CustomSamplingRule( rate: 0.5f, - ruleName: "Allow_half", patternFormat: SamplingRulesFormat.Regex, serviceNamePattern: ".*", operationNamePattern: ".*", diff --git a/tracer/test/Datadog.Trace.Tests/TraceContextTests.cs b/tracer/test/Datadog.Trace.Tests/TraceContextTests.cs index 140daa58ad0d..89146a584d97 100644 --- a/tracer/test/Datadog.Trace.Tests/TraceContextTests.cs +++ b/tracer/test/Datadog.Trace.Tests/TraceContextTests.cs @@ -4,10 +4,6 @@ // using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using Datadog.Trace.Configuration; -using Datadog.Trace.PlatformHelpers; using Datadog.Trace.TestHelpers; using Datadog.Trace.Util; using FluentAssertions; @@ -18,7 +14,7 @@ namespace Datadog.Trace.Tests { public class TraceContextTests { - private readonly Mock _tracerMock = new Mock(); + private readonly Mock _tracerMock = new(); [Fact] public void UtcNow_GivesLegitTime() @@ -168,11 +164,12 @@ public void FullFlushShouldNotPropagateSamplingPriority() // but a full flush should be triggered rather than a partial, because every span in the trace has been closed traceContext.CloseSpan(rootSpan); - spans.Value.Should().NotBeNullOrEmpty("a full flush should have been triggered"); + spans.Should().NotBeNull("a full flush should have been triggered"); + spans!.Value.Should().NotBeNullOrEmpty("a full flush should have been triggered"); rootSpan.GetMetric(Metrics.SamplingPriority).Should().BeNull("because sampling priority is not added until serialization"); - spans.Value.Should().OnlyContain(s => s.GetMetric(Metrics.SamplingPriority) == null, "because sampling priority is not added until serialization"); + spans!.Value.Should().OnlyContain(s => s.GetMetric(Metrics.SamplingPriority) == null, "because sampling priority is not added until serialization"); } [Fact] @@ -214,8 +211,9 @@ public void PartialFlushShouldPropagateMetadata() traceContext.CloseSpan(span); } - spans.Value.Should().NotBeNullOrEmpty("partial flush should have been triggered"); - spans.Value.Should().OnlyContain(s => s.GetMetric(Metrics.SamplingPriority) == null, "because sampling priority is not added until serialization"); + spans.Should().NotBeNull("partial flush should have been triggered"); + spans!.Value.Should().NotBeNullOrEmpty("partial flush should have been triggered"); + spans!.Value.Should().OnlyContain(s => s.GetMetric(Metrics.SamplingPriority) == null, "because sampling priority is not added until serialization"); } } } diff --git a/tracer/test/test-applications/integrations/Samples.WebRequest/Program.cs b/tracer/test/test-applications/integrations/Samples.WebRequest/Program.cs index 215a2576d7c6..804d3ae7ae42 100644 --- a/tracer/test/test-applications/integrations/Samples.WebRequest/Program.cs +++ b/tracer/test/test-applications/integrations/Samples.WebRequest/Program.cs @@ -40,8 +40,12 @@ public static async Task Main(string[] args) // send http requests using WebClient Console.WriteLine(); - Console.WriteLine("Sending request with WebClient."); + Console.WriteLine("Sending requests with WebClient."); await RequestHelpers.SendWebClientRequests(_tracingDisabled, url, RequestContent); + + // send http requests using WebRequest + Console.WriteLine(); + Console.WriteLine("Sending requests with WebRequest."); await RequestHelpers.SendWebRequestRequests(_tracingDisabled, url, RequestContent); Console.WriteLine(); @@ -55,6 +59,20 @@ private static void HandleHttpRequests(HttpListenerContext context) { Console.WriteLine("[HttpListener] received request"); + // read request content and headers. + // output the headers to the console first _before_ asserting their presence. + using (var reader = new StreamReader(context.Request.InputStream, context.Request.ContentEncoding)) + { + string requestContent = reader.ReadToEnd(); + Console.WriteLine($"[HttpListener] request content: {requestContent}"); + + foreach (string headerName in context.Request.Headers) + { + string headerValue = context.Request.Headers[headerName]; + Console.WriteLine($"[HttpListener] request header: {headerName}={headerValue}"); + } + } + // Check Datadog headers if (!_ignoreAsync || context.Request.Url.Query.IndexOf("Async", StringComparison.OrdinalIgnoreCase) == -1) { @@ -81,19 +99,6 @@ private static void HandleHttpRequests(HttpListenerContext context) } } - // read request content and headers - using (var reader = new StreamReader(context.Request.InputStream, context.Request.ContentEncoding)) - { - string requestContent = reader.ReadToEnd(); - Console.WriteLine($"[HttpListener] request content: {requestContent}"); - - foreach (string headerName in context.Request.Headers) - { - string headerValue = context.Request.Headers[headerName]; - Console.WriteLine($"[HttpListener] request header: {headerName}={headerValue}"); - } - } - // write response content byte[] responseBytes = Utf8.GetBytes(ResponseContent); context.Response.ContentEncoding = Utf8; diff --git a/tracer/test/test-applications/integrations/Samples.WebRequest/RequestHelpers.cs b/tracer/test/test-applications/integrations/Samples.WebRequest/RequestHelpers.cs index b57518719907..0b9752e87727 100644 --- a/tracer/test/test-applications/integrations/Samples.WebRequest/RequestHelpers.cs +++ b/tracer/test/test-applications/integrations/Samples.WebRequest/RequestHelpers.cs @@ -657,10 +657,8 @@ private static string GetUrlForTest(string testName, string baseUrl) private static NameValueCollection GenerateCurrentDistributedTracingHeaders() { var current = Activity.Current; - var decimalTraceId = Convert.ToUInt64(current.TraceId.ToHexString().Substring(16, 16), 16); - var decimalSpanId = Convert.ToUInt64(current.SpanId.ToHexString(), 16); - return new NameValueCollection() + return new NameValueCollection { { "traceparent", current.Id }, { "tracestate", current.TraceStateString },