Skip to content

Commit

Permalink
Add ability to disable ActivitySources
Browse files Browse the repository at this point in the history
This allows for users to pass in a comma separated
list of Activity Source names (glob-pattern supported)
that should not be listened to by the Datadog Tracer.
  • Loading branch information
bouwkast committed Aug 16, 2024
1 parent 338ab55 commit eda5838
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 8 deletions.
32 changes: 26 additions & 6 deletions tracer/src/Datadog.Trace/Activity/ActivityListenerHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,34 @@ public static bool OnShouldListenTo<T>(T source)
{
if (source is IActivitySource activitySource)
{
if (handler.ShouldListenTo(sName, activitySource.Version) && HandlerBySource.TryAdd(sName, handler))
if (handler.ShouldListenTo(sName, activitySource.Version))
{
Log.Debug("ActivityListenerHandler: {SourceName} will be handled by {Handler}.", sName, handler);
return true;
if (handler is DisableActivityHandler)
{
Log.Information("ActivityListenerHandler: {SourceName} will not be listened to by the .NET Tracer.", sName);
return false;
}

if (HandlerBySource.TryAdd(sName, handler))
{
Log.Debug("ActivityListenerHandler: {SourceName} will be handled by {Handler}.", sName, handler);
return true;
}
}
}
else if (handler.ShouldListenTo(sName, null) && HandlerBySource.TryAdd(sName, handler))
else if (handler.ShouldListenTo(sName, null))
{
Log.Debug("ActivityListenerHandler: {SourceName} will be handled by {Handler}.", sName, handler);
return true;
if (handler is DisableActivityHandler)
{
Log.Information("ActivityListenerHandler: {SourceName} will not be listened to by the .NET Tracer.", sName);
return false;
}

if (HandlerBySource.TryAdd(sName, handler))
{
Log.Debug("ActivityListenerHandler: {SourceName} will be handled by {Handler}.", sName, handler);
return true;
}
}
}

