Skip to content

Commit 33db3c2

Browse files
[Tracing] Adds support for mapping stable OpenTelemetry environment variables to their Datadog equivalents (#5661)
### High-level toggles - `OTEL_SDK_DISABLED="true"` sets `DD_TRACE_OTEL_ENABLED=false` - `OTEL_TRACES_EXPORTER="none"` sets `DD_TRACE_ENABLED=false` - `OTEL_METRICS_EXPORTER="none"` sets `DD_RUNTIME_METRICS_ENABLED=false` - `OTEL_LOG_LEVEL="debug"` sets `DD_TRACE_DEBUG=true` ### Service metadata - `OTEL_SERVICE_NAME` sets `DD_SERVICE` - `OTEL_RESOURCE_ATTRIBUTES` sets `DD_TAGS` - The set of key-value pairs in `OTEL_RESOURCE_ATTRIBUTES` are separated by a `=` (whereas the Datadog convention is to use a `:`), so a translation is done to map the values successfully - This also recognizes `"service.name"` for setting `DD_SERVICE`, `"service.version"` for setting `"DD_VERSION"`, and `"deployment.environment"` for setting `DD_ENV`. This is the mapping from OpenTelemetry semantic conventions to Datadog Unified Service Tagging. ### Additional features - `OTEL_TRACES_SAMPLER`/`OTEL_TRACES_SAMPLER_ARG` set `DD_TRACE_SAMPLE_RATE` - `OTEL_PROPAGATORS` sets `DD_TRACE_PROPAGATION_STYLE` Note: If the corresponding Datadog environment variable is set (even if an invalid value), the OpenTelemetry environment variable will be ignored and treated as if it wasn't set.
1 parent bbc509e commit 33db3c2

18 files changed

+831
-103
lines changed

tracer/src/Datadog.Trace/AgentProcessManager.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,17 @@ public static void Initialize()
7474
return;
7575
}
7676

77-
var automaticTraceEnabled = EnvironmentHelpers.GetEnvironmentVariable(ConfigurationKeys.TraceEnabled, string.Empty)?.ToBoolean() ?? true;
77+
bool automaticTraceEnabled = true;
78+
if (EnvironmentHelpers.GetEnvironmentVariable(ConfigurationKeys.TraceEnabled, string.Empty) is string stringValue)
79+
{
80+
automaticTraceEnabled = stringValue.ToBoolean() ?? true;
81+
}
82+
else if (EnvironmentHelpers.GetEnvironmentVariable(ConfigurationKeys.OpenTelemetry.TracesExporter, string.Empty) is string otelTraceExporter
83+
&& string.Equals(otelTraceExporter, "none", StringComparison.OrdinalIgnoreCase))
84+
{
85+
automaticTraceEnabled = false;
86+
}
87+
7888
var automaticProfilingEnabled = EnvironmentHelpers.GetEnvironmentVariable(ContinuousProfiler.ConfigurationKeys.ProfilingEnabled)?.ToBoolean() ?? false;
7989

8090
if (azureAppServiceSettings.CustomTracingEnabled || automaticTraceEnabled || automaticProfilingEnabled)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// <copyright file="ConfigurationKeys.OpenTelemetry.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
#nullable enable
6+
7+
namespace Datadog.Trace.Configuration
8+
{
9+
internal partial class ConfigurationKeys
10+
{
11+
internal class OpenTelemetry
12+
{
13+
/// <summary>
14+
/// Configuration key for disabling the OpenTelemetry API's.
15+
/// </summary>
16+
public const string SdkDisabled = "OTEL_SDK_DISABLED";
17+
18+
/// <summary>
19+
/// Configuration key for a list of key-value pairs to be set as
20+
/// resource attributes. We currently map these to span tags.
21+
/// </summary>
22+
public const string ResourceAttributes = "OTEL_RESOURCE_ATTRIBUTES";
23+
24+
/// <summary>
25+
/// Configuration key for a list of tracing propagators.
26+
/// Datadog only supports a subset of the OpenTelemetry propagators.
27+
/// Also, the 'b3' OpenTelemetry propagator is mapped to the
28+
/// 'b3 single header' Datadog propagator.
29+
/// </summary>
30+
public const string Propagators = "OTEL_PROPAGATORS";
31+
32+
/// <summary>
33+
/// Configuration key to set the application's default service name.
34+
/// </summary>
35+
public const string ServiceName = "OTEL_SERVICE_NAME";
36+
37+
/// <summary>
38+
/// Configuration key to set the log level.
39+
/// </summary>
40+
public const string LogLevel = "OTEL_LOG_LEVEL";
41+
42+
/// <summary>
43+
/// Configuration key to set the exporter for metrics.
44+
/// We only recognize the value 'none', which is the
45+
/// equivalent of setting <see cref="ConfigurationKeys.RuntimeMetricsEnabled"/>
46+
/// to false.
47+
/// </summary>
48+
public const string MetricsExporter = "OTEL_METRICS_EXPORTER";
49+
50+
/// <summary>
51+
/// Configuration key to set the exporter for traces.
52+
/// We only recognize the value 'none', which is the
53+
/// equivalent of setting <see cref="ConfigurationKeys.TraceEnabled"/>
54+
/// to false.
55+
/// </summary>
56+
public const string TracesExporter = "OTEL_TRACES_EXPORTER";
57+
58+
/// <summary>
59+
/// Configuration key to set the sampler for traces.
60+
/// to false.
61+
/// </summary>
62+
public const string TracesSampler = "OTEL_TRACES_SAMPLER";
63+
64+
/// <summary>
65+
/// Configuration key to set an additional argument for the
66+
/// traces sampler.
67+
/// to false.
68+
/// </summary>
69+
public const string TracesSamplerArg = "OTEL_TRACES_SAMPLER_ARG";
70+
}
71+
}
72+
}

