diff --git a/Datadog.Trace.sln b/Datadog.Trace.sln index b521f4de89..4a1643e926 100644 --- a/Datadog.Trace.sln +++ b/Datadog.Trace.sln @@ -33,13 +33,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .gitattributes = .gitattributes .gitignore = .gitignore Datadog.Trace.proj = Datadog.Trace.proj - OpenTelemetry.AutoInstrumentation.snk = OpenTelemetry.AutoInstrumentation.snk Directory.Build.props = Directory.Build.props docker-compose.yml = docker-compose.yml GlobalSuppressions.cs = GlobalSuppressions.cs integrations.json = integrations.json LICENSE = LICENSE LICENSE-3rdparty.csv = LICENSE-3rdparty.csv + OpenTelemetry.AutoInstrumentation.snk = OpenTelemetry.AutoInstrumentation.snk docs\README.md = docs\README.md stylecop.json = stylecop.json EndProjectSection @@ -428,7 +428,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "crank", "crank", "{E3FB283A EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DuplicateTypeProxy", "test\test-applications\regression\DuplicateTypeProxy\DuplicateTypeProxy.csproj", "{34B67004-7249-4EF1-8E12-6E6DA37EA6BE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.AspNetCoreRazorPages", "test\test-applications\integrations\Samples.AspNetCoreRazorPages\Samples.AspNetCoreRazorPages.csproj", "{1B9E6BF4-9D48-4988-9945-248096119E46}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.AspNetCoreRazorPages", "test\test-applications\integrations\Samples.AspNetCoreRazorPages\Samples.AspNetCoreRazorPages.csproj", "{1B9E6BF4-9D48-4988-9945-248096119E46}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.Vendoring", "test\test-applications\integrations\Samples.Vendoring\Samples.Vendoring.csproj", "{0D4ACA4A-44DC-4603-8992-926B5BED5D76}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution @@ -1590,18 +1592,28 @@ Global {34B67004-7249-4EF1-8E12-6E6DA37EA6BE}.Release|x64.Build.0 = Release|x64 {34B67004-7249-4EF1-8E12-6E6DA37EA6BE}.Release|x86.ActiveCfg = Release|x86 {34B67004-7249-4EF1-8E12-6E6DA37EA6BE}.Release|x86.Build.0 = Release|x86 - {1B9E6BF4-9D48-4988-9945-248096119E46}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B9E6BF4-9D48-4988-9945-248096119E46}.Release|Any CPU.Build.0 = Release|Any CPU {1B9E6BF4-9D48-4988-9945-248096119E46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1B9E6BF4-9D48-4988-9945-248096119E46}.Debug|Any CPU.Build.0 = Debug|Any CPU {1B9E6BF4-9D48-4988-9945-248096119E46}.Debug|x64.ActiveCfg = Debug|Any CPU {1B9E6BF4-9D48-4988-9945-248096119E46}.Debug|x64.Build.0 = Debug|Any CPU {1B9E6BF4-9D48-4988-9945-248096119E46}.Debug|x86.ActiveCfg = Debug|Any CPU {1B9E6BF4-9D48-4988-9945-248096119E46}.Debug|x86.Build.0 = Debug|Any CPU + {1B9E6BF4-9D48-4988-9945-248096119E46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B9E6BF4-9D48-4988-9945-248096119E46}.Release|Any CPU.Build.0 = Release|Any CPU {1B9E6BF4-9D48-4988-9945-248096119E46}.Release|x64.ActiveCfg = Release|Any CPU {1B9E6BF4-9D48-4988-9945-248096119E46}.Release|x64.Build.0 = Release|Any CPU {1B9E6BF4-9D48-4988-9945-248096119E46}.Release|x86.ActiveCfg = Release|Any CPU {1B9E6BF4-9D48-4988-9945-248096119E46}.Release|x86.Build.0 = Release|Any CPU + {0D4ACA4A-44DC-4603-8992-926B5BED5D76}.Debug|Any CPU.ActiveCfg = Debug|x86 + {0D4ACA4A-44DC-4603-8992-926B5BED5D76}.Debug|x64.ActiveCfg = Debug|x64 + {0D4ACA4A-44DC-4603-8992-926B5BED5D76}.Debug|x64.Build.0 = Debug|x64 + {0D4ACA4A-44DC-4603-8992-926B5BED5D76}.Debug|x86.ActiveCfg = Debug|x86 + {0D4ACA4A-44DC-4603-8992-926B5BED5D76}.Debug|x86.Build.0 = Debug|x86 + {0D4ACA4A-44DC-4603-8992-926B5BED5D76}.Release|Any CPU.ActiveCfg = Release|x86 + {0D4ACA4A-44DC-4603-8992-926B5BED5D76}.Release|x64.ActiveCfg = Release|x64 + {0D4ACA4A-44DC-4603-8992-926B5BED5D76}.Release|x64.Build.0 = Release|x64 + {0D4ACA4A-44DC-4603-8992-926B5BED5D76}.Release|x86.ActiveCfg = Release|x86 + {0D4ACA4A-44DC-4603-8992-926B5BED5D76}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1727,6 +1739,7 @@ Global {E3FB283A-B766-4887-95E1-329667671921} = {A0C5FBBB-CFB2-4FB9-B8F0-55676E9DCF06} {34B67004-7249-4EF1-8E12-6E6DA37EA6BE} = {498A300E-D036-49B7-A43D-821D1CAF11A5} {1B9E6BF4-9D48-4988-9945-248096119E46} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A} + {0D4ACA4A-44DC-4603-8992-926B5BED5D76} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {160A1D00-1F5B-40F8-A155-621B4459D78F} diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Instrumentation.cs b/src/Datadog.Trace.ClrProfiler.Managed/Instrumentation.cs index 74fdb4193c..baa7b13094 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/Instrumentation.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/Instrumentation.cs @@ -4,6 +4,7 @@ using Datadog.Trace.Configuration; using Datadog.Trace.DiagnosticListeners; using Datadog.Trace.Logging; +using Datadog.Trace.Plugins; using Datadog.Trace.ServiceFabric; namespace Datadog.Trace.ClrProfiler @@ -59,8 +60,11 @@ public static void Initialize() try { - // ensure global instance is created if it's not already - _ = Tracer.Instance; + // Creates GlobalSettings instance and loads plugins + var plugins = PluginManager.TryLoadPlugins(GlobalSettings.Source.PluginsConfiguration); + + // First call to create Tracer instace + Tracer.Instance = new Tracer(plugins); } catch { diff --git a/src/Datadog.Trace/Configuration/ConfigurationKeys.cs b/src/Datadog.Trace/Configuration/ConfigurationKeys.cs index 23c7646b35..b63e2dd626 100644 --- a/src/Datadog.Trace/Configuration/ConfigurationKeys.cs +++ b/src/Datadog.Trace/Configuration/ConfigurationKeys.cs @@ -12,6 +12,11 @@ public static class ConfigurationKeys /// public const string ConfigurationFileName = "OTEL_TRACE_CONFIG_FILE"; + /// + /// Configuration key for the path to the plugins configuration file. + /// + public const string PluginConfigurationFileName = "OTEL_DOTNET_TRACER_PLUGINS_FILE"; + /// /// Configuration key for the application's environment. Sets the "env" tag on every . /// diff --git a/src/Datadog.Trace/Configuration/GlobalSettings.cs b/src/Datadog.Trace/Configuration/GlobalSettings.cs index 77b1450a3a..4427a1cca0 100644 --- a/src/Datadog.Trace/Configuration/GlobalSettings.cs +++ b/src/Datadog.Trace/Configuration/GlobalSettings.cs @@ -32,6 +32,11 @@ internal GlobalSettings(IConfigurationSource source) DiagnosticSourceEnabled = source?.GetBool(ConfigurationKeys.DiagnosticSourceEnabled) ?? // default value true; + + if (TryLoadPluginJsonConfigurationFile(source, out JsonConfigurationSource jsonConfigurationSource)) + { + PluginsConfiguration = jsonConfigurationSource; + } } /// @@ -55,6 +60,11 @@ internal GlobalSettings(IConfigurationSource source) /// internal bool DiagnosticSourceEnabled { get; } + /// + /// Gets the plugins configuration. + /// + internal JsonConfigurationSource PluginsConfiguration { get; } + /// /// Set whether debug mode is enabled. /// Affects the level of logs written to file. @@ -121,15 +131,28 @@ internal static CompositeConfigurationSource CreateDefaultConfigurationSource() return configurationSource; } - private static bool TryLoadJsonConfigurationFile(IConfigurationSource configurationSource, out IConfigurationSource jsonConfigurationSource) + private static bool TryLoadPluginJsonConfigurationFile(IConfigurationSource configurationSource, out JsonConfigurationSource jsonConfigurationSource) + { + var configurationFileName = configurationSource?.GetString(ConfigurationKeys.PluginConfigurationFileName) ?? + Path.Combine(GetCurrentDirectory(), "plugins.json"); + + return TryLoadJsonConfigurationFile(configurationFileName, out jsonConfigurationSource); + } + + private static bool TryLoadJsonConfigurationFile(IConfigurationSource configurationSource, out JsonConfigurationSource jsonConfigurationSource) + { + // if environment variable is not set, look for default file name in the current directory + var configurationFileName = configurationSource.GetString(ConfigurationKeys.ConfigurationFileName) ?? + configurationSource.GetString("OTEL_DOTNET_TRACER_CONFIG_FILE") ?? + Path.Combine(GetCurrentDirectory(), "datadog.json"); + + return TryLoadJsonConfigurationFile(configurationFileName, out jsonConfigurationSource); + } + + private static bool TryLoadJsonConfigurationFile(string configurationFileName, out JsonConfigurationSource jsonConfigurationSource) { try { - // if environment variable is not set, look for default file name in the current directory - var configurationFileName = configurationSource.GetString(ConfigurationKeys.ConfigurationFileName) ?? - configurationSource.GetString("OTEL_DOTNET_TRACER_CONFIG_FILE") ?? - Path.Combine(GetCurrentDirectory(), "datadog.json"); - if (string.Equals(Path.GetExtension(configurationFileName), ".JSON", StringComparison.OrdinalIgnoreCase) && File.Exists(configurationFileName)) { diff --git a/src/Datadog.Trace/Configuration/JsonConfigurationSource.cs b/src/Datadog.Trace/Configuration/JsonConfigurationSource.cs index f19215a520..3a22e8728f 100644 --- a/src/Datadog.Trace/Configuration/JsonConfigurationSource.cs +++ b/src/Datadog.Trace/Configuration/JsonConfigurationSource.cs @@ -145,6 +145,15 @@ public IDictionary GetDictionary(string key, bool allowOptionalM return GetDictionaryInternal(key, allowOptionalMappings); } + /// + /// Gets the string representation of json config. + /// + /// String format. + public override string ToString() + { + return _configuration.ToString(); + } + private IDictionary GetDictionaryInternal(string key, bool allowOptionalMappings) { var token = _configuration.SelectToken(key, errorWhenNoMatch: false); diff --git a/src/Datadog.Trace/Configuration/PropagatorType.cs b/src/Datadog.Trace/Configuration/PropagatorType.cs deleted file mode 100644 index 1ba5a5de9d..0000000000 --- a/src/Datadog.Trace/Configuration/PropagatorType.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Datadog.Trace.Configuration -{ - /// - /// Enumeration for the available propagator types. - /// - public enum PropagatorType - { - /// - /// The default propagator. - /// Default is Datadog. - /// - Default, - - /// - /// The Datadog propagator. - /// - Datadog, - - /// - /// The B3 propagator - /// - B3, - - /// - /// The W3C propagator - /// - W3C - } -} diff --git a/src/Datadog.Trace/Configuration/TracerSettings.cs b/src/Datadog.Trace/Configuration/TracerSettings.cs index 5d550dce05..e6b223996f 100644 --- a/src/Datadog.Trace/Configuration/TracerSettings.cs +++ b/src/Datadog.Trace/Configuration/TracerSettings.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using Datadog.Trace.Configuration.Types; using Datadog.Trace.ExtensionMethods; using Datadog.Trace.PlatformHelpers; using Datadog.Trace.Util; @@ -369,7 +370,7 @@ public TracerSettings(IConfigurationSource source) /// Default is Datadog /// /// - public HashSet Propagators { get; set; } + public HashSet Propagators { get; set; } /// /// Gets or sets a value indicating whether runtime metrics @@ -640,18 +641,18 @@ internal string GetServiceName(Tracer tracer, string serviceName) return ServiceNameMappings.GetServiceName(tracer.DefaultServiceName, serviceName); } - private static HashSet GetPropagators(IConfigurationSource source) + private static HashSet GetPropagators(IConfigurationSource source) { - var propagators = source.GetTypedValues(ConfigurationKeys.Propagators); + var propagators = source.GetStrings(ConfigurationKeys.Propagators); if (!propagators.Any()) { // TODO: Default to W3C (be aware of integration tests) // see more: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md#global-propagators - return new HashSet() { PropagatorType.Datadog }; + return new HashSet() { PropagatorTypes.Datadog }; } - return new HashSet(propagators); + return new HashSet(propagators); } } } diff --git a/src/Datadog.Trace/Configuration/Types/PropagatorTypes.cs b/src/Datadog.Trace/Configuration/Types/PropagatorTypes.cs new file mode 100644 index 0000000000..5b1f9cfc5e --- /dev/null +++ b/src/Datadog.Trace/Configuration/Types/PropagatorTypes.cs @@ -0,0 +1,23 @@ +namespace Datadog.Trace.Configuration.Types +{ + /// + /// Contains default available propagator types. + /// + public static class PropagatorTypes + { + /// + /// The Datadog propagator. + /// + public const string Datadog = "Datadog"; + + /// + /// The B3 propagator + /// + public const string B3 = "B3"; + + /// + /// The W3C propagator + /// + public const string W3C = "W3C"; + } +} diff --git a/src/Datadog.Trace/Conventions/ITraceIdConvention.cs b/src/Datadog.Trace/Conventions/ITraceIdConvention.cs index c9c6638547..770b764e3a 100644 --- a/src/Datadog.Trace/Conventions/ITraceIdConvention.cs +++ b/src/Datadog.Trace/Conventions/ITraceIdConvention.cs @@ -3,10 +3,19 @@ namespace Datadog.Trace.Conventions /// /// Convention used when defining format of TraceId. /// - internal interface ITraceIdConvention + public interface ITraceIdConvention { + /// + /// Generates new unique trace id based on convention. + /// + /// Trace id. TraceId GenerateNewTraceId(); + /// + /// Creates new trace id based on given string. + /// + /// String of id. + /// Trace id. TraceId CreateFromString(string id); } } diff --git a/src/Datadog.Trace/FrameworkDescription.cs b/src/Datadog.Trace/FrameworkDescription.cs index 803e31d89a..856d6e65f7 100644 --- a/src/Datadog.Trace/FrameworkDescription.cs +++ b/src/Datadog.Trace/FrameworkDescription.cs @@ -1,10 +1,8 @@ using System; -using System.Linq; +using System.Collections.Generic; using System.Reflection; -using System.Runtime.InteropServices; -using System.Text.RegularExpressions; +using System.Runtime.Versioning; using Datadog.Trace.Logging; -using Microsoft.Win32; namespace Datadog.Trace { @@ -29,6 +27,15 @@ internal partial class FrameworkDescription Tuple.Create(378389, "4.5"), }; + private static readonly IReadOnlyDictionary TargetFrameworkMapping = new Dictionary() + { + { ".NETFramework,Version=v4.5", "net45" }, + { ".NETFramework,Version=v4.6.1", "net461" }, + { ".NETStandard,Version=v2.0", "netstandard2.0" }, + { ".NETCoreApp,Version=v3.1", "netcoreapp3.1" }, + { ".NETCoreApp,Version=v5.0", "net50" } + }; + private FrameworkDescription( string name, string productVersion, @@ -41,6 +48,7 @@ private FrameworkDescription( OSPlatform = osPlatform; OSArchitecture = osArchitecture; ProcessArchitecture = processArchitecture; + TargetFramework = GetTargetFramework(); } public string Name { get; } @@ -53,6 +61,8 @@ private FrameworkDescription( public string ProcessArchitecture { get; } + public string TargetFramework { get; } + public override string ToString() { // examples: @@ -61,6 +71,20 @@ public override string ToString() return $"{Name} {ProductVersion} {ProcessArchitecture} on {OSPlatform} {OSArchitecture}"; } + private static string GetTargetFramework() + { + var framework = typeof(FrameworkDescription).Assembly + .GetCustomAttribute()? + .FrameworkName; + + if (!TargetFrameworkMapping.TryGetValue(framework, out string targetFramework)) + { + throw new InvalidOperationException($"Target framework mapping is not defined for '{framework}'"); + } + + return targetFramework; + } + private static string GetVersionFromAssemblyAttributes() { string productVersion = null; diff --git a/src/Datadog.Trace/Plugins/IOTelExtension.cs b/src/Datadog.Trace/Plugins/IOTelExtension.cs new file mode 100644 index 0000000000..d97eb2c55e --- /dev/null +++ b/src/Datadog.Trace/Plugins/IOTelExtension.cs @@ -0,0 +1,9 @@ +namespace Datadog.Trace.Plugins +{ + /// + /// Base marker interface for extendability points. + /// + public interface IOTelExtension + { + } +} diff --git a/src/Datadog.Trace/Plugins/PluginManager.cs b/src/Datadog.Trace/Plugins/PluginManager.cs new file mode 100644 index 0000000000..5864fffec2 --- /dev/null +++ b/src/Datadog.Trace/Plugins/PluginManager.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Datadog.Trace.Configuration; +using Datadog.Trace.Logging; +using Datadog.Trace.Util; +using Datadog.Trace.Vendors.Newtonsoft.Json.Linq; + +namespace Datadog.Trace.Plugins +{ + internal static class PluginManager + { + private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(PluginManager)); + + internal static IReadOnlyCollection TryLoadPlugins(JsonConfigurationSource pluginsConfig) + { + if (pluginsConfig == null) + { + return ArrayHelper.Empty(); + } + + var targetFramework = FrameworkDescription.Instance.TargetFramework; + + Log.Debug("Executing plugins configuration: {0}", pluginsConfig); + Log.Information("Trying to load plugins with target framework '{0}'.", targetFramework); + + string[] pluginFiles; + + try + { + // TODO: Here additional metadata could be loaded (eg: for security and integrity) + // instead of just string path + pluginFiles = pluginsConfig.GetValue($"['{targetFramework}']").ToObject(); + } + catch (ArgumentException ex) + { + Log.Warning(ex, "Could not parse list of plugin paths. Invalid plugin configuration provided."); + + return ArrayHelper.Empty(); + } + + if (pluginFiles == null || !pluginFiles.Any()) + { + Log.Information("Skipping plugins load. Could not find any plugins with target framework '{0}'.", targetFramework); + + return ArrayHelper.Empty(); + } + + var loadedPlugins = TryLoadPlugins(pluginFiles); + + Log.Information("Successfully loaded '{0}' plugin(s).", property: loadedPlugins.Count); + + return loadedPlugins; + } + + private static IReadOnlyCollection TryLoadPlugins(string[] pluginFiles) + { + var loaded = new List(); + + foreach (string file in pluginFiles) + { + string fullPath = Path.GetFullPath(file); + + if (File.Exists(fullPath)) + { + try + { + Assembly pluginAssembly = Assembly.LoadFrom(fullPath); + ICollection extensions = GetExtensions(pluginAssembly); + + if (extensions != null && extensions.Any()) + { + loaded.AddRange(extensions); + + Log.Information("Plugin assembly loaded '{0}'.", pluginAssembly.FullName); + } + else + { + Log.Warning("Could not load {0} from '{1}'.", nameof(IOTelExtension), pluginAssembly.FullName); + } + } + catch (Exception ex) when ( + ExceptionUtil.IsAssemblyLoadException(ex) || + ExceptionUtil.IsDynamicInvocationException(ex)) + { + Log.Warning(ex, "Plugin assembly could not be loaded. Skipping vendor plugin load."); + } + } + else + { + Log.Warning("Plugin path is defined but could not find the path '{0}'.", fullPath); + } + } + + return loaded; + } + + private static ICollection GetExtensions(Assembly assembly) + { + var extensionType = typeof(IOTelExtension); + + return assembly + .GetTypes() + .Where(p => extensionType.IsAssignableFrom(p)) + .Select(p => (IOTelExtension)Activator.CreateInstance(p)) + .ToList(); + } + } +} diff --git a/src/Datadog.Trace/Propagation/B3SpanContextPropagator.cs b/src/Datadog.Trace/Propagation/B3SpanContextPropagator.cs index 44353acb05..d0e124f203 100644 --- a/src/Datadog.Trace/Propagation/B3SpanContextPropagator.cs +++ b/src/Datadog.Trace/Propagation/B3SpanContextPropagator.cs @@ -10,7 +10,7 @@ namespace Datadog.Trace.Propagation /// /// Class that handles B3 style context propagation. /// - internal class B3SpanContextPropagator : IPropagator + public class B3SpanContextPropagator : IPropagator { private const NumberStyles NumberStyle = NumberStyles.HexNumber; @@ -20,12 +20,17 @@ internal class B3SpanContextPropagator : IPropagator private readonly ITraceIdConvention _traceIdConvention; + /// + /// Initializes a new instance of the class. + /// + /// Trace id convention public B3SpanContextPropagator(ITraceIdConvention traceIdConvention) { _traceIdConvention = traceIdConvention; } - public void Inject(SpanContext context, T carrier, Action setter) + /// + public virtual void Inject(SpanContext context, T carrier, Action setter) { if (context == null) { throw new ArgumentNullException(nameof(context)); } @@ -51,7 +56,8 @@ public void Inject(SpanContext context, T carrier, Action } } - public SpanContext Extract(T carrier, Func> getter) + /// + public virtual SpanContext Extract(T carrier, Func> getter) { if (carrier == null) { throw new ArgumentNullException(nameof(carrier)); } diff --git a/src/Datadog.Trace/Propagation/CompositePropagatorsProvider.cs b/src/Datadog.Trace/Propagation/CompositePropagatorsProvider.cs new file mode 100644 index 0000000000..88d76ffdae --- /dev/null +++ b/src/Datadog.Trace/Propagation/CompositePropagatorsProvider.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Datadog.Trace.Conventions; +using Datadog.Trace.Logging; + +namespace Datadog.Trace.Propagation +{ + internal class CompositePropagatorsProvider + { + private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(); + + private readonly ICollection _providers; + + public CompositePropagatorsProvider() + { + _providers = new List(); + } + + public void RegisterProvider(IPropagatorsProvider provider) + { + _providers.Add(provider); + } + + public IEnumerable GetPropagators(IEnumerable propagatorIds, ITraceIdConvention traceIdConvention) + { + return propagatorIds.Select(type => GetPropagator(type, traceIdConvention)).ToList(); + } + + private IPropagator GetPropagator(string propagatorId, ITraceIdConvention traceIdConvention) + { + var propagator = _providers + .Where(x => x.CanProvide(propagatorId, traceIdConvention)) + .Select(x => x.GetPropagator(propagatorId, traceIdConvention)) + .FirstOrDefault(); + + if (propagator == null) + { + string msg = $"There is no propagator registered for type '{propagatorId}'."; + + Log.Error(msg); + + throw new InvalidOperationException(msg); + } + + return propagator; + } + } +} diff --git a/src/Datadog.Trace/Propagation/ContextPropagatorFactory.cs b/src/Datadog.Trace/Propagation/ContextPropagatorFactory.cs deleted file mode 100644 index 7869be072c..0000000000 --- a/src/Datadog.Trace/Propagation/ContextPropagatorFactory.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Datadog.Trace.Configuration; -using Datadog.Trace.Conventions; - -namespace Datadog.Trace.Propagation -{ - internal static class ContextPropagatorFactory - { - private static readonly IReadOnlyDictionary> PropagatorSelector = - new Dictionary>() - { - { PropagatorType.W3C, convention => new W3CSpanContextPropagator(convention) }, - { PropagatorType.B3, convention => new B3SpanContextPropagator(convention) }, - { PropagatorType.Datadog, convention => new DDSpanContextPropagator(convention) }, - { PropagatorType.Default, convention => new DDSpanContextPropagator(convention) }, - }; - - public static ICollection BuildPropagators(IEnumerable propagatorTypes, ITraceIdConvention traceIdConvention) - { - return propagatorTypes.Select(type => BuildPropagator(type, traceIdConvention)).ToList(); - } - - private static IPropagator BuildPropagator(PropagatorType propagatorType, ITraceIdConvention traceIdConvention) - { - if (PropagatorSelector.TryGetValue(propagatorType, out Func getter)) - { - // W3C propagator requires Otel TraceId convention as it's specification clearly states lengths of traceId and spanId values in the header. - if (propagatorType == PropagatorType.W3C && traceIdConvention is not OtelTraceIdConvention) - { - throw new NotSupportedException($"'{PropagatorType.W3C}' propagator requires '{ConventionType.OpenTelemetry}' convention to be set"); - } - - return getter(traceIdConvention); - } - - throw new InvalidOperationException($"There is no propagator registered for type '{propagatorType}'."); - } - } -} diff --git a/src/Datadog.Trace/Propagation/IPropagator.cs b/src/Datadog.Trace/Propagation/IPropagator.cs index 405fe1d547..aa3a712e22 100644 --- a/src/Datadog.Trace/Propagation/IPropagator.cs +++ b/src/Datadog.Trace/Propagation/IPropagator.cs @@ -3,10 +3,13 @@ namespace Datadog.Trace.Propagation { - internal interface IPropagator + /// + /// Specifies interface for span context propagator. + /// + public interface IPropagator { /// - /// Propagates the specified context by adding new headers to a carrier + /// Propagates the specified context by adding new headers to a carrier. /// This locks the sampling priority for . /// /// A value that will be propagated into instance. diff --git a/src/Datadog.Trace/Propagation/IPropagatorsProvider.cs b/src/Datadog.Trace/Propagation/IPropagatorsProvider.cs new file mode 100644 index 0000000000..930a1319d1 --- /dev/null +++ b/src/Datadog.Trace/Propagation/IPropagatorsProvider.cs @@ -0,0 +1,28 @@ +using Datadog.Trace.Conventions; +using Datadog.Trace.Plugins; + +namespace Datadog.Trace.Propagation +{ + /// + /// Factory interface for propagators. + /// Implementation must have a default (parameterless) constructor. + /// + public interface IPropagatorsProvider : IOTelExtension + { + /// + /// Gets propagator for propagator id. + /// + /// Propagator id. + /// Trace id convention. + /// Context propagator. + IPropagator GetPropagator(string propagatorId, ITraceIdConvention traceIdConvention); + + /// + /// Gets if provider can provide propagator for required spec. + /// + /// Propagator id. + /// Trace id convention. + /// Is providable. + bool CanProvide(string propagatorId, ITraceIdConvention traceIdConvention); + } +} diff --git a/src/Datadog.Trace/Propagation/OTelPropagatorsProvider.cs b/src/Datadog.Trace/Propagation/OTelPropagatorsProvider.cs new file mode 100644 index 0000000000..f39bbf92d0 --- /dev/null +++ b/src/Datadog.Trace/Propagation/OTelPropagatorsProvider.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using Datadog.Trace.Configuration; +using Datadog.Trace.Configuration.Types; +using Datadog.Trace.Conventions; + +namespace Datadog.Trace.Propagation +{ + internal class OTelPropagatorsProvider : IPropagatorsProvider + { + private static readonly IReadOnlyDictionary> PropagatorSelector = + new Dictionary>(StringComparer.InvariantCultureIgnoreCase) + { + { PropagatorTypes.W3C, convention => new W3CSpanContextPropagator(convention) }, + { PropagatorTypes.B3, convention => new B3SpanContextPropagator(convention) }, + { PropagatorTypes.Datadog, convention => new DDSpanContextPropagator(convention) }, + }; + + public bool CanProvide(string propagatorId, ITraceIdConvention traceIdConvention) + { + if (propagatorId == PropagatorTypes.W3C && traceIdConvention is not OtelTraceIdConvention) + { + return false; + } + + return PropagatorSelector.ContainsKey(propagatorId); + } + + /// + /// Builds the propagator with given spec. + /// + /// Propagator id. + /// Trace id convention. + /// Context propagator. + public IPropagator GetPropagator(string propagatorId, ITraceIdConvention traceIdConvention) + { + if (PropagatorSelector.TryGetValue(propagatorId, out Func getter)) + { + // W3C propagator requires Otel TraceId convention as it's specification clearly states lengths of traceId and spanId values in the header. + if (propagatorId == PropagatorTypes.W3C && traceIdConvention is not OtelTraceIdConvention) + { + throw new NotSupportedException($"'{PropagatorTypes.W3C}' propagator requires '{ConventionType.OpenTelemetry}' convention to be set"); + } + + return getter(traceIdConvention); + } + + throw new InvalidOperationException($"There is no propagator registered for type '{propagatorId}'."); + } + } +} diff --git a/src/Datadog.Trace/Propagation/W3CSpanContextPropagator.cs b/src/Datadog.Trace/Propagation/W3CSpanContextPropagator.cs index 28299028eb..9eaf87f715 100644 --- a/src/Datadog.Trace/Propagation/W3CSpanContextPropagator.cs +++ b/src/Datadog.Trace/Propagation/W3CSpanContextPropagator.cs @@ -9,7 +9,10 @@ namespace Datadog.Trace.Propagation { - internal class W3CSpanContextPropagator : IPropagator + /// + /// Class that handles W3C style context propagation. + /// + public class W3CSpanContextPropagator : IPropagator { private const string TraceParentFormat = "00-{0}-{1}-01"; @@ -27,12 +30,17 @@ internal class W3CSpanContextPropagator : IPropagator private readonly ITraceIdConvention _traceIdConvention; + /// + /// Initializes a new instance of the class. + /// + /// Trace id convention public W3CSpanContextPropagator(ITraceIdConvention traceIdConvention) { _traceIdConvention = traceIdConvention; } - public void Inject(SpanContext context, T carrier, Action setter) + /// + public virtual void Inject(SpanContext context, T carrier, Action setter) { // lock sampling priority when span propagates. context.TraceContext?.LockSamplingPriority(); @@ -44,7 +52,8 @@ public void Inject(SpanContext context, T carrier, Action } } - public SpanContext Extract(T carrier, Func> getter) + /// + public virtual SpanContext Extract(T carrier, Func> getter) { var traceParentCollection = getter(carrier, W3CHeaderNames.TraceParent).ToList(); if (traceParentCollection.Count != 1) diff --git a/src/Datadog.Trace/Tracer.cs b/src/Datadog.Trace/Tracer.cs index 86beafffc0..20023f5c2a 100644 --- a/src/Datadog.Trace/Tracer.cs +++ b/src/Datadog.Trace/Tracer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -14,6 +15,7 @@ using Datadog.Trace.DogStatsd; using Datadog.Trace.Logging; using Datadog.Trace.PlatformHelpers; +using Datadog.Trace.Plugins; using Datadog.Trace.Propagation; using Datadog.Trace.RuntimeMetrics; using Datadog.Trace.Sampling; @@ -67,7 +69,17 @@ static Tracer() /// Initializes a new instance of the class with default settings. /// public Tracer() - : this(settings: null, traceWriter: null, sampler: null, scopeManager: null, statsd: null) + : this(settings: null, plugins: null, traceWriter: null, sampler: null, scopeManager: null, statsd: null) + { + } + + /// + /// Initializes a new instance of the class and extends + /// implementation with plugins + /// + /// Plugins to extend with + public Tracer(IReadOnlyCollection plugins) + : this(settings: null, plugins: plugins, traceWriter: null, sampler: null, scopeManager: null, statsd: null) { } @@ -80,11 +92,11 @@ public Tracer() /// or null to use the default configuration sources. /// public Tracer(TracerSettings settings) - : this(settings, traceWriter: null, sampler: null, scopeManager: null, statsd: null) + : this(settings, plugins: null, traceWriter: null, sampler: null, scopeManager: null, statsd: null) { } - internal Tracer(TracerSettings settings, ITraceWriter traceWriter, ISampler sampler, IScopeManager scopeManager, IDogStatsd statsd) + internal Tracer(TracerSettings settings, IReadOnlyCollection plugins, ITraceWriter traceWriter, ISampler sampler, IScopeManager scopeManager, IDogStatsd statsd) { // update the count of Tracer instances Interlocked.Increment(ref _liveTracerCount); @@ -119,8 +131,7 @@ internal Tracer(TracerSettings settings, ITraceWriter traceWriter, ISampler samp _scopeManager = scopeManager ?? new AsyncLocalScopeManager(); Sampler = sampler ?? new RuleBasedSampler(new RateLimiter(Settings.MaxTracesSubmittedPerSecond)); - var propagators = ContextPropagatorFactory.BuildPropagators(Settings.Propagators, TraceIdConvention); - _propagator = new CompositeTextMapPropagator(propagators); + _propagator = CreateCompositePropagator(Settings, TraceIdConvention, plugins ?? ArrayHelper.Empty()); if (!string.IsNullOrWhiteSpace(Settings.CustomSamplingRules)) { @@ -745,6 +756,26 @@ private static ITraceWriter CreateTraceWriter(TracerSettings settings, IDogStats } } + private static CompositeTextMapPropagator CreateCompositePropagator(TracerSettings settings, ITraceIdConvention traceIdConvention, IReadOnlyCollection extensions) + { + var compositeProvider = new CompositePropagatorsProvider(); + compositeProvider.RegisterProvider(new OTelPropagatorsProvider()); + + foreach (var extension in extensions) + { + if (extension is IPropagatorsProvider provider) + { + compositeProvider.RegisterProvider(provider); + } + } + + var propagators = compositeProvider + .GetPropagators(settings.Propagators, traceIdConvention) + .ToList(); + + return new CompositeTextMapPropagator(propagators); + } + private void InitializeLibLogScopeEventSubscriber(IScopeManager scopeManager, string defaultServiceName, string version, string env) { new LibLogScopeEventSubscriber(scopeManager, defaultServiceName, version ?? string.Empty, env ?? string.Empty); diff --git a/src/Datadog.Trace/Util/ExceptionUtil.cs b/src/Datadog.Trace/Util/ExceptionUtil.cs new file mode 100644 index 0000000000..200bd5253a --- /dev/null +++ b/src/Datadog.Trace/Util/ExceptionUtil.cs @@ -0,0 +1,37 @@ +using System; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security; + +namespace Datadog.Trace.Util +{ + internal static class ExceptionUtil + { + public static bool IsAssemblyLoadException(Exception ex) + { + return ex is ArgumentNullException + or FileNotFoundException + or FileLoadException + or BadImageFormatException + or SecurityException + or ArgumentException + or PathTooLongException; + } + + public static bool IsDynamicInvocationException(Exception ex) + { + return ex is ReflectionTypeLoadException + or ArgumentNullException + or ArgumentException + or NotSupportedException + or TargetInvocationException + or MethodAccessException + or MemberAccessException + or InvalidComObjectException + or MissingMethodException + or COMException + or TypeLoadException; + } + } +} diff --git a/test/Datadog.Trace.ClrProfiler.Managed.Tests/OtelScopeFactoryTests.cs b/test/Datadog.Trace.ClrProfiler.Managed.Tests/OtelScopeFactoryTests.cs index 4a6f044240..e2da904278 100644 --- a/test/Datadog.Trace.ClrProfiler.Managed.Tests/OtelScopeFactoryTests.cs +++ b/test/Datadog.Trace.ClrProfiler.Managed.Tests/OtelScopeFactoryTests.cs @@ -16,7 +16,7 @@ public void OutboundHttp(Input input, Result expected) { var settings = new TracerSettings(); settings.Convention = ConventionType.OpenTelemetry; - var tracer = new Tracer(settings, Mock.Of(), Mock.Of(), scopeManager: null, statsd: null); + var tracer = new Tracer(settings, plugins: null, Mock.Of(), Mock.Of(), scopeManager: null, statsd: null); using (var scope = ScopeFactory.CreateOutboundHttpScope(tracer, input.Method, new Uri(input.Uri), new IntegrationInfo((int)IntegrationIds.HttpMessageHandler), out var tags)) { diff --git a/test/Datadog.Trace.ClrProfiler.Managed.Tests/ScopeFactoryTests.cs b/test/Datadog.Trace.ClrProfiler.Managed.Tests/ScopeFactoryTests.cs index 79156b3004..078dfe24ff 100644 --- a/test/Datadog.Trace.ClrProfiler.Managed.Tests/ScopeFactoryTests.cs +++ b/test/Datadog.Trace.ClrProfiler.Managed.Tests/ScopeFactoryTests.cs @@ -68,7 +68,7 @@ public void CleanUri_ResourceName(string uri, string method, string expected) var settings = new TracerSettings(); var writerMock = new Mock(); var samplerMock = new Mock(); - var tracer = new Tracer(settings, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); + var tracer = new Tracer(settings, plugins: null, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); using (var automaticScope = ScopeFactory.CreateOutboundHttpScope(tracer, method, new Uri(uri), new IntegrationInfo((int)IntegrationIds.HttpMessageHandler), out _)) { @@ -93,7 +93,7 @@ public void CleanUri_HttpUrlTag(string uri, string expected) var settings = new TracerSettings(); var writerMock = new Mock(); var samplerMock = new Mock(); - var tracer = new Tracer(settings, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); + var tracer = new Tracer(settings, plugins: null, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); const string method = "GET"; @@ -113,7 +113,7 @@ public void CreateOutboundHttpScope_AlwaysCreatesOneAutomaticInstrumentationScop var settings = new TracerSettings(); var writerMock = new Mock(); var samplerMock = new Mock(); - var tracer = new Tracer(settings, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); + var tracer = new Tracer(settings, plugins: null, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); const string method = "GET"; const string url = "http://www.contoso.com"; diff --git a/test/Datadog.Trace.IntegrationTests/DiagnosticListeners/AspNetCoreDiagnosticObserverTests.cs b/test/Datadog.Trace.IntegrationTests/DiagnosticListeners/AspNetCoreDiagnosticObserverTests.cs index 51f856d1d9..253404553b 100644 --- a/test/Datadog.Trace.IntegrationTests/DiagnosticListeners/AspNetCoreDiagnosticObserverTests.cs +++ b/test/Datadog.Trace.IntegrationTests/DiagnosticListeners/AspNetCoreDiagnosticObserverTests.cs @@ -261,7 +261,7 @@ private static Tracer GetTracer(ITraceWriter writer = null, IConfigurationSource var agentWriter = writer ?? new Mock().Object; var samplerMock = new Mock(); - return new Tracer(settings, agentWriter, samplerMock.Object, scopeManager: null, statsd: null); + return new Tracer(settings, plugins: null, agentWriter, samplerMock.Object, scopeManager: null, statsd: null); } private class AgentWriterStub : ITraceWriter diff --git a/test/Datadog.Trace.IntegrationTests/SendTracesToAgent.cs b/test/Datadog.Trace.IntegrationTests/SendTracesToAgent.cs index a72b28e9a9..aa80f8fb07 100644 --- a/test/Datadog.Trace.IntegrationTests/SendTracesToAgent.cs +++ b/test/Datadog.Trace.IntegrationTests/SendTracesToAgent.cs @@ -24,7 +24,7 @@ public SendTracesToAgent() var api = new Api(endpoint, apiRequestFactory: null, statsd: null); var agentWriter = new AgentWriter(api, new NullMetrics()); - _tracer = new Tracer(settings, agentWriter, sampler: null, scopeManager: null, statsd: null); + _tracer = new Tracer(settings, plugins: null, agentWriter, sampler: null, scopeManager: null, statsd: null); } [Fact(Skip = "Run manually")] diff --git a/test/Datadog.Trace.IntegrationTests/SendTracesToZipkinCollector.cs b/test/Datadog.Trace.IntegrationTests/SendTracesToZipkinCollector.cs index b3b4f0303d..b166c832a8 100644 --- a/test/Datadog.Trace.IntegrationTests/SendTracesToZipkinCollector.cs +++ b/test/Datadog.Trace.IntegrationTests/SendTracesToZipkinCollector.cs @@ -18,7 +18,7 @@ public SendTracesToZipkinCollector() var agentUri = new Uri($"http://localhost:{collectorPort}/api/v2/spans"); var exporter = new ZipkinExporter(agentUri); var exporterWriter = new ExporterWriter(exporter, new NullMetrics()); - _tracer = new Tracer(new TracerSettings(), exporterWriter, sampler: null, scopeManager: null, statsd: null); + _tracer = new Tracer(new TracerSettings(), plugins: null, exporterWriter, sampler: null, scopeManager: null, statsd: null); _zipkinCollector = new MockZipkinCollector(collectorPort); } diff --git a/test/Datadog.Trace.OpenTracing.IntegrationTests/OpenTracingSendTracesToAgent.cs b/test/Datadog.Trace.OpenTracing.IntegrationTests/OpenTracingSendTracesToAgent.cs index 2d6116629d..2e76e26be1 100644 --- a/test/Datadog.Trace.OpenTracing.IntegrationTests/OpenTracingSendTracesToAgent.cs +++ b/test/Datadog.Trace.OpenTracing.IntegrationTests/OpenTracingSendTracesToAgent.cs @@ -23,7 +23,7 @@ public OpenTracingSendTracesToAgent() var api = new Api(endpoint, apiRequestFactory: null, statsd: null); var agentWriter = new AgentWriter(api, new NullMetrics()); - var tracer = new Tracer(settings, agentWriter, sampler: null, scopeManager: null, statsd: null); + var tracer = new Tracer(settings, plugins: null, agentWriter, sampler: null, scopeManager: null, statsd: null); _tracer = new OpenTracingTracer(tracer); } diff --git a/test/Datadog.Trace.OpenTracing.Tests/OpenTracingSpanBuilderTests.cs b/test/Datadog.Trace.OpenTracing.Tests/OpenTracingSpanBuilderTests.cs index 91c037428e..60d1e7d2ef 100644 --- a/test/Datadog.Trace.OpenTracing.Tests/OpenTracingSpanBuilderTests.cs +++ b/test/Datadog.Trace.OpenTracing.Tests/OpenTracingSpanBuilderTests.cs @@ -24,7 +24,7 @@ public OpenTracingSpanBuilderTests() var writerMock = new Mock(MockBehavior.Strict); var samplerMock = new Mock(); - var datadogTracer = new Tracer(settings, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); + var datadogTracer = new Tracer(settings, plugins: null, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); _tracer = new OpenTracingTracer(datadogTracer); } diff --git a/test/Datadog.Trace.OpenTracing.Tests/OpenTracingSpanTests.cs b/test/Datadog.Trace.OpenTracing.Tests/OpenTracingSpanTests.cs index 373280eab2..8e118107f5 100644 --- a/test/Datadog.Trace.OpenTracing.Tests/OpenTracingSpanTests.cs +++ b/test/Datadog.Trace.OpenTracing.Tests/OpenTracingSpanTests.cs @@ -18,7 +18,7 @@ public OpenTracingSpanTests() var writerMock = new Mock(); var samplerMock = new Mock(); - var datadogTracer = new Tracer(settings, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); + var datadogTracer = new Tracer(settings, plugins: null, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); _tracer = new OpenTracingTracer(datadogTracer); } diff --git a/test/Datadog.Trace.OpenTracing.Tests/OpenTracingTracerTests.cs b/test/Datadog.Trace.OpenTracing.Tests/OpenTracingTracerTests.cs index 74c6d3ae36..010b9d640e 100644 --- a/test/Datadog.Trace.OpenTracing.Tests/OpenTracingTracerTests.cs +++ b/test/Datadog.Trace.OpenTracing.Tests/OpenTracingTracerTests.cs @@ -23,7 +23,7 @@ public OpenTracingTracerTests() var writerMock = new Mock(); var samplerMock = new Mock(); - var datadogTracer = new Tracer(settings, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); + var datadogTracer = new Tracer(settings, plugins: null, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); _tracer = new OpenTracingTracer(datadogTracer); } diff --git a/test/Datadog.Trace.Tests/CorrelationIdentifierTests.cs b/test/Datadog.Trace.Tests/CorrelationIdentifierTests.cs index ff716fc922..82b6c4612a 100644 --- a/test/Datadog.Trace.Tests/CorrelationIdentifierTests.cs +++ b/test/Datadog.Trace.Tests/CorrelationIdentifierTests.cs @@ -1,10 +1,6 @@ using System; using System.Reflection; -using Datadog.Trace.Agent; using Datadog.Trace.Configuration; -using Datadog.Trace.Sampling; -using Datadog.Trace.TestHelpers; -using Moq; using Xunit; using Xunit.Sdk; diff --git a/test/Datadog.Trace.Tests/DiagnosticListeners/AspNetCoreDiagnosticObserverTests.cs b/test/Datadog.Trace.Tests/DiagnosticListeners/AspNetCoreDiagnosticObserverTests.cs index 5a7323c20c..a229e3d3bb 100644 --- a/test/Datadog.Trace.Tests/DiagnosticListeners/AspNetCoreDiagnosticObserverTests.cs +++ b/test/Datadog.Trace.Tests/DiagnosticListeners/AspNetCoreDiagnosticObserverTests.cs @@ -89,7 +89,7 @@ private static Tracer GetTracer() var writerMock = new Mock(); var samplerMock = new Mock(); - return new Tracer(settings, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); + return new Tracer(settings, plugins: null, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); } private static HttpContext GetHttpContext() diff --git a/test/Datadog.Trace.Tests/DogStatsDTests.cs b/test/Datadog.Trace.Tests/DogStatsDTests.cs index d248d1cf33..6ec20e49b8 100644 --- a/test/Datadog.Trace.Tests/DogStatsDTests.cs +++ b/test/Datadog.Trace.Tests/DogStatsDTests.cs @@ -107,7 +107,7 @@ public void Send_metrics_when_enabled() StartupDiagnosticLogEnabled = false, }; - var tracer = new Tracer(settings, traceWriter: null, sampler: null, scopeManager: null, statsd); + var tracer = new Tracer(settings, plugins: null, traceWriter: null, sampler: null, scopeManager: null, statsd); using (var scope = tracer.StartActive("root")) { diff --git a/test/Datadog.Trace.Tests/Logging/LoggingProviderTestHelpers.cs b/test/Datadog.Trace.Tests/Logging/LoggingProviderTestHelpers.cs index 2d0b917bcd..d35a259d37 100644 --- a/test/Datadog.Trace.Tests/Logging/LoggingProviderTestHelpers.cs +++ b/test/Datadog.Trace.Tests/Logging/LoggingProviderTestHelpers.cs @@ -1,14 +1,9 @@ using System; -using System.Globalization; -using System.IO; -using System.Text; using Datadog.Trace.Agent; using Datadog.Trace.Configuration; using Datadog.Trace.Logging; using Datadog.Trace.Sampling; using Moq; -using Serilog.Formatting.Display; -using Xunit; namespace Datadog.Trace.Tests.Logging { @@ -28,7 +23,7 @@ internal static Tracer InitializeTracer(bool enableLogsInjection) settings.ServiceVersion = "custom-version"; settings.Environment = "custom-env"; - return new Tracer(settings, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); + return new Tracer(settings, plugins: null, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); } internal static void LogInSpanWithServiceName(Tracer tracer, ILog logger, Func openMappedContext, string service, out Scope scope) diff --git a/test/Datadog.Trace.Tests/Propagators/CompositePropagatorsProviderTests.cs b/test/Datadog.Trace.Tests/Propagators/CompositePropagatorsProviderTests.cs new file mode 100644 index 0000000000..b7b7eddaea --- /dev/null +++ b/test/Datadog.Trace.Tests/Propagators/CompositePropagatorsProviderTests.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Datadog.Trace.Conventions; +using Datadog.Trace.Propagation; +using Xunit; + +namespace Datadog.Trace.Tests.Propagators +{ + public class CompositePropagatorsProviderTests + { + private const string Propagator1 = "Propagator_1"; + private const string Propagator2 = "Propagator_2"; + private const string Propagator3 = "Propagator_3"; + + [Fact] + public void CompositePropagatorsProvider_GetPropagators() + { + var provider = new CompositePropagatorsProvider(); + provider.RegisterProvider(new ProviderStub(Propagator1)); + provider.RegisterProvider(new ProviderStub(Propagator3)); + + var propagators = provider + .GetPropagators(new[] { Propagator1, Propagator3 }, null) + .Cast() + .ToList(); + + Assert.Equal(2, propagators.Count()); + Assert.Equal(Propagator1, propagators[0].Id); + Assert.Equal(Propagator3, propagators[1].Id); + } + + [Fact] + public void CompositePropagatorsProvider_GetPropagators_When_NoProvider() + { + var provider = new CompositePropagatorsProvider(); + provider.RegisterProvider(new ProviderStub(Propagator1)); + + Assert.Throws(() => provider + .GetPropagators(new[] { Propagator2 }, null) + .Cast() + .ToList()); + } + + private class ProviderStub : IPropagatorsProvider + { + private readonly string _provides; + + public ProviderStub(string provides) + { + _provides = provides; + } + + public bool CanProvide(string propagatorId, ITraceIdConvention traceIdConvention) + { + return _provides == propagatorId; + } + + public IPropagator GetPropagator(string propagatorId, ITraceIdConvention traceIdConvention) + { + return new PropagatorStub(propagatorId); + } + } + + private class PropagatorStub : IPropagator + { + public PropagatorStub(string id) + { + Id = id; + } + + public string Id { get; } + + public SpanContext Extract(T carrier, Func> getter) + { + throw new NotImplementedException(); + } + + public void Inject(SpanContext context, T carrier, Action setter) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/test/Datadog.Trace.Tests/SpanTests.cs b/test/Datadog.Trace.Tests/SpanTests.cs index 5a45d94c63..2914b45141 100644 --- a/test/Datadog.Trace.Tests/SpanTests.cs +++ b/test/Datadog.Trace.Tests/SpanTests.cs @@ -27,7 +27,7 @@ public SpanTests(ITestOutputHelper output) _writerMock = new Mock(); var samplerMock = new Mock(); - _tracer = new Tracer(settings, _writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); + _tracer = new Tracer(settings, plugins: null, _writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); } [Fact] diff --git a/test/Datadog.Trace.Tests/TracerSettingsTests.cs b/test/Datadog.Trace.Tests/TracerSettingsTests.cs index 81f5139b28..375e2eac4c 100644 --- a/test/Datadog.Trace.Tests/TracerSettingsTests.cs +++ b/test/Datadog.Trace.Tests/TracerSettingsTests.cs @@ -33,7 +33,7 @@ public void ConfiguredTracerSettings_DefaultTagsSetFromEnvironmentVariable(strin IConfigurationSource source = new NameValueConfigurationSource(collection); var settings = new TracerSettings(source); - var tracer = new Tracer(settings, _writerMock.Object, _samplerMock.Object, scopeManager: null, statsd: null); + var tracer = new Tracer(settings, plugins: null, _writerMock.Object, _samplerMock.Object, scopeManager: null, statsd: null); var span = tracer.StartSpan("Operation"); Assert.Equal(span.GetTag(tagKey), value); @@ -52,7 +52,7 @@ public void DDVarTakesPrecedenceOverDDTags(string envKey, string tagKey) var settings = new TracerSettings(source); Assert.True(settings.GlobalTags.Any()); - var tracer = new Tracer(settings, _writerMock.Object, _samplerMock.Object, scopeManager: null, statsd: null); + var tracer = new Tracer(settings, plugins: null, _writerMock.Object, _samplerMock.Object, scopeManager: null, statsd: null); var span = tracer.StartSpan("Operation"); Assert.Equal(span.GetTag(tagKey), envValue); @@ -75,7 +75,7 @@ public void TraceEnabled(string value, bool areTracesEnabled) _writerMock.Invocations.Clear(); - var tracer = new Tracer(tracerSettings, _writerMock.Object, _samplerMock.Object, scopeManager: null, statsd: null); + var tracer = new Tracer(tracerSettings, plugins: null, _writerMock.Object, _samplerMock.Object, scopeManager: null, statsd: null); var span = tracer.StartSpan("TestTracerDisabled"); span.Dispose(); diff --git a/test/Datadog.Trace.Tests/TracerTests.cs b/test/Datadog.Trace.Tests/TracerTests.cs index 63daa6ecfc..0a889ee815 100644 --- a/test/Datadog.Trace.Tests/TracerTests.cs +++ b/test/Datadog.Trace.Tests/TracerTests.cs @@ -44,7 +44,7 @@ public TracerTests() var writerMock = new Mock(); var samplerMock = new Mock(); - _tracer = new Tracer(settings, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); + _tracer = new Tracer(settings, plugins: null, writerMock.Object, samplerMock.Object, scopeManager: null, statsd: null); } [Fact] diff --git a/test/benchmarks/Benchmarks.Trace/AspNetCoreBenchmark.cs b/test/benchmarks/Benchmarks.Trace/AspNetCoreBenchmark.cs index 56dc43ae08..b08b432e18 100644 --- a/test/benchmarks/Benchmarks.Trace/AspNetCoreBenchmark.cs +++ b/test/benchmarks/Benchmarks.Trace/AspNetCoreBenchmark.cs @@ -31,7 +31,7 @@ static AspNetCoreBenchmark() StartupDiagnosticLogEnabled = false, }; - Tracer.Instance = new Tracer(settings, new DummyAgentWriter(), null, null, null); + Tracer.Instance = new Tracer(settings, null, new DummyAgentWriter(), null, null, null); var builder = new WebHostBuilder() .UseStartup(); diff --git a/test/benchmarks/Benchmarks.Trace/DbCommandBenchmark.cs b/test/benchmarks/Benchmarks.Trace/DbCommandBenchmark.cs index 6929c94f80..b68eae9b86 100644 --- a/test/benchmarks/Benchmarks.Trace/DbCommandBenchmark.cs +++ b/test/benchmarks/Benchmarks.Trace/DbCommandBenchmark.cs @@ -25,7 +25,7 @@ static DbCommandBenchmark() StartupDiagnosticLogEnabled = false }; - Tracer.Instance = new Tracer(settings, new DummyAgentWriter(), null, null, null); + Tracer.Instance = new Tracer(settings, null, new DummyAgentWriter(), null, null, null); var methodInfo = typeof(IDbCommand).GetMethod("ExecuteNonQuery", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); diff --git a/test/benchmarks/Benchmarks.Trace/ElasticsearchBenchmark.cs b/test/benchmarks/Benchmarks.Trace/ElasticsearchBenchmark.cs index 29388fb4c1..9de7dd41c0 100644 --- a/test/benchmarks/Benchmarks.Trace/ElasticsearchBenchmark.cs +++ b/test/benchmarks/Benchmarks.Trace/ElasticsearchBenchmark.cs @@ -33,7 +33,7 @@ static ElasticsearchBenchmark() StartupDiagnosticLogEnabled = false }; - Tracer.Instance = new Tracer(settings, new DummyAgentWriter(), null, null, null); + Tracer.Instance = new Tracer(settings, null, new DummyAgentWriter(), null, null, null); var methodInfo = typeof(RequestPipeline).GetMethod("CallElasticsearchAsync", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); diff --git a/test/benchmarks/Benchmarks.Trace/GraphQLBenchmark.cs b/test/benchmarks/Benchmarks.Trace/GraphQLBenchmark.cs index 48b92398e5..db9f6ca46c 100644 --- a/test/benchmarks/Benchmarks.Trace/GraphQLBenchmark.cs +++ b/test/benchmarks/Benchmarks.Trace/GraphQLBenchmark.cs @@ -28,7 +28,7 @@ static GraphQLBenchmark() StartupDiagnosticLogEnabled = false }; - Tracer.Instance = new Tracer(settings, new DummyAgentWriter(), null, null, null); + Tracer.Instance = new Tracer(settings, null, new DummyAgentWriter(), null, null, null); var methodInfo = typeof(GraphQLClient).GetMethod("ExecuteAsync", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); diff --git a/test/benchmarks/Benchmarks.Trace/HttpClientBenchmark.cs b/test/benchmarks/Benchmarks.Trace/HttpClientBenchmark.cs index 89248638b4..7a381bf185 100644 --- a/test/benchmarks/Benchmarks.Trace/HttpClientBenchmark.cs +++ b/test/benchmarks/Benchmarks.Trace/HttpClientBenchmark.cs @@ -31,7 +31,7 @@ static HttpClientBenchmark() StartupDiagnosticLogEnabled = false }; - Tracer.Instance = new Tracer(settings, new DummyAgentWriter(), null, null, null); + Tracer.Instance = new Tracer(settings, null, new DummyAgentWriter(), null, null, null); var methodInfo = typeof(HttpMessageHandler).GetMethod("SendAsync", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); diff --git a/test/benchmarks/Benchmarks.Trace/RedisBenchmark.cs b/test/benchmarks/Benchmarks.Trace/RedisBenchmark.cs index aec01299d1..3911c5914f 100644 --- a/test/benchmarks/Benchmarks.Trace/RedisBenchmark.cs +++ b/test/benchmarks/Benchmarks.Trace/RedisBenchmark.cs @@ -30,7 +30,7 @@ static RedisBenchmark() StartupDiagnosticLogEnabled = false }; - Tracer.Instance = new Tracer(settings, new DummyAgentWriter(), null, null, null); + Tracer.Instance = new Tracer(settings, null, new DummyAgentWriter(), null, null, null); var methodInfo = typeof(RedisNativeClient).GetMethod("SendReceive", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); diff --git a/test/benchmarks/Benchmarks.Trace/SerilogBenchmark.cs b/test/benchmarks/Benchmarks.Trace/SerilogBenchmark.cs index 1669eaf5d4..5b3aca7cdd 100644 --- a/test/benchmarks/Benchmarks.Trace/SerilogBenchmark.cs +++ b/test/benchmarks/Benchmarks.Trace/SerilogBenchmark.cs @@ -26,7 +26,7 @@ static SerilogBenchmark() ServiceVersion = "version" }; - LogInjectionTracer = new Tracer(logInjectionSettings, new DummyAgentWriter(), null, null, null); + LogInjectionTracer = new Tracer(logInjectionSettings, null, new DummyAgentWriter(), null, null, null); Tracer.Instance = LogInjectionTracer; var baselineSettings = new TracerSettings @@ -37,7 +37,7 @@ static SerilogBenchmark() ServiceVersion = "version" }; - BaselineTracer = new Tracer(baselineSettings, new DummyAgentWriter(), null, null, null); + BaselineTracer = new Tracer(baselineSettings, null, new DummyAgentWriter(), null, null, null); EnrichedLogger = new LoggerConfiguration() // Add Enrich.FromLogContext to emit Datadog properties diff --git a/test/benchmarks/Benchmarks.Trace/SpanBenchmark.cs b/test/benchmarks/Benchmarks.Trace/SpanBenchmark.cs index 1924e0c727..356c1380dc 100644 --- a/test/benchmarks/Benchmarks.Trace/SpanBenchmark.cs +++ b/test/benchmarks/Benchmarks.Trace/SpanBenchmark.cs @@ -23,7 +23,7 @@ static SpanBenchmark() StartupDiagnosticLogEnabled = false }; - Tracer = new Tracer(settings, new DummyAgentWriter(), null, null, null); + Tracer = new Tracer(settings, null, new DummyAgentWriter(), null, null, null); } /// diff --git a/test/test-applications/integrations/Samples.Vendoring/Propagation/VendorPropagatorsProvider.cs b/test/test-applications/integrations/Samples.Vendoring/Propagation/VendorPropagatorsProvider.cs new file mode 100644 index 0000000000..060a0fcb1a --- /dev/null +++ b/test/test-applications/integrations/Samples.Vendoring/Propagation/VendorPropagatorsProvider.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using Datadog.Trace.Conventions; +using Datadog.Trace.Propagation; +using Samples.Vendoring.Types; + +namespace Samples.Vendoring.Propagation +{ + public class VendorPropagatorsProvider : IPropagatorsProvider + { + private static readonly IReadOnlyDictionary> PropagatorSelector = + new Dictionary>(StringComparer.InvariantCultureIgnoreCase) + { + { VendorPropagatorTypes.VendorPropagator, convention => new VendorSpanContextPropagator(convention) } + }; + + public bool CanProvide(string propagatorId, ITraceIdConvention traceIdConvention) + { + return PropagatorSelector.ContainsKey(propagatorId); + } + + public IPropagator GetPropagator(string propagatorId, ITraceIdConvention traceIdConvention) + { + if (PropagatorSelector.TryGetValue(propagatorId, out Func getter)) + { + return getter(traceIdConvention); + } + + throw new InvalidOperationException($"There is no propagator registered for type '{propagatorId}'."); + } + } +} diff --git a/test/test-applications/integrations/Samples.Vendoring/Propagation/VendorSpanContextPropagator.cs b/test/test-applications/integrations/Samples.Vendoring/Propagation/VendorSpanContextPropagator.cs new file mode 100644 index 0000000000..fd0492a7dc --- /dev/null +++ b/test/test-applications/integrations/Samples.Vendoring/Propagation/VendorSpanContextPropagator.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Datadog.Trace; +using Datadog.Trace.Conventions; +using Datadog.Trace.Propagation; + +namespace Samples.Vendoring.Propagation +{ + internal class VendorSpanContextPropagator : IPropagator + { + private const string TraceIdHeader = "vendor-trace-id"; + private const string SpanIdHeader = "vendor-span-id"; + + private readonly ITraceIdConvention _traceIdConvention; + + public VendorSpanContextPropagator(ITraceIdConvention traceIdConvention) + { + _traceIdConvention = traceIdConvention; + } + + public SpanContext Extract(T carrier, Func> getter) + { + var traceId = ParseTraceId(carrier, getter); + + if (traceId == TraceId.Zero) + { + // a valid traceId is required to use distributed tracing + return null; + } + + var spanId = ParseSpanId(carrier, getter); + + return new SpanContext(traceId, spanId); + } + + public void Inject(SpanContext context, T carrier, Action setter) + { + setter(carrier, TraceIdHeader, context.TraceId.ToString()); + setter(carrier, SpanIdHeader, context.SpanId.ToString()); + } + + private TraceId ParseTraceId(T carrier, Func> getter) + { + var headerValue = getter(carrier, TraceIdHeader).FirstOrDefault(); + if (headerValue == null) + { + return TraceId.Zero; + } + + return _traceIdConvention.CreateFromString(headerValue); + } + + private ulong ParseSpanId(T carrier, Func> getter) + { + var headerValue = getter(carrier, SpanIdHeader).FirstOrDefault(); + if (headerValue == null) + { + return default; + } + + ulong.TryParse(headerValue, out ulong spanId); + return spanId; + } + } +} diff --git a/test/test-applications/integrations/Samples.Vendoring/Samples.Vendoring.csproj b/test/test-applications/integrations/Samples.Vendoring/Samples.Vendoring.csproj new file mode 100644 index 0000000000..bf4bedaaa7 --- /dev/null +++ b/test/test-applications/integrations/Samples.Vendoring/Samples.Vendoring.csproj @@ -0,0 +1,12 @@ + + + + true + Library + + + + + + + diff --git a/test/test-applications/integrations/Samples.Vendoring/Types/VendorPropagatorTypes.cs b/test/test-applications/integrations/Samples.Vendoring/Types/VendorPropagatorTypes.cs new file mode 100644 index 0000000000..004e674fb4 --- /dev/null +++ b/test/test-applications/integrations/Samples.Vendoring/Types/VendorPropagatorTypes.cs @@ -0,0 +1,7 @@ +namespace Samples.Vendoring.Types +{ + internal static class VendorPropagatorTypes + { + public const string VendorPropagator = "Vendor"; + } +} diff --git a/test/test-applications/integrations/Samples.Vendoring/plugins.json b/test/test-applications/integrations/Samples.Vendoring/plugins.json new file mode 100644 index 0000000000..94fdd379af --- /dev/null +++ b/test/test-applications/integrations/Samples.Vendoring/plugins.json @@ -0,0 +1,12 @@ +/* + Example of plugin configuration file +*/ + +{ + "net45": [ + "plugins/Samples.Vendoring.dll" + ], + "netcoreapp3.1": [ + "plugins/Samples.Vendoring.dll" + ] +}