Expand All @@ -71,6 +89,7 @@ public static void OnActivityWithSourceStarted<T>(string sourceName, T activity)
var sName = sourceName ?? "(null)";
if (HandlerBySource.TryGetValue(sName, out var handler))
{
Log.Debug("ActivityListenerHandler: ActivityStarted event handled by. [Source={SourceName}]", sName);
handler.ActivityStarted(sName, activity);
}
else
Expand All @@ -86,6 +105,7 @@ public static void OnActivityWithSourceStopped<T>(string sourceName, T activity)
if (HandlerBySource.TryGetValue(sName, out var handler))
{
handler.ActivityStopped(sName, activity);
Log.Debug("ActivityListenerHandler: Stopped event handled by. [Source={SourceName}]", sName);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ namespace Datadog.Trace.Activity.Handlers
{
internal static class ActivityHandlersRegister
{
// Disable Activity Handler does not listen to the activity source at all.
internal static readonly DisableActivityHandler DisableHandler = new();

// Ignore Activity Handler catches existing integrations that also emits activities.
internal static readonly IgnoreActivityHandler IgnoreHandler = new();

// Activity handlers in order, the first handler where ShouldListenTo returns true will always handle that source.
internal static readonly IActivityHandler[] Handlers =
{
DisableHandler,

IgnoreHandler,

// Azure Service Bus handlers
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// <copyright file="DisableActivityHandler.cs" company="Datadog">
// 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.
// </copyright>

#nullable enable

using System.Collections.Generic;
using System.Text.RegularExpressions;
using Datadog.Trace.Activity.DuckTypes;
using Datadog.Trace.Sampling;

namespace Datadog.Trace.Activity.Handlers
{
internal class DisableActivityHandler : IActivityHandler
{
private List<Regex>? _disabledSourceNameGlobs = null;
private bool _disableAll = false;

public void ActivityStarted<T>(string sourceName, T activity)
where T : IActivity
{
// do nothing; this should not be called
}

public void ActivityStopped<T>(string sourceName, T activity)
where T : IActivity
{
// do nothing; this should not be called
}

/// <summary>
/// Determines whether <see cref="DisableActivityHandler"/> will "listen" to <paramref name="sourceName"/>.
/// <para>
/// Note that "listen" in this case means that the created ActivityListener will not subscribe to the ActivitySource.
/// </para>
/// </summary>
/// <returns><see langword="true"/> when the Tracer will disable the ActivitySource; otherwise <see langword="false"/></returns>
public bool ShouldListenTo(string sourceName, string? version)
{
if (_disableAll)
{
return true; // "*" was specified as a pattern, short circuit to disable all
}

_disabledSourceNameGlobs ??= PopulateGlobs();
if (_disabledSourceNameGlobs.Count == 0)
{
return false; // no glob patterns specified, sourceName will not be disabled
}

foreach (var regex in _disabledSourceNameGlobs)
{
if (regex.IsMatch(sourceName))
{
return true; // disable ActivitySource of "sourceName" from being listened to by the tracer
}
}

var toDisable = Tracer.Instance.Settings.DisabledActivitySources;
if (toDisable is null || toDisable.Length == 0)
{
return false;
}

// sources were specified to be disabled, but this sourceName didn't match any of them
return false; // sourceName will _not_ be disabled
}

private List<Regex> PopulateGlobs()
{
var globs = new List<Regex>();
var toDisable = Tracer.Instance.Settings.DisabledActivitySources;
if (toDisable is null || toDisable.Length == 0)
{
return globs;
}

foreach (var disabledSourceNameGlob in toDisable)
{
// HACK: using RegexBuilder here even though it isn't _really_ for this
var globRegex = RegexBuilder.Build(disabledSourceNameGlob, SamplingRulesFormat.Glob, RegexBuilder.DefaultTimeout);
// handle special case where a "*" pattern will be null
if (globRegex is null)
{
_disableAll = true;
return [];
}

globs.Add(globRegex);
}

return globs;
}
}
}
7 changes: 7 additions & 0 deletions tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ internal static partial class ConfigurationKeys
/// <seealso cref="TracerSettings.DisabledIntegrationNames"/>
public const string DisabledIntegrations = "DD_DISABLED_INTEGRATIONS";

/// <summary>
/// Configuration key for a list of ActivitySource names (supports globbing) that will be disabled.
/// Default is empty (all ActivitySources will be subscribed to by default).
/// Supports multiple values separated with commas.
/// </summary>
public const string DisabledActivitySources = "DD_DISABLED_ACTIVITY_SOURCES";

/// <summary>
/// Configuration key for enabling or disabling default Analytics.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ internal ImmutableTracerSettings(TracerSettings settings, bool unusedParamNotToU
SpanSamplingRules = settings.SpanSamplingRules;
_globalSamplingRate = settings.GlobalSamplingRateInternal;
IntegrationsInternal = new ImmutableIntegrationSettingsCollection(settings.IntegrationsInternal, settings.DisabledIntegrationNamesInternal);
DisabledActivitySources = settings.DisabledActivitySources;
_headerTags = new ReadOnlyDictionary<string, string>(settings.HeaderTagsInternal);
GrpcTagsInternal = new ReadOnlyDictionary<string, string>(settings.GrpcTagsInternal);
IpHeader = settings.IpHeader;
Expand Down Expand Up @@ -338,6 +339,12 @@ internal ImmutableTracerSettings(TracerSettings settings, bool unusedParamNotToU
[GeneratePublicApi(PublicApiUsage.ImmutableTracerSettings_Integrations_Get)]
internal ImmutableIntegrationSettingsCollection IntegrationsInternal { get; }

/// <summary>
/// Gets the comma separated string of disabled ActivitySources that will not be handled at all.
/// </summary>
/// <seealso cref="ConfigurationKeys.DisabledActivitySources"/>
internal string[] DisabledActivitySources { get; }

/// <summary>
/// Gets the global tags, which are applied to all <see cref="Span"/>s.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions tracer/src/Datadog.Trace/Configuration/TracerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ _ when x.ToBoolean() is { } boolean => boolean,

DisabledIntegrationNamesInternal = new HashSet<string>(disabledIntegrationNames, StringComparer.OrdinalIgnoreCase);

var disabledActivitySources = config.WithKeys(ConfigurationKeys.DisabledActivitySources).AsString();

DisabledActivitySources = !string.IsNullOrEmpty(disabledActivitySources) ? TrimSplitString(disabledActivitySources, commaSeparator) : [];

IntegrationsInternal = new IntegrationSettingsCollection(source, unusedParamNotToUsePublicApi: false);

ExporterInternal = new ExporterSettings(source, _telemetry);
Expand Down Expand Up @@ -596,6 +600,12 @@ _ when x.ToBoolean() is { } boolean => boolean,
[ConfigKey(ConfigurationKeys.DisabledIntegrations)]
internal HashSet<string> DisabledIntegrationNamesInternal { get; set; }

/// <summary>
/// Gets the names of disabled ActivitySources.
/// </summary>
/// <seealso cref="ConfigurationKeys.DisabledActivitySources"/>
internal string[] DisabledActivitySources { get; }

/// <summary>
/// Gets or sets the transport settings that dictate how the tracer connects to the agent.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public NetActivitySdkTests(ITestOutputHelper output)
public async Task SubmitsTraces()
{
SetEnvironmentVariable("DD_TRACE_OTEL_ENABLED", "true");
SetEnvironmentVariable("DD_DISABLED_ACTIVITY_SOURCES", "Disabled.By.ExactMatch,*.By.Glob*");

using (var telemetry = this.ConfigureTelemetry())
using (var agent = EnvironmentHelper.GetMockAgent())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ namespace NetActivitySdk;
public static class Program
{
private static ActivitySource _source;
private static ActivitySource _ignored;
private static ActivitySource _disabledGlob;
private static ActivitySource _disabledExact;

private static string SpanLinkTraceId1;
private static string SpanLinkTraceId2;
Expand All @@ -19,12 +22,15 @@ public static async Task Main(string[] args)
{
Console.WriteLine($"SpanId 1: {SpanLinkSpanId1} SpanId 2: {SpanLinkSpanId2}");
_source = new ActivitySource("Samples.NetActivitySdk");
_ignored = new ActivitySource("Microsoft.AspNetCore"); // this is an ignored source
_disabledGlob = new ActivitySource("Disabled.By.GlobPattern");
_disabledExact = new ActivitySource("Disabled.By.ExactMatch");

var activityListener = new ActivityListener
{
//ActivityStarted = activity => Console.WriteLine($"{activity.DisplayName} - Started"),
ActivityStopped = activity => PrintSpanStoppedInformation(activity),
ShouldListenTo = _ => true,
ShouldListenTo = source => { return !source.Name.Contains("Disabled"); }, // return true for all except Disabled ones
Sample = (ref ActivityCreationOptions<ActivityContext> options) => ActivitySamplingResult.AllData
};

Expand All @@ -51,6 +57,8 @@ public static async Task Main(string[] args)

RunManuallyUpdatedStartTime(); // 3 spans (50 total)

RunIgnoredAndDisabledSources(); // 0 spans (50 total)

await Task.Delay(1000);
}

Expand Down Expand Up @@ -208,6 +216,22 @@ private static void RunManuallyUpdatedStartTime()
}
}

private static void RunIgnoredAndDisabledSources()
{
using var ignoredSpan = _ignored.StartActivity("Ignored - SHOULD NOT BE IN SNAPSHOT") ?? throw new InvalidOperationException("Ignored Activity was null - was it disabled?");
using var disabledSpanGlob = _disabledGlob.StartActivity("DisabledGlob - SHOULD BE NULL AND NOT IN SNAPSHOT");
if (disabledSpanGlob is not null)
{
throw new InvalidOperationException("DisabledGlob Activity wasn't null");
}

using var disabledSpanExact = _disabledExact.StartActivity("DisabledExact - SHOULD BE NULL AND NOT IN SNAPSHOT");
if (disabledSpanExact is not null)
{
throw new InvalidOperationException("DisabledExact Activity wasn't null");
}
}

private static void RunActivityUpdate()
{
using var errorSpan = _source.StartActivity("ErrorSpan");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@

"DD_DOTNET_TRACER_HOME": "$(SolutionDir)shared\\bin\\monitoring-home",
"DD_TRACE_OTEL_ENABLED": "true",
"DD_TRACE_DEBUG": "true"
"DD_TRACE_DEBUG": "true",
"DD_DISABLED_ACTIVITY_SOURCES": "Disabled.By.ExactMatch,*.By.Glob*"
},
"nativeDebugging": false
},
Expand Down

0 comments on commit eda5838

Please sign in to comment.