tracer/src/Datadog.Trace/Configuration/ConfigurationSources/CompositeConfigurationSource.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,14 @@ IEnumerator IEnumerable.GetEnumerator()
167167
[PublicApi]
168168
public IDictionary<string, string>? GetDictionary(string key, bool allowOptionalMappings)
169169
{
170-
return _sources.Select(source => source.GetDictionary(key, NullConfigurationTelemetry.Instance, validator: null, allowOptionalMappings))
170+
return _sources.Select(source => source.GetDictionary(key, NullConfigurationTelemetry.Instance, validator: null, allowOptionalMappings, separator: ':'))
171171
.FirstOrDefault(value => value != null)?.Result;
172172
}
173173

174+
/// <inheritdoc />
175+
bool ITelemeteredConfigurationSource.IsPresent(string key)
176+
=> _sources.Select(source => source.IsPresent(key)).FirstOrDefault(value => value);
177+
174178
/// <inheritdoc />
175179
ConfigurationResult<string>? ITelemeteredConfigurationSource.GetString(string key, IConfigurationTelemetry telemetry, Func<string, bool>? validator, bool recordValue)
176180
=> _sources.Select(source => source.GetString(key, telemetry, validator, recordValue)).FirstOrDefault(value => value != null);
@@ -192,8 +196,8 @@ IEnumerator IEnumerable.GetEnumerator()
192196
=> _sources.Select(source => source.GetDictionary(key, telemetry, validator)).FirstOrDefault(value => value != null);
193197

194198
/// <inheritdoc />
195-
ConfigurationResult<IDictionary<string, string>>? ITelemeteredConfigurationSource.GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings)
196-
=> _sources.Select(source => source.GetDictionary(key, telemetry, validator, allowOptionalMappings)).FirstOrDefault(value => value != null);
199+
ConfigurationResult<IDictionary<string, string>>? ITelemeteredConfigurationSource.GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator)
200+
=> _sources.Select(source => source.GetDictionary(key, telemetry, validator, allowOptionalMappings, separator)).FirstOrDefault(value => value != null);
197201

198202
/// <inheritdoc />
199203
ConfigurationResult<T>? ITelemeteredConfigurationSource.GetAs<T>(string key, IConfigurationTelemetry telemetry, Func<string, ParsingResult<T>> converter, Func<T, bool>? validator, bool recordValue)

tracer/src/Datadog.Trace/Configuration/ConfigurationSources/JsonConfigurationSource.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,14 @@ internal static JsonConfigurationSource FromFile(string filename, ConfigurationO
213213
return StringConfigurationSource.ParseCustomKeyValuesInternal(token.ToString(), allowOptionalMappings);
214214
}
215215

