diff --git a/docs/trace/building-your-own-exporter/Program.cs b/docs/trace/building-your-own-exporter/Program.cs index d110bdc212e..ac182a02091 100644 --- a/docs/trace/building-your-own-exporter/Program.cs +++ b/docs/trace/building-your-own-exporter/Program.cs @@ -25,11 +25,7 @@ public class Program public static void Main() { using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddActivitySources( - new string[] - { - "MyCompany.MyProduct.MyLibrary", - }) + .AddActivitySource("MyCompany.MyProduct.MyLibrary") .AddMyExporter() .Build(); diff --git a/docs/trace/building-your-own-processor/Program.cs b/docs/trace/building-your-own-processor/Program.cs index a1db73735e5..a18aea5eabb 100644 --- a/docs/trace/building-your-own-processor/Program.cs +++ b/docs/trace/building-your-own-processor/Program.cs @@ -26,10 +26,7 @@ public class Program public static void Main() { using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddActivitySources(new string[] - { - "MyCompany.MyProduct.MyLibrary", - }) + .AddActivitySource("MyCompany.MyProduct.MyLibrary") .AddProcessor(new MyActivityProcessor("A")) .AddProcessor(new MyActivityProcessor("B")) .Build(); diff --git a/docs/trace/building-your-own-sampler/Program.cs b/docs/trace/building-your-own-sampler/Program.cs index 47bf0be3a2d..9dc5128ee22 100644 --- a/docs/trace/building-your-own-sampler/Program.cs +++ b/docs/trace/building-your-own-sampler/Program.cs @@ -27,11 +27,7 @@ public class Program public static void Main() { using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddActivitySources( - new string[] - { - "MyCompany.MyProduct.MyLibrary", - }) + .AddActivitySource("MyCompany.MyProduct.MyLibrary") .SetSampler(new MySampler()) .AddProcessor(new SimpleActivityProcessor(new ConsoleExporter(new ConsoleExporterOptions()))) .Build(); diff --git a/docs/trace/getting-started/Program.cs b/docs/trace/getting-started/Program.cs index cb034769442..3e46ca7bf93 100644 --- a/docs/trace/getting-started/Program.cs +++ b/docs/trace/getting-started/Program.cs @@ -27,11 +27,7 @@ public class Program public static void Main() { using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddActivitySources( - new string[] - { - "MyCompany.MyProduct.MyLibrary", - }) + .AddActivitySource("MyCompany.MyProduct.MyLibrary") .AddProcessor(new SimpleActivityProcessor(new ConsoleExporter(new ConsoleExporterOptions()))) .Build(); diff --git a/src/OpenTelemetry/Trace/TracerProviderBuilder.cs b/src/OpenTelemetry/Trace/TracerProviderBuilder.cs index 088fbfb947a..e2e5a2d11d9 100644 --- a/src/OpenTelemetry/Trace/TracerProviderBuilder.cs +++ b/src/OpenTelemetry/Trace/TracerProviderBuilder.cs @@ -15,7 +15,6 @@ // using System; using System.Collections.Generic; -using System.Diagnostics; using OpenTelemetry.Resources; using OpenTelemetry.Trace.Samplers; @@ -26,17 +25,19 @@ namespace OpenTelemetry.Trace /// public class TracerProviderBuilder { - internal TracerProviderBuilder() - { - } + private readonly List instrumentations = new List(); + + private readonly List processors = new List(); - internal Sampler Sampler { get; private set; } + private readonly List sources = new List(); - internal Resource Resource { get; private set; } = Resource.Empty; + private Sampler sampler = new ParentOrElseSampler(new AlwaysOnSampler()); - internal ActivityProcessor ActivityProcessor { get; private set; } + private Resource resource = Resource.Empty; - internal Dictionary ActivitySourceNames { get; private set; } + internal TracerProviderBuilder() + { + } internal List InstrumentationFactories { get; private set; } @@ -47,7 +48,7 @@ internal TracerProviderBuilder() /// Returns for chaining. public TracerProviderBuilder SetSampler(Sampler sampler) { - this.Sampler = sampler ?? throw new ArgumentNullException(nameof(sampler)); + this.sampler = sampler; return this; } @@ -58,45 +59,44 @@ public TracerProviderBuilder SetSampler(Sampler sampler) /// Returns for chaining. public TracerProviderBuilder SetResource(Resource resource) { - this.Resource = resource ?? Resource.Empty; + this.resource = resource ?? Resource.Empty; return this; } /// /// Adds given activitysource name to the list of subscribed sources. /// - /// Activity source name. + /// Activity source name. /// Returns for chaining. - public TracerProviderBuilder AddActivitySource(string activitySourceName) - { + public TracerProviderBuilder AddActivitySource(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException($"{nameof(name)} is null or whitespace."); + } + // TODO: We need to fix the listening model. // Today it ignores version. - if (this.ActivitySourceNames == null) - { - this.ActivitySourceNames = new Dictionary(StringComparer.OrdinalIgnoreCase); - } + this.sources.Add(name); - this.ActivitySourceNames[activitySourceName] = true; return this; } /// /// Adds given activitysource names to the list of subscribed sources. /// - /// Activity source names. + /// Activity source names. /// Returns for chaining. - public TracerProviderBuilder AddActivitySources(IEnumerable activitySourceNames) - { - // TODO: We need to fix the listening model. - // Today it ignores version. - if (this.ActivitySourceNames == null) - { - this.ActivitySourceNames = new Dictionary(StringComparer.OrdinalIgnoreCase); + public TracerProviderBuilder AddActivitySource(IEnumerable names) + { + if (names == null) + { + throw new ArgumentNullException(nameof(names)); } - foreach (var activitySourceName in activitySourceNames) + foreach (var name in names) { - this.ActivitySourceNames[activitySourceName] = true; + this.AddActivitySource(name); } return this; @@ -108,24 +108,14 @@ public TracerProviderBuilder AddActivitySources(IEnumerable activitySour /// Activity processor to add. /// Returns for chaining. public TracerProviderBuilder AddProcessor(ActivityProcessor activityProcessor) - { - if (this.ActivityProcessor == null) - { - this.ActivityProcessor = activityProcessor; - } - else if (this.ActivityProcessor is CompositeActivityProcessor compositeProcessor) - { - compositeProcessor.AddProcessor(activityProcessor); - } - else - { - this.ActivityProcessor = new CompositeActivityProcessor(new[] - { - this.ActivityProcessor, - activityProcessor, - }); + { + if (activityProcessor == null) + { + throw new ArgumentNullException(nameof(activityProcessor)); } + this.processors.Add(activityProcessor); + return this; } @@ -160,106 +150,26 @@ public TracerProviderBuilder AddInstrumentation( public TracerProvider Build() { - this.Sampler = this.Sampler ?? new ParentOrElseSampler(new AlwaysOnSampler()); - - var provider = new TracerProviderSdk - { - Resource = this.Resource, - Sampler = this.Sampler, - ActivityProcessor = this.ActivityProcessor, - }; - - var activitySource = new ActivitySourceAdapter(provider.Sampler, provider.ActivityProcessor, provider.Resource); + var provider = new TracerProviderSdk(this.sources, null, this.sampler, this.resource); + + foreach (var processor in this.processors) + { + provider.AddProcessor(processor); + } + + var adapter = new ActivitySourceAdapter(this.sampler, provider.ActivityProcessor, this.resource); if (this.InstrumentationFactories != null) { foreach (var instrumentation in this.InstrumentationFactories) { - provider.Instrumentations.Add(instrumentation.Factory(activitySource)); + this.instrumentations.Add(instrumentation.Factory(adapter)); } } - provider.ActivityListener = new ActivityListener - { - // Callback when Activity is started. - ActivityStarted = (activity) => - { - if (activity.IsAllDataRequested) - { - activity.SetResource(this.Resource); - } - - provider.ActivityProcessor?.OnStart(activity); - }, - - // Callback when Activity is stopped. - ActivityStopped = (activity) => - { - provider.ActivityProcessor?.OnEnd(activity); - }, - - // Function which takes ActivitySource and returns true/false to indicate if it should be subscribed to - // or not. - ShouldListenTo = (activitySource) => - { - if (this.ActivitySourceNames == null) - { - return false; - } - - return this.ActivitySourceNames.ContainsKey(activitySource.Name); - }, - - // Setting this to true means TraceId will be always - // available in sampling callbacks and will be the actual - // traceid used, if activity ends up getting created. - AutoGenerateRootContextTraceId = true, - - // This delegate informs ActivitySource about sampling decision when the parent context is an ActivityContext. - GetRequestedDataUsingContext = (ref ActivityCreationOptions options) => ComputeActivityDataRequest(options, this.Sampler), - }; - - ActivitySource.AddActivityListener(provider.ActivityListener); - return provider; } - internal static ActivityDataRequest ComputeActivityDataRequest( - in ActivityCreationOptions options, - Sampler sampler) - { - var isRootSpan = /*TODO: Put back once AutoGenerateRootContextTraceId is removed. - options.Parent.TraceId == default ||*/ options.Parent.SpanId == default; - - if (sampler != null) - { - // As we set ActivityListener.AutoGenerateRootContextTraceId = true, - // Parent.TraceId will always be the TraceId of the to-be-created Activity, - // if it get created. - ActivityTraceId traceId = options.Parent.TraceId; - - var samplingParameters = new SamplingParameters( - options.Parent, - traceId, - options.Name, - options.Kind, - options.Tags, - options.Links); - - var shouldSample = sampler.ShouldSample(samplingParameters); - if (shouldSample.IsSampled) - { - return ActivityDataRequest.AllDataAndRecorded; - } - } - - // If it is the root span select PropagationData so the trace ID is preserved - // even if no activity of the trace is recorded (sampled per OpenTelemetry parlance). - return isRootSpan - ? ActivityDataRequest.PropagationData - : ActivityDataRequest.None; - } - internal readonly struct InstrumentationFactory { public readonly string Name; diff --git a/src/OpenTelemetry/Trace/TracerProviderSdk.cs b/src/OpenTelemetry/Trace/TracerProviderSdk.cs index 10cd4207894..825393cf579 100644 --- a/src/OpenTelemetry/Trace/TracerProviderSdk.cs +++ b/src/OpenTelemetry/Trace/TracerProviderSdk.cs @@ -17,13 +17,15 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; +using System.Text.RegularExpressions; using OpenTelemetry.Resources; namespace OpenTelemetry.Trace { internal class TracerProviderSdk : TracerProvider { - public readonly List Instrumentations = new List(); + public readonly List Instrumentations; public Resource Resource; public ActivityProcessor ActivityProcessor; public ActivityListener ActivityListener; @@ -35,8 +37,104 @@ static TracerProviderSdk() Activity.ForceDefaultIdFormat = true; } - internal TracerProviderSdk() - { + internal TracerProviderSdk( + IEnumerable sources, + IEnumerable instrumentations = null, + Sampler sampler = null, + Resource resource = null) + { + if (sources == null) + { + throw new ArgumentNullException(nameof(sources)); + } + + if (!sources.Any()) + { + throw new ArgumentException($"{nameof(sources)} collection is empty."); + } + + var wildcardMode = false; + + foreach (var name in sources) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException($"{nameof(sources)} collection contains null or whitespace strings."); + } + + if (name.Contains('*')) + { + wildcardMode = true; + } + } + + if (instrumentations == null) + { + this.Instrumentations = new List(); + } + else + { + // TODO: check if individual element is null + this.Instrumentations = new List(instrumentations); + } + + this.Sampler = sampler; + + this.Resource = resource; + + var listener = new ActivityListener + { + // Callback when Activity is started. + ActivityStarted = (activity) => + { + if (activity.IsAllDataRequested) + { + activity.SetResource(this.Resource); + } + + this.ActivityProcessor?.OnStart(activity); + }, + + // Callback when Activity is stopped. + ActivityStopped = (activity) => + { + this.ActivityProcessor?.OnEnd(activity); + }, + + // Setting this to true means TraceId will be always + // available in sampling callbacks and will be the actual + // traceid used, if activity ends up getting created. + AutoGenerateRootContextTraceId = true, + + // This delegate informs ActivitySource about sampling decision when the parent context is an ActivityContext. + GetRequestedDataUsingContext = (ref ActivityCreationOptions options) => ComputeActivityDataRequest(options, this.Sampler), + }; + + if (wildcardMode) + { + var pattern = "^(" + string.Join("|", from name in sources select '(' + Regex.Escape(name).Replace("\\*", ".*") + ')') + ")$"; + var regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); + + // Function which takes ActivitySource and returns true/false to indicate if it should be subscribed to + // or not. + listener.ShouldListenTo = (activitySource) => regex.IsMatch(activitySource.Name); + } + else + { + var activitySources = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var name in sources) + { + activitySources[name] = true; + } + + // Function which takes ActivitySource and returns true/false to indicate if it should be subscribed to + // or not. + listener.ShouldListenTo = (activitySource) => activitySources.ContainsKey(activitySource.Name); + } + + ActivitySource.AddActivityListener(listener); + this.ActivityListener = listener; } protected override void Dispose(bool disposing) @@ -57,5 +155,41 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } + + private static ActivityDataRequest ComputeActivityDataRequest( + in ActivityCreationOptions options, + Sampler sampler) + { + var isRootSpan = /*TODO: Put back once AutoGenerateRootContextTraceId is removed. + options.Parent.TraceId == default ||*/ options.Parent.SpanId == default; + + if (sampler != null) + { + // As we set ActivityListener.AutoGenerateRootContextTraceId = true, + // Parent.TraceId will always be the TraceId of the to-be-created Activity, + // if it get created. + ActivityTraceId traceId = options.Parent.TraceId; + + var samplingParameters = new SamplingParameters( + options.Parent, + traceId, + options.Name, + options.Kind, + options.Tags, + options.Links); + + var shouldSample = sampler.ShouldSample(samplingParameters); + if (shouldSample.IsSampled) + { + return ActivityDataRequest.AllDataAndRecorded; + } + } + + // If it is the root span select PropagationData so the trace ID is preserved + // even if no activity of the trace is recorded (sampled per OpenTelemetry parlance). + return isRootSpan + ? ActivityDataRequest.PropagationData + : ActivityDataRequest.None; + } } }