Skip to content

Commit

Permalink
Add DD_TRACE_GLOBAL_TAGS Configuration Option (DataDog#533)
Browse files Browse the repository at this point in the history
Change includes revision to `IConfigurationSource` to support an `IDictionary<string, string>` configuration value.
  • Loading branch information
bobuva authored Oct 26, 2019
1 parent 4a3d2eb commit cbba883
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;

Expand Down Expand Up @@ -98,5 +99,12 @@ IEnumerator IEnumerable.GetEnumerator()
{
return _sources.GetEnumerator();
}

/// <inheritdoc />
public IDictionary<string, string> GetDictionary(string key)
{
return _sources.Select(source => source.GetDictionary(key))
.FirstOrDefault(value => value != null);
}
}
}
5 changes: 5 additions & 0 deletions src/Datadog.Trace/Configuration/ConfigurationKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ public static class ConfigurationKeys
/// <seealso cref="TracerSettings.AnalyticsEnabled"/>
public const string GlobalAnalyticsEnabled = "DD_TRACE_ANALYTICS_ENABLED";

/// <summary>
/// Configuration key for a list of tags to be applied globally to spans.
/// </summary>
public const string GlobalTags = "DD_TRACE_GLOBAL_TAGS";

/// <summary>
/// Configuration key for enabling or disabling the automatic injection
/// of correlation identifiers into the logging context.
Expand Down
12 changes: 11 additions & 1 deletion src/Datadog.Trace/Configuration/IConfigurationSource.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Collections.Generic;

namespace Datadog.Trace.Configuration
{
/// <summary>
Expand Down Expand Up @@ -36,5 +38,13 @@ public interface IConfigurationSource
/// <param name="key">The key that identifies the setting.</param>
/// <returns>The value of the setting, or <c>null</c> if not found.</returns>
bool? GetBool(string key);
}

/// <summary>
/// Gets the <see cref="IDictionary{TKey, TValue}"/> value of
/// the setting with the specified key.
/// </summary>
/// <param name="key">The key that identifies the setting.</param>
/// <returns>The value of the setting, or <c>null</c> if not found.</returns>
IDictionary<string, string> GetDictionary(string key);
}
}
37 changes: 37 additions & 0 deletions src/Datadog.Trace/Configuration/JsonConfigurationSource.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
Expand Down Expand Up @@ -97,5 +99,40 @@ public T GetValue<T>(string key)
? default
: token.Value<T>();
}

/// <summary>
/// Gets a <see cref="ConcurrentDictionary{TKey, TValue}"/> containing all of the values.
/// </summary>
/// <remarks>
/// Example JSON where `globalTags` is the configuration key.
/// {
/// "globalTags": {
/// "name1": "value1",
/// "name2": "value2"
/// }
/// }
/// </remarks>
/// <param name="key">The key that identifies the setting.</param>
/// <returns><see cref="IDictionary{TKey, TValue}"/> containing all of the key-value pairs.</returns>
/// <exception cref="JsonReaderException">Thrown if the configuration value is not a valid JSON string.</exception>"
public IDictionary<string, string> GetDictionary(string key)
{
var token = _configuration.SelectToken(key, errorWhenNoMatch: false);
if (token == null)
{
return null;
}

// Presence of a curly brace indicates that this is likely a JSON string. An exception will be thrown
// if it fails to parse or convert to a dictionary.
if (token.Type == JTokenType.Object)
{
var dictionary = token
?.ToObject<ConcurrentDictionary<string, string>>() as ConcurrentDictionary<string, string>;
return dictionary;
}

return StringConfigurationSource.ParseCustomKeyValues(token.ToString());
}
}
}
54 changes: 54 additions & 0 deletions src/Datadog.Trace/Configuration/StringConfigurationSource.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace Datadog.Trace.Configuration
{
/// <summary>
Expand All @@ -6,6 +9,47 @@ namespace Datadog.Trace.Configuration
/// </summary>
public abstract class StringConfigurationSource : IConfigurationSource
{
/// <summary>
/// Returns a <see cref="IDictionary{TKey, TValue}"/> from parsing
/// <paramref name="data"/>.
/// </summary>
/// <param name="data">A string containing key-value pairs which are comma-separated, and for which the key and value are colon-separated.</param>
/// <returns><see cref="IDictionary{TKey, TValue}"/> of key value pairs.</returns>
public static IDictionary<string, string> ParseCustomKeyValues(string data)
{
var dictionary = new ConcurrentDictionary<string, string>();

// A null return value means the key was not present,
// and CompositeConfigurationSource depends on this behavior
// (it returns the first non-null value it finds).
if (data == null)
{
return null;
}

if (string.IsNullOrWhiteSpace(data))
{
return dictionary;
}

var entries = data.Split(',');

foreach (var e in entries)
{
var kv = e.Split(':');
if (kv.Length != 2)
{
continue;
}

var key = kv[0];
var value = kv[1];
dictionary[key] = value;
}

return dictionary;
}

/// <inheritdoc />
public abstract string GetString(string key);

Expand Down Expand Up @@ -38,5 +82,15 @@ public abstract class StringConfigurationSource : IConfigurationSource
? result
: (bool?)null;
}

/// <summary>
/// Gets a <see cref="ConcurrentDictionary{TKey, TValue}"/> from parsing
/// </summary>
/// <param name="key">The key</param>
/// <returns><see cref="ConcurrentDictionary{TKey, TValue}"/> containing all of the key-value pairs.</returns>
public IDictionary<string, string> GetDictionary(string key)
{
return ParseCustomKeyValues(GetString(key));
}
}
}
10 changes: 10 additions & 0 deletions src/Datadog.Trace/Configuration/TracerSettings.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;