216+
/// <inheritdoc />
217+
bool ITelemeteredConfigurationSource.IsPresent(string key)
218+
{
219+
JToken? token = SelectToken(key);
220+
221+
return token is not null;
222+
}
223+
216224
/// <inheritdoc />
217225
ConfigurationResult<string>? ITelemeteredConfigurationSource.GetString(string key, IConfigurationTelemetry telemetry, Func<string, bool>? validator, bool recordValue)
218226
{
@@ -366,11 +374,11 @@ internal static JsonConfigurationSource FromFile(string filename, ConfigurationO
366374

367375
/// <inheritdoc />
368376
ConfigurationResult<IDictionary<string, string>>? ITelemeteredConfigurationSource.GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator)
369-
=> GetDictionary(key, telemetry, validator, allowOptionalMappings: false);
377+
=> GetDictionary(key, telemetry, validator, allowOptionalMappings: false, separator: ':');
370378

371379
/// <inheritdoc />
372-
ConfigurationResult<IDictionary<string, string>>? ITelemeteredConfigurationSource.GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings)
373-
=> GetDictionary(key, telemetry, validator, allowOptionalMappings);
380+
ConfigurationResult<IDictionary<string, string>>? ITelemeteredConfigurationSource.GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator)
381+
=> GetDictionary(key, telemetry, validator, allowOptionalMappings, separator);
374382

375383
private protected virtual JToken? SelectToken(string key) => _configuration?.SelectToken(key, errorWhenNoMatch: false);
376384

@@ -379,7 +387,7 @@ internal static JsonConfigurationSource FromFile(string filename, ConfigurationO
379387
return token.ToObject<ConcurrentDictionary<string, string>>();
380388
}
381389

382-
private ConfigurationResult<IDictionary<string, string>>? GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings)
390+
private ConfigurationResult<IDictionary<string, string>>? GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator)
383391
{
384392
var token = SelectToken(key);
385393
if (token == null)
@@ -416,7 +424,7 @@ internal static JsonConfigurationSource FromFile(string filename, ConfigurationO
416424
}
417425
}
418426

419-
var result = StringConfigurationSource.ParseCustomKeyValuesInternal(tokenAsString, allowOptionalMappings);
427+
var result = StringConfigurationSource.ParseCustomKeyValuesInternal(tokenAsString, allowOptionalMappings, separator);
420428
return Validate(result);
421429
}
422430
catch (InvalidCastException)

tracer/src/Datadog.Trace/Configuration/ConfigurationSources/NullConfigurationSource.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// <copyright file="NullConfigurationSource.cs" company="Datadog">
1+
// <copyright file="NullConfigurationSource.cs" company="Datadog">
22
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
33
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
44
// </copyright>
@@ -16,6 +16,8 @@ internal class NullConfigurationSource : IConfigurationSource, ITelemeteredConfi
1616
{
1717
public static readonly NullConfigurationSource Instance = new();
1818

19+
public bool IsPresent(string key) => false;
20+
1921
public string? GetString(string key) => null;
2022

2123
public int? GetInt32(string key) => null;
@@ -43,7 +45,7 @@ internal class NullConfigurationSource : IConfigurationSource, ITelemeteredConfi
4345
public ConfigurationResult<IDictionary<string, string>>? GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator)
4446
=> null;
4547

46-
public ConfigurationResult<IDictionary<string, string>>? GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings)
48+
public ConfigurationResult<IDictionary<string, string>>? GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator)
4749
=> null;
4850

4951
public ConfigurationResult<T>? GetAs<T>(string key, IConfigurationTelemetry telemetry, Func<string, ParsingResult<T>> converter, Func<T, bool>? validator, bool recordValue)

tracer/src/Datadog.Trace/Configuration/ConfigurationSources/StringConfigurationSource.cs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ public abstract class StringConfigurationSource : IConfigurationSource, ITelemet
5858

5959
[return: NotNullIfNotNull(nameof(data))]
6060
internal static IDictionary<string, string>? ParseCustomKeyValuesInternal(string? data, bool allowOptionalMappings)
61+
=> ParseCustomKeyValuesInternal(data, allowOptionalMappings, ':');
62+
63+
[return: NotNullIfNotNull(nameof(data))]
64+
internal static IDictionary<string, string>? ParseCustomKeyValuesInternal(string? data, bool allowOptionalMappings, char separator)
6165
{
6266
// A null return value means the key was not present,
6367
// and CompositeConfigurationSource depends on this behavior
@@ -79,30 +83,30 @@ public abstract class StringConfigurationSource : IConfigurationSource, ITelemet
7983

8084
foreach (var entry in entries)
8185
{
82-
// we need TrimStart() before looking for ':' so we can skip entries with no key
83-
// (that is, entries with a leading ':', like "<empty or whitespace>:value")
86+
// we need TrimStart() before looking for the separator so we can skip entries with no key
87+
// (that is, entries with a leading separator, like "<empty or whitespace>:value")
8488
var trimmedEntry = entry.TrimStart();
8589

8690
if (trimmedEntry.Length > 0)
8791
{
88-
// colonIndex == 0 is a leading colon, not valid
89-
var colonIndex = trimmedEntry.IndexOf(':');
92+
// separatorIndex == 0 is a leading separator, not valid
93+
var separatorIndex = trimmedEntry.IndexOf(separator);
9094

91-
if (colonIndex < 0 && allowOptionalMappings)
95+
if (separatorIndex < 0 && allowOptionalMappings)
9296
{
93-
// entries with no colon are allowed (e.g. "key1, key2:value2, key3"),
97+
// entries with no separator are allowed (e.g. "key1, key2:value2, key3"),
9498
// it's a key with no value.
9599
// note we already did TrimStart(), so we only need TrimEnd().
96100
var key = trimmedEntry.TrimEnd();
97101
dictionary[key] = string.Empty;
98102
}
99-
else if (colonIndex > 0)
103+
else if (separatorIndex > 0)
100104
{
101-
// split at the first colon only. any other colons are part of the value.
102-
// if a colon is present with no value, we take the value to be empty (e.g. "key1:, key2: ").
105+
// split at the first separator only. any other separators are part of the value.
106+
// if a separator is present with no value, we take the value to be empty (e.g. "key1:, key2: ").
103107
// note we already did TrimStart() on the key, so it only needs TrimEnd().
104-
var key = trimmedEntry.Substring(0, colonIndex).TrimEnd();
105-
var value = trimmedEntry.Substring(colonIndex + 1).Trim();
108+
var key = trimmedEntry.Substring(0, separatorIndex).TrimEnd();
109+
var value = trimmedEntry.Substring(separatorIndex + 1).Trim();
106110
dictionary[key] = value;
107111
}
108112
}
@@ -168,6 +172,14 @@ public abstract class StringConfigurationSource : IConfigurationSource, ITelemet
168172
return ParseCustomKeyValuesInternal(GetString(key), allowOptionalMappings);
169173
}
170174

175+
/// <inheritdoc />
176+
bool ITelemeteredConfigurationSource.IsPresent(string key)
177+
{
178+
var value = GetString(key);
179+
180+
return value is not null;
181+
}
182+
171183
/// <inheritdoc />
172184
ConfigurationResult<string>? ITelemeteredConfigurationSource.GetString(string key, IConfigurationTelemetry telemetry, Func<string, bool>? validator, bool recordValue)
173185
{
@@ -296,13 +308,13 @@ public abstract class StringConfigurationSource : IConfigurationSource, ITelemet
296308

297309
/// <inheritdoc />
298310
ConfigurationResult<IDictionary<string, string>>? ITelemeteredConfigurationSource.GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator)
299-
=> GetDictionary(key, telemetry, validator, allowOptionalMappings: false);
311+
=> GetDictionary(key, telemetry, validator, allowOptionalMappings: false, separator: ':');
300312

301313
/// <inheritdoc />
302-
ConfigurationResult<IDictionary<string, string>>? ITelemeteredConfigurationSource.GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings)
303-
=> GetDictionary(key, telemetry, validator, allowOptionalMappings);
314+
ConfigurationResult<IDictionary<string, string>>? ITelemeteredConfigurationSource.GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator)
315+
=> GetDictionary(key, telemetry, validator, allowOptionalMappings, separator);
304316

305-
private ConfigurationResult<IDictionary<string, string>>? GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings)
317+
private ConfigurationResult<IDictionary<string, string>>? GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator)
306318
{
307319
var value = GetString(key);
308320

@@ -314,7 +326,7 @@ public abstract class StringConfigurationSource : IConfigurationSource, ITelemet
314326
// We record the original dictionary value here instead of serializing the _parsed_ value
315327
// Currently we have no validation of the dictionary values during parsing, so there's no way to get
316328
// a validation error that needs recording at this stage
317-
var result = ParseCustomKeyValuesInternal(value, allowOptionalMappings);
329+
var result = ParseCustomKeyValuesInternal(value, allowOptionalMappings, separator);
318330

319331
if (validator is null || validator(result))
320332
{

0 commit comments

Comments
 (0)