namespace Datadog.Trace.Configuration
{
Expand Down Expand Up @@ -76,6 +78,9 @@ public TracerSettings(IConfigurationSource source)
false;

Integrations = new IntegrationSettingsCollection(source);

GlobalTags = source?.GetDictionary(ConfigurationKeys.GlobalTags) ??
new ConcurrentDictionary<string, string>();
}

/// <summary>
Expand Down Expand Up @@ -141,6 +146,11 @@ public TracerSettings(IConfigurationSource source)
/// </summary>
public IntegrationSettingsCollection Integrations { get; }

/// <summary>
/// Gets or sets the global tags, which are applied to all <see cref="Span"/>s.
/// </summary>
public IDictionary<string, string> GlobalTags { get; set; }

/// <summary>
/// Create a <see cref="TracerSettings"/> populated from the default sources
/// returned by <see cref="CreateDefaultConfigurationSource"/>.
Expand Down
3 changes: 3 additions & 0 deletions src/Datadog.Trace/IDatadogTracer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Datadog.Trace.Configuration;
using Datadog.Trace.Sampling;

namespace Datadog.Trace
Expand All @@ -14,6 +15,8 @@ internal interface IDatadogTracer

ISampler Sampler { get; }

TracerSettings Settings { get; }

Span StartSpan(string operationName, ISpanContext parent, string serviceName, DateTimeOffset? startTime, bool ignoreActiveScope);

void Write(List<Span> span);
Expand Down
9 changes: 9 additions & 0 deletions src/Datadog.Trace/Tracer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,15 @@ public Span StartSpan(string operationName, ISpanContext parent = null, string s
span.SetTag(Tags.Env, env);
}

// Apply any global tags
if (Settings.GlobalTags.Count > 0)
{
foreach (var entry in Settings.GlobalTags)
{
span.SetTag(entry.Key, entry.Value);
}
}

traceContext.AddSpan(span);
return span;
}
Expand Down
48 changes: 41 additions & 7 deletions test/Datadog.Trace.Tests/Configuration/ConfigurationSourceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public static IEnumerable<object[]> GetDefaultTestData()
yield return new object[] { CreateFunc(s => s.ServiceName), null };
yield return new object[] { CreateFunc(s => s.DisabledIntegrationNames.Count), 0 };
yield return new object[] { CreateFunc(s => s.LogsInjectionEnabled), false };
yield return new object[] { CreateFunc(s => s.GlobalTags.Count), 0 };
}

public static IEnumerable<object[]> GetTestData()
Expand All @@ -36,6 +37,26 @@ public static IEnumerable<object[]> GetTestData()
yield return new object[] { ConfigurationKeys.ServiceName, "web-service", CreateFunc(s => s.ServiceName), "web-service" };

yield return new object[] { ConfigurationKeys.DisabledIntegrations, "integration1;integration2", CreateFunc(s => s.DisabledIntegrationNames.Count), 2 };

yield return new object[] { ConfigurationKeys.GlobalTags, "k1:v1, k2:v2", CreateFunc(s => s.GlobalTags.Count), 2 };
}

// JsonConfigurationSource needs to be tested with JSON data, which cannot be used with the other IConfigurationSource implementations.
public static IEnumerable<object[]> GetJsonTestData()
{
yield return new object[] { @"{ ""DD_TRACE_GLOBAL_TAGS"": { ""name1"":""value1"", ""name2"": ""value2""} }", CreateFunc(s => s.GlobalTags.Count), 2 };
}

public static IEnumerable<object[]> GetBadJsonTestData1()
{
// Extra opening brace
yield return new object[] { @"{ ""DD_TRACE_GLOBAL_TAGS"": { { ""name1"":""value1"", ""name2"": ""value2""} }" };
}

public static IEnumerable<object[]> GetBadJsonTestData2()
{
// Missing closing brace
yield return new object[] { @"{ ""DD_TRACE_GLOBAL_TAGS"": { ""name1"":""value1"", ""name2"": ""value2"" }" };
}

public static Func<TracerSettings, object> CreateFunc(Func<TracerSettings, object> settingGetter)
Expand Down Expand Up @@ -89,20 +110,33 @@ public void EnvironmentConfigurationSource(
}

[Theory]
[MemberData(nameof(GetTestData))]
public void NameValueConfigurationSource1(
string key,
[MemberData(nameof(GetJsonTestData))]
public void JsonConfigurationSource(
string value,
Func<TracerSettings, object> settingGetter,
object expectedValue)
{
var config = new Dictionary<string, string> { [key] = value };
string json = JsonConvert.SerializeObject(config);
IConfigurationSource source = new JsonConfigurationSource(json);
IConfigurationSource source = new JsonConfigurationSource(value);
var settings = new TracerSettings(source);

object actualValue = settingGetter(settings);
var actualValue = settingGetter(settings);
Assert.Equal(expectedValue, actualValue);
}

[Theory]
[MemberData(nameof(GetBadJsonTestData1))]
public void JsonConfigurationSource_BadData1(
string value)
{
Assert.Throws<JsonReaderException>(() => { new JsonConfigurationSource(value); });
}

[Theory]
[MemberData(nameof(GetBadJsonTestData2))]
public void JsonConfigurationSource_BadData2(
string value)
{
Assert.Throws<JsonSerializationException>(() => { new JsonConfigurationSource(value); });
}
}
}
4 changes: 4 additions & 0 deletions test/Datadog.Trace.Tests/Datadog.Trace.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="log4net" Version="2.0.8" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
Expand Down
1 change: 1 addition & 0 deletions test/Datadog.Trace.Tests/TracerTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Datadog.Trace.Agent;
Expand Down

0 comments on commit cbba883

Please sign in to comment.