diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/CompositeConfigurationSource.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/CompositeConfigurationSource.cs index d4b426007614..cc30e197c128 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/CompositeConfigurationSource.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/CompositeConfigurationSource.cs @@ -31,6 +31,8 @@ public CompositeConfigurationSource(IEnumerable sources) _sources = [..sources]; } + public ConfigurationOrigins Origin => ConfigurationOrigins.Unknown; + /// /// Adds a new configuration source to this instance. /// @@ -51,50 +53,260 @@ public void Add(IConfigurationSource source) /// public ConfigurationResult GetString(string key, IConfigurationTelemetry telemetry, Func? validator, bool recordValue) - => _sources - .Select(source => source.GetString(key, telemetry, validator, recordValue)) - .FirstOrDefault(value => value.IsValid, ConfigurationResult.NotFound()); + { + // We iterate in reverse order, and keep the last successful value + // because we need to record the data for all the sources in telemetry + // We also have to keep track of whether the last value was the last _found_ value + // as we need to "restore" the telemetry if so. + var result = ConfigurationResult.NotFound(); + var isLastFound = false; + var origin = ConfigurationOrigins.Unknown; + for (var i = _sources.Count - 1; i >= 0; i--) + { + var source = _sources[i]; + var value = source.GetString(key, telemetry, validator, recordValue); + if (value.IsValid) + { + result = value; + isLastFound = true; + origin = source.Origin; + } + else if (value.IsPresent) + { + isLastFound = false; + } + } + + if (result.IsValid && !isLastFound) + { + telemetry.Record(key, result.Result, recordValue, origin); + } + + return result; + } /// public ConfigurationResult GetInt32(string key, IConfigurationTelemetry telemetry, Func? validator) - => _sources - .Select(source => source.GetInt32(key, telemetry, validator)) - .FirstOrDefault(value => value.IsValid, ConfigurationResult.NotFound()); + { + // We iterate in reverse order, and keep the last successful value + // because we need to record the data for all the sources in telemetry + var result = ConfigurationResult.NotFound(); + var isLastFound = false; + var origin = ConfigurationOrigins.Unknown; + for (var i = _sources.Count - 1; i >= 0; i--) + { + var source = _sources[i]; + var value = source.GetInt32(key, telemetry, validator); + if (value.IsValid) + { + result = value; + isLastFound = true; + origin = source.Origin; + } + else if (value.IsPresent) + { + isLastFound = false; + } + } + + if (result.IsValid && !isLastFound) + { + telemetry.Record(key, result.Result, origin); + } + + return result; + } /// public ConfigurationResult GetDouble(string key, IConfigurationTelemetry telemetry, Func? validator) - => _sources - .Select(source => source.GetDouble(key, telemetry, validator)) - .FirstOrDefault(value => value.IsValid, ConfigurationResult.NotFound()); + { + // We iterate in reverse order, and keep the last successful value + // because we need to record the data for all the sources in telemetry + var result = ConfigurationResult.NotFound(); + var isLastFound = false; + var origin = ConfigurationOrigins.Unknown; + for (var i = _sources.Count - 1; i >= 0; i--) + { + var source = _sources[i]; + var value = source.GetDouble(key, telemetry, validator); + if (value.IsValid) + { + result = value; + isLastFound = true; + origin = source.Origin; + } + else if (value.IsPresent) + { + isLastFound = false; + } + } + + if (result.IsValid && !isLastFound) + { + telemetry.Record(key, result.Result, origin); + } + + return result; + } /// public ConfigurationResult GetBool(string key, IConfigurationTelemetry telemetry, Func? validator) - => _sources - .Select(source => source.GetBool(key, telemetry, validator)) - .FirstOrDefault(value => value.IsValid, ConfigurationResult.NotFound()); + { + // We iterate in reverse order, and keep the last successful value + // because we need to record the data for all the sources in telemetry + var result = ConfigurationResult.NotFound(); + var isLastFound = false; + var origin = ConfigurationOrigins.Unknown; + for (var i = _sources.Count - 1; i >= 0; i--) + { + var source = _sources[i]; + var value = source.GetBool(key, telemetry, validator); + if (value.IsValid) + { + result = value; + isLastFound = true; + origin = source.Origin; + } + else if (value.IsPresent) + { + isLastFound = false; + } + } + + if (result.IsValid && !isLastFound) + { + telemetry.Record(key, result.Result, origin); + } + + return result; + } /// public ConfigurationResult> GetDictionary(string key, IConfigurationTelemetry telemetry, Func, bool>? validator) - => _sources - .Select(source => source.GetDictionary(key, telemetry, validator)) - .FirstOrDefault(value => value.IsValid, ConfigurationResult>.NotFound()); + { + // We iterate in reverse order, and keep the last successful value + // because we need to record the data for all the sources in telemetry + var result = ConfigurationResult>.NotFound(); + var isLastFound = false; + var origin = ConfigurationOrigins.Unknown; + for (var i = _sources.Count - 1; i >= 0; i--) + { + var source = _sources[i]; + var value = source.GetDictionary(key, telemetry, validator); + if (value.IsValid) + { + result = value; + isLastFound = true; + origin = source.Origin; + } + else if (value.IsPresent) + { + isLastFound = false; + } + } + + if (result.IsValid && !isLastFound) + { + telemetry.Record(key, result.TelemetryOverride ?? result.Result?.ToString(), recordValue: true, origin); + } + + return result; + } /// public ConfigurationResult> GetDictionary(string key, IConfigurationTelemetry telemetry, Func, bool>? validator, bool allowOptionalMappings, char separator) - => _sources - .Select(source => source.GetDictionary(key, telemetry, validator, allowOptionalMappings, separator)) - .FirstOrDefault(value => value.IsValid, ConfigurationResult>.NotFound()); + { + // We iterate in reverse order, and keep the last successful value + // because we need to record the data for all the sources in telemetry + var result = ConfigurationResult>.NotFound(); + var isLastFound = false; + var origin = ConfigurationOrigins.Unknown; + for (var i = _sources.Count - 1; i >= 0; i--) + { + var source = _sources[i]; + var value = source.GetDictionary(key, telemetry, validator, allowOptionalMappings, separator); + if (value.IsValid) + { + result = value; + isLastFound = true; + origin = source.Origin; + } + else if (value.IsPresent) + { + isLastFound = false; + } + } + + if (result.IsValid && !isLastFound) + { + telemetry.Record(key, result.TelemetryOverride ?? result.Result?.ToString(), recordValue: true, origin); + } + + return result; + } /// public ConfigurationResult> GetDictionary(string key, IConfigurationTelemetry telemetry, Func, bool>? validator, Func> parser) - => _sources - .Select(source => source.GetDictionary(key, telemetry, validator, parser)) - .FirstOrDefault(value => value.IsValid, ConfigurationResult>.NotFound()); + { + // We iterate in reverse order, and keep the last successful value + // because we need to record the data for all the sources in telemetry + var result = ConfigurationResult>.NotFound(); + var isLastFound = false; + var origin = ConfigurationOrigins.Unknown; + for (var i = _sources.Count - 1; i >= 0; i--) + { + var source = _sources[i]; + var value = source.GetDictionary(key, telemetry, validator, parser); + if (value.IsValid) + { + result = value; + isLastFound = true; + origin = source.Origin; + } + else if (value.IsPresent) + { + isLastFound = false; + } + } + + if (result.IsValid && !isLastFound) + { + telemetry.Record(key, result.TelemetryOverride ?? result.Result?.ToString(), recordValue: true, origin); + } + + return result; + } /// public ConfigurationResult GetAs(string key, IConfigurationTelemetry telemetry, Func> converter, Func? validator, bool recordValue) - => _sources - .Select(source => source.GetAs(key, telemetry, converter, validator, recordValue)) - .FirstOrDefault(value => value.IsValid, ConfigurationResult.NotFound()); + { + // We iterate in reverse order, and keep the last successful value + // because we need to record the data for all the sources in telemetry + var result = ConfigurationResult.NotFound(); + var isLastFound = false; + var origin = ConfigurationOrigins.Unknown; + for (var i = _sources.Count - 1; i >= 0; i--) + { + var source = _sources[i]; + var value = source.GetAs(key, telemetry, converter, validator, recordValue); + if (value.IsValid) + { + result = value; + isLastFound = true; + origin = source.Origin; + } + else if (value.IsPresent) + { + isLastFound = false; + } + } + + if (result.IsValid && !isLastFound) + { + telemetry.Record(key, result.TelemetryOverride ?? result.Result?.ToString(), recordValue: true, origin); + } + + return result; + } } } diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/DictionaryConfigurationSource.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/DictionaryConfigurationSource.cs index 4649ff54ce92..8774fc579262 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/DictionaryConfigurationSource.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/DictionaryConfigurationSource.cs @@ -19,7 +19,7 @@ public DictionaryConfigurationSource(IReadOnlyDictionary diction _dictionary = dictionary; } - internal override ConfigurationOrigins Origin => ConfigurationOrigins.Code; + public override ConfigurationOrigins Origin => ConfigurationOrigins.Code; protected override string? GetString(string key) { diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/DictionaryObjectConfigurationSource.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/DictionaryObjectConfigurationSource.cs index 8fcccfe63a68..36cf6e8a463c 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/DictionaryObjectConfigurationSource.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/DictionaryObjectConfigurationSource.cs @@ -15,8 +15,6 @@ namespace Datadog.Trace.Configuration; internal class DictionaryObjectConfigurationSource : IConfigurationSource { - private readonly ConfigurationOrigins _origin; - public DictionaryObjectConfigurationSource(IReadOnlyDictionary dictionary) : this(dictionary, ConfigurationOrigins.Code) { @@ -25,9 +23,11 @@ public DictionaryObjectConfigurationSource(IReadOnlyDictionary public DictionaryObjectConfigurationSource(IReadOnlyDictionary dictionary, ConfigurationOrigins origin) { Dictionary = dictionary; - _origin = origin; + Origin = origin; } + public ConfigurationOrigins Origin { get; } + protected IReadOnlyDictionary Dictionary { get; } protected virtual bool TryGetValue(string key, out object? value) @@ -39,17 +39,17 @@ public ConfigurationResult GetString(string key, IConfigurationTelemetry { if (objValue is not string value) { - telemetry.Record(key, objValue.ToString(), recordValue: true, _origin, TelemetryErrorCode.UnexpectedTypeInConfigurationSource); + telemetry.Record(key, objValue.ToString(), recordValue: true, Origin, TelemetryErrorCode.UnexpectedTypeInConfigurationSource); return ConfigurationResult.ParseFailure(); } if (validator is null || validator(value)) { - telemetry.Record(key, value, recordValue, _origin); + telemetry.Record(key, value, recordValue, Origin); return ConfigurationResult.Valid(value); } - telemetry.Record(key, value, recordValue, _origin, TelemetryErrorCode.FailedValidation); + telemetry.Record(key, value, recordValue, Origin, TelemetryErrorCode.FailedValidation); return ConfigurationResult.Invalid(value); } @@ -62,17 +62,17 @@ public ConfigurationResult GetInt32(string key, IConfigurationTelemetry tel { if (objValue is not int value) { - telemetry.Record(key, objValue.ToString(), recordValue: true, _origin, TelemetryErrorCode.UnexpectedTypeInConfigurationSource); + telemetry.Record(key, objValue.ToString(), recordValue: true, Origin, TelemetryErrorCode.UnexpectedTypeInConfigurationSource); return ConfigurationResult.ParseFailure(); } if (validator is null || validator(value)) { - telemetry.Record(key, value, _origin); + telemetry.Record(key, value, Origin); return ConfigurationResult.Valid(value); } - telemetry.Record(key, value, _origin, TelemetryErrorCode.FailedValidation); + telemetry.Record(key, value, Origin, TelemetryErrorCode.FailedValidation); return ConfigurationResult.Invalid(value); } @@ -85,17 +85,17 @@ public ConfigurationResult GetDouble(string key, IConfigurationTelemetry { if (objValue is not double value) { - telemetry.Record(key, objValue.ToString(), recordValue: true, _origin, TelemetryErrorCode.UnexpectedTypeInConfigurationSource); + telemetry.Record(key, objValue.ToString(), recordValue: true, Origin, TelemetryErrorCode.UnexpectedTypeInConfigurationSource); return ConfigurationResult.ParseFailure(); } if (validator is null || validator(value)) { - telemetry.Record(key, value, _origin); + telemetry.Record(key, value, Origin); return ConfigurationResult.Valid(value); } - telemetry.Record(key, value, _origin, TelemetryErrorCode.FailedValidation); + telemetry.Record(key, value, Origin, TelemetryErrorCode.FailedValidation); return ConfigurationResult.Invalid(value); } @@ -108,17 +108,17 @@ public ConfigurationResult GetBool(string key, IConfigurationTelemetry tel { if (objValue is not bool value) { - telemetry.Record(key, objValue.ToString(), recordValue: true, _origin, TelemetryErrorCode.UnexpectedTypeInConfigurationSource); + telemetry.Record(key, objValue.ToString(), recordValue: true, Origin, TelemetryErrorCode.UnexpectedTypeInConfigurationSource); return ConfigurationResult.ParseFailure(); } if (validator is null || validator(value)) { - telemetry.Record(key, value, _origin); + telemetry.Record(key, value, Origin); return ConfigurationResult.Valid(value); } - telemetry.Record(key, value, _origin, TelemetryErrorCode.FailedValidation); + telemetry.Record(key, value, Origin, TelemetryErrorCode.FailedValidation); return ConfigurationResult.Invalid(value); } @@ -134,18 +134,18 @@ public ConfigurationResult> GetDictionary(string key { if (objValue is not IDictionary value) { - telemetry.Record(key, objValue.ToString(), recordValue: true, _origin, TelemetryErrorCode.UnexpectedTypeInConfigurationSource); + telemetry.Record(key, objValue.ToString(), recordValue: true, Origin, TelemetryErrorCode.UnexpectedTypeInConfigurationSource); return ConfigurationResult>.ParseFailure(); } var dictAsString = string.Join($"{separator}", value.Select(x => $"{key}:{value}")); if (validator is null || validator(value)) { - telemetry.Record(key, dictAsString, recordValue: true, _origin); - return ConfigurationResult>.Valid(value); + telemetry.Record(key, dictAsString, recordValue: true, Origin); + return ConfigurationResult>.Valid(value, dictAsString); } - telemetry.Record(key, dictAsString, recordValue: true, _origin, TelemetryErrorCode.FailedValidation); + telemetry.Record(key, dictAsString, recordValue: true, Origin, TelemetryErrorCode.FailedValidation); return ConfigurationResult>.Invalid(value); } @@ -172,15 +172,15 @@ public ConfigurationResult GetAs(string key, IConfigurationTelemetry telem { if (validator is null || validator(result.Result)) { - telemetry.Record(key, valueAsString, recordValue, _origin); - return ConfigurationResult.Valid(result.Result); + telemetry.Record(key, valueAsString, recordValue, Origin); + return ConfigurationResult.Valid(result.Result, valueAsString); } - telemetry.Record(key, valueAsString, recordValue, _origin, TelemetryErrorCode.FailedValidation); + telemetry.Record(key, valueAsString, recordValue, Origin, TelemetryErrorCode.FailedValidation); return ConfigurationResult.Invalid(result.Result); } - telemetry.Record(key, valueAsString, recordValue, _origin, TelemetryErrorCode.ParsingCustomError); + telemetry.Record(key, valueAsString, recordValue, Origin, TelemetryErrorCode.ParsingCustomError); return ConfigurationResult.ParseFailure(); } diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/EnvironmentConfigurationSource.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/EnvironmentConfigurationSource.cs index e534972ae7b1..a38f7154e3b4 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/EnvironmentConfigurationSource.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/EnvironmentConfigurationSource.cs @@ -17,7 +17,7 @@ namespace Datadog.Trace.Configuration internal class EnvironmentConfigurationSource : StringConfigurationSource { /// - internal override ConfigurationOrigins Origin => ConfigurationOrigins.EnvVars; + public override ConfigurationOrigins Origin => ConfigurationOrigins.EnvVars; /// protected override string? GetString(string key) diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/HandsOffConfigurationSource.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/HandsOffConfigurationSource.cs index 2ed3803f981f..45c63d0033e8 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/HandsOffConfigurationSource.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/HandsOffConfigurationSource.cs @@ -17,7 +17,7 @@ internal sealed class HandsOffConfigurationSource(Dictionary con private readonly Dictionary _configurations = configurations; private readonly bool _localFile = localFile; - internal override ConfigurationOrigins Origin => _localFile ? ConfigurationOrigins.LocalStableConfig : ConfigurationOrigins.FleetStableConfig; + public override ConfigurationOrigins Origin => _localFile ? ConfigurationOrigins.LocalStableConfig : ConfigurationOrigins.FleetStableConfig; protected override string? GetString(string key) { diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/IConfigurationSource.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/IConfigurationSource.cs index 960b6a28cb73..852241d8c0ed 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/IConfigurationSource.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/IConfigurationSource.cs @@ -18,6 +18,11 @@ namespace Datadog.Trace.Configuration; /// public interface IConfigurationSource { + /// + /// Gets the origin of the configuration source. + /// + public ConfigurationOrigins Origin { get; } + /// /// Gets the value of /// the setting with the specified key. diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/JsonConfigurationSource.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/JsonConfigurationSource.cs index e588bcedf2df..5dc2ea43aa18 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/JsonConfigurationSource.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/JsonConfigurationSource.cs @@ -28,7 +28,6 @@ internal class JsonConfigurationSource : IConfigurationSource { private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(JsonConfigurationSource)); private readonly JToken? _configuration; - private readonly ConfigurationOrigins _origin; /// /// Initializes a new instance of the @@ -60,9 +59,11 @@ private protected JsonConfigurationSource(string json, ConfigurationOrigins orig if (deserialize is null) { ThrowHelper.ThrowArgumentNullException(nameof(deserialize)); } _configuration = deserialize(json); - _origin = origin; + Origin = origin; } + public ConfigurationOrigins Origin { get; } + internal string? JsonConfigurationFilePath { get; } internal bool TreatNullDictionaryAsEmpty { get; set; } = true; @@ -108,17 +109,17 @@ public ConfigurationResult GetString(string key, IConfigurationTelemetry { if (validator is null || validator(value)) { - telemetry.Record(key, value, recordValue, _origin); + telemetry.Record(key, value, recordValue, Origin); return ConfigurationResult.Valid(value); } - telemetry.Record(key, value, recordValue, _origin, TelemetryErrorCode.FailedValidation); + telemetry.Record(key, value, recordValue, Origin, TelemetryErrorCode.FailedValidation); return ConfigurationResult.Invalid(value); } } catch (Exception) { - telemetry.Record(key, token?.ToString(), recordValue, _origin, TelemetryErrorCode.JsonStringError); + telemetry.Record(key, token?.ToString(), recordValue, Origin, TelemetryErrorCode.JsonStringError); throw; // Existing behaviour } @@ -137,17 +138,17 @@ public ConfigurationResult GetInt32(string key, IConfigurationTelemetry tel { if (validator is null || validator(value.Value)) { - telemetry.Record(key, value.Value, _origin); + telemetry.Record(key, value.Value, Origin); return ConfigurationResult.Valid(value.Value); } - telemetry.Record(key, value.Value, _origin, TelemetryErrorCode.FailedValidation); + telemetry.Record(key, value.Value, Origin, TelemetryErrorCode.FailedValidation); return ConfigurationResult.Invalid(value.Value); } } catch (Exception) { - telemetry.Record(key, token?.ToString(), recordValue: true, _origin, TelemetryErrorCode.JsonInt32Error); + telemetry.Record(key, token?.ToString(), recordValue: true, Origin, TelemetryErrorCode.JsonInt32Error); throw; // Exising behaviour } @@ -166,17 +167,17 @@ public ConfigurationResult GetDouble(string key, IConfigurationTelemetry { if (validator is null || validator(value.Value)) { - telemetry.Record(key, value.Value, _origin); + telemetry.Record(key, value.Value, Origin); return ConfigurationResult.Valid(value.Value); } - telemetry.Record(key, value.Value, _origin, TelemetryErrorCode.FailedValidation); + telemetry.Record(key, value.Value, Origin, TelemetryErrorCode.FailedValidation); return ConfigurationResult.Invalid(value.Value); } } catch (Exception) { - telemetry.Record(key, token?.ToString(), recordValue: true, _origin, TelemetryErrorCode.JsonDoubleError); + telemetry.Record(key, token?.ToString(), recordValue: true, Origin, TelemetryErrorCode.JsonDoubleError); throw; // Exising behaviour } @@ -195,17 +196,17 @@ public ConfigurationResult GetBool(string key, IConfigurationTelemetry tel { if (validator is null || validator(value.Value)) { - telemetry.Record(key, value.Value, _origin); + telemetry.Record(key, value.Value, Origin); return ConfigurationResult.Valid(value.Value); } - telemetry.Record(key, value.Value, _origin, TelemetryErrorCode.FailedValidation); + telemetry.Record(key, value.Value, Origin, TelemetryErrorCode.FailedValidation); return ConfigurationResult.Invalid(value.Value); } } catch (Exception) { - telemetry.Record(key, token?.ToString(), recordValue: true, _origin, TelemetryErrorCode.JsonBooleanError); + telemetry.Record(key, token?.ToString(), recordValue: true, Origin, TelemetryErrorCode.JsonBooleanError); throw; // Exising behaviour } @@ -228,20 +229,20 @@ public ConfigurationResult GetAs(string key, IConfigurationTelemetry telem { if (validator is null || validator(value.Result)) { - telemetry.Record(key, valueAsString, recordValue, _origin); - return ConfigurationResult.Valid(value.Result); + telemetry.Record(key, valueAsString, recordValue, Origin); + return ConfigurationResult.Valid(value.Result, valueAsString); } - telemetry.Record(key, valueAsString, recordValue, _origin, TelemetryErrorCode.FailedValidation); + telemetry.Record(key, valueAsString, recordValue, Origin, TelemetryErrorCode.FailedValidation); return ConfigurationResult.Invalid(value.Result); } - telemetry.Record(key, valueAsString, recordValue, _origin, TelemetryErrorCode.ParsingCustomError); + telemetry.Record(key, valueAsString, recordValue, Origin, TelemetryErrorCode.ParsingCustomError); } } catch (Exception) { - telemetry.Record(key, token?.ToString(), recordValue, _origin, TelemetryErrorCode.JsonStringError); + telemetry.Record(key, token?.ToString(), recordValue, Origin, TelemetryErrorCode.JsonStringError); throw; // Exising behaviour } @@ -301,7 +302,7 @@ public ConfigurationResult> GetDictionary(string key catch (Exception e) { Log.Error(e, "Unable to parse configuration value for {ConfigurationKey} as key-value pairs of strings.", key); - telemetry.Record(key, tokenAsString, recordValue: true, _origin, TelemetryErrorCode.JsonStringError); + telemetry.Record(key, tokenAsString, recordValue: true, Origin, TelemetryErrorCode.JsonStringError); return ConfigurationResult>.ParseFailure(); } } @@ -311,7 +312,7 @@ public ConfigurationResult> GetDictionary(string key } catch (InvalidCastException) { - telemetry.Record(key, tokenAsString, recordValue: true, _origin, TelemetryErrorCode.JsonStringError); + telemetry.Record(key, tokenAsString, recordValue: true, Origin, TelemetryErrorCode.JsonStringError); throw; // Exising behaviour } @@ -319,11 +320,11 @@ ConfigurationResult> Validate(IDictionary>.Valid(dictionary); + telemetry.Record(key, tokenAsString, recordValue: true, Origin); + return ConfigurationResult>.Valid(dictionary, tokenAsString); } - telemetry.Record(key, tokenAsString, recordValue: true, _origin, TelemetryErrorCode.FailedValidation); + telemetry.Record(key, tokenAsString, recordValue: true, Origin, TelemetryErrorCode.FailedValidation); return ConfigurationResult>.Invalid(dictionary); } } @@ -363,7 +364,7 @@ public ConfigurationResult> GetDictionary(string key catch (Exception e) { Log.Error(e, "Unable to parse configuration value for {ConfigurationKey} as key-value pairs of strings.", key); - telemetry.Record(key, tokenAsString, recordValue: true, _origin, TelemetryErrorCode.JsonStringError); + telemetry.Record(key, tokenAsString, recordValue: true, Origin, TelemetryErrorCode.JsonStringError); return ConfigurationResult>.ParseFailure(); } } @@ -373,7 +374,7 @@ public ConfigurationResult> GetDictionary(string key } catch (InvalidCastException) { - telemetry.Record(key, tokenAsString, recordValue: true, _origin, TelemetryErrorCode.JsonStringError); + telemetry.Record(key, tokenAsString, recordValue: true, Origin, TelemetryErrorCode.JsonStringError); throw; // Exising behaviour } @@ -381,11 +382,11 @@ ConfigurationResult> Validate(IDictionary>.Valid(dictionary); + telemetry.Record(key, tokenAsString, recordValue: true, Origin); + return ConfigurationResult>.Valid(dictionary, tokenAsString); } - telemetry.Record(key, tokenAsString, recordValue: true, _origin, TelemetryErrorCode.FailedValidation); + telemetry.Record(key, tokenAsString, recordValue: true, Origin, TelemetryErrorCode.FailedValidation); return ConfigurationResult>.Invalid(dictionary); } } diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/NameValueConfigurationSource.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/NameValueConfigurationSource.cs index f7f086b575d0..040beeaac4ca 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/NameValueConfigurationSource.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/NameValueConfigurationSource.cs @@ -39,7 +39,7 @@ internal NameValueConfigurationSource(NameValueCollection nameValueCollection, C Origin = origin; } - internal override ConfigurationOrigins Origin { get; } + public override ConfigurationOrigins Origin { get; } /// protected override string? GetString(string key) diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/NullConfigurationSource.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/NullConfigurationSource.cs index 804712d77bb2..456a100fe577 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/NullConfigurationSource.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/NullConfigurationSource.cs @@ -16,6 +16,8 @@ internal class NullConfigurationSource : IConfigurationSource { public static readonly NullConfigurationSource Instance = new(); + public ConfigurationOrigins Origin => ConfigurationOrigins.Unknown; + public ConfigurationResult GetString(string key, IConfigurationTelemetry telemetry, Func? validator, bool recordValue) => ConfigurationResult.NotFound(); diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/StringConfigurationSource.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/StringConfigurationSource.cs index 7298b7b8777d..b22d9bcec633 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/StringConfigurationSource.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/StringConfigurationSource.cs @@ -24,7 +24,7 @@ internal abstract class StringConfigurationSource : IConfigurationSource { private static readonly char[] DictionarySeparatorChars = { ',' }; - internal abstract ConfigurationOrigins Origin { get; } + public abstract ConfigurationOrigins Origin { get; } /// /// Returns a from parsing @@ -229,7 +229,7 @@ public ConfigurationResult GetAs(string key, IConfigurationTelemetry telem if (validator is null || validator(result.Result)) { telemetry.Record(key, value, recordValue, Origin); - return ConfigurationResult.Valid(result.Result); + return ConfigurationResult.Valid(result.Result, value); } telemetry.Record(key, value, recordValue, Origin, TelemetryErrorCode.FailedValidation); @@ -262,7 +262,7 @@ public ConfigurationResult> GetDictionary(string key if (validator is null || validator(result)) { telemetry.Record(key, value, recordValue: true, Origin); - return ConfigurationResult>.Valid(result); + return ConfigurationResult>.Valid(result, value); } telemetry.Record(key, value, recordValue: true, Origin, TelemetryErrorCode.FailedValidation); @@ -287,7 +287,7 @@ public ConfigurationResult> GetDictionary(string key if (validator is null || validator(result)) { telemetry.Record(key, value, recordValue: true, Origin); - return ConfigurationResult>.Valid(result); + return ConfigurationResult>.Valid(result, value); } telemetry.Record(key, value, recordValue: true, Origin, TelemetryErrorCode.FailedValidation); diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/Telemetry/ConfigurationBuilder.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/Telemetry/ConfigurationBuilder.cs index 372f9bef35c9..dd2fe2d515e5 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/Telemetry/ConfigurationBuilder.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/Telemetry/ConfigurationBuilder.cs @@ -120,24 +120,31 @@ public string AsString(string defaultValue, Func? validator) [return: NotNullIfNotNull(nameof(defaultValue))] private string? AsString(string? defaultValue, Func? validator, bool recordValue) { + // pre-record the default value, so it's in the "correct" place in the stack + if (defaultValue is not null) + { + Telemetry.Record(Key, defaultValue, recordValue, ConfigurationOrigins.Default); + } + var result = GetStringResult(validator, converter: null, recordValue); if (result is { Result: { } ddResult, IsValid: true }) { return ddResult; } - if (defaultValue is null) + if (defaultValue is not null && result.IsPresent) { - return null; + // re-record telemetry because we found an invalid value in sources which clobbered it + Telemetry.Record(Key, defaultValue, recordValue, ConfigurationOrigins.Default); } - Telemetry.Record(Key, defaultValue, recordValue, ConfigurationOrigins.Default); return defaultValue; } [return: NotNullIfNotNull(nameof(getDefaultValue))] private string? AsString(Func? getDefaultValue, Func? validator, Func>? converter, bool recordValue) { + // We don't "pre-record" the default because it's expensive to create var result = GetStringResult(validator, converter, recordValue); if (result is { Result: { } ddResult, IsValid: true }) { @@ -161,6 +168,10 @@ public string AsString(string defaultValue, Func? validator) public T GetAs(DefaultResult defaultValue, Func? validator, Func> converter) where T : notnull { + // Ideally we would like to pre-record the default telemetry here so it's in the correct place + // in the stack, but the GetAs behaviour of the JsonConfigurationSource is problematic, as it + // adds a telemetry result but still returns NotFound, so we can't use NotFound as the indicator + // of whether we need to re-record the telemetry or not var result = GetAs(validator, converter); if (result is { Result: { } ddResult, IsValid: true }) { @@ -174,6 +185,7 @@ public T GetAs(DefaultResult defaultValue, Func? validator, Func< public T GetAs(Func> getDefaultValue, Func? validator, Func> converter) where T : notnull { + // We don't "pre-record" the default because it's expensive to create var result = GetAs(validator, converter); if (result is { Result: { } ddResult, IsValid: true }) { @@ -204,7 +216,7 @@ public T GetAs(Func> getDefaultValue, Func? validat // **************** public bool? AsBool() => AsBool(defaultValue: null, validator: null, converter: null); - public bool AsBool(bool defaultValue) => AsBool(() => defaultValue, validator: null).Value; + public bool AsBool(bool defaultValue) => AsBool(defaultValue, validator: null); public bool? AsBool(Func validator) => AsBool(defaultValue: null, validator, converter: null); @@ -218,24 +230,30 @@ public bool AsBool(bool defaultValue, Func? validator) [return: NotNullIfNotNull(nameof(defaultValue))] public bool? AsBool(bool? defaultValue, Func? validator, Func>? converter) { + // pre-record the default value, so it's in the "correct" place in the stack + if (defaultValue.HasValue) + { + Telemetry.Record(Key, defaultValue.Value, ConfigurationOrigins.Default); + } + var result = GetBoolResult(validator, converter: null); if (result is { Result: { } ddResult, IsValid: true }) { return ddResult; } - if (defaultValue is not { } value) + if (defaultValue is { } value && result.IsPresent) { - return null; + Telemetry.Record(Key, value, ConfigurationOrigins.Default); } - Telemetry.Record(Key, value, ConfigurationOrigins.Default); - return value; + return defaultValue; } [return: NotNullIfNotNull(nameof(getDefaultValue))] // This doesn't work with nullables, but it still expresses intent public bool? AsBool(Func? getDefaultValue, Func? validator, Func>? converter) { + // We don't "pre-record" the default because it's expensive to create var result = GetBoolResult(validator, converter); if (result is { Result: { } ddResult, IsValid: true }) { @@ -268,19 +286,24 @@ public bool AsBool(bool defaultValue, Func? validator) [return: NotNullIfNotNull(nameof(defaultValue))] // This doesn't work with nullables, but it still expresses intent public int? AsInt32(int? defaultValue, Func? validator, Func>? converter) { + // pre-record the default value, so it's in the "correct" place in the stack + if (defaultValue.HasValue) + { + Telemetry.Record(Key, defaultValue.Value, ConfigurationOrigins.Default); + } + var result = GetInt32Result(validator, converter); if (result is { Result: { } ddResult, IsValid: true }) { return ddResult; } - if (defaultValue is not { } value) + if (defaultValue is { } value && result.IsPresent) { - return null; + Telemetry.Record(Key, value, ConfigurationOrigins.Default); } - Telemetry.Record(Key, value, ConfigurationOrigins.Default); - return value; + return defaultValue; } // **************** @@ -299,19 +322,24 @@ public bool AsBool(bool defaultValue, Func? validator) [return: NotNullIfNotNull(nameof(defaultValue))] public double? AsDouble(double? defaultValue, Func? validator, Func>? converter) { + // pre-record the default value, so it's in the "correct" place in the stack + if (defaultValue.HasValue) + { + Telemetry.Record(Key, defaultValue.Value, ConfigurationOrigins.Default); + } + var result = GetDoubleResult(validator, converter); if (result is { Result: { } ddResult, IsValid: true }) { return ddResult; } - if (defaultValue is not { } value) + if (defaultValue is { } value && result.IsPresent) { - return null; + Telemetry.Record(Key, value, ConfigurationOrigins.Default); } - Telemetry.Record(Key, value, ConfigurationOrigins.Default); - return value; + return defaultValue; } // **************** diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/Telemetry/ConfigurationResult.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/Telemetry/ConfigurationResult.cs index 4bb3ae15b811..ab1f3253880d 100644 --- a/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/Telemetry/ConfigurationResult.cs +++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationSources/Telemetry/ConfigurationResult.cs @@ -15,9 +15,10 @@ namespace Datadog.Trace.Configuration.ConfigurationSources.Telemetry; /// The type of the returned value public readonly record struct ConfigurationResult { - private ConfigurationResult(T? result, ConfigurationLoadResult loadResult) + private ConfigurationResult(T? result, string? telemetryOverride, ConfigurationLoadResult loadResult) { Result = result; + TelemetryOverride = telemetryOverride; LoadResult = loadResult; } @@ -26,6 +27,12 @@ private ConfigurationResult(T? result, ConfigurationLoadResult loadResult) /// public T? Result { get; } + /// + /// Gets the configuration value that represents the in telemetry, + /// where cannot be directly stored in telemetry. + /// + public string? TelemetryOverride { get; } + /// /// Gets a value indicating whether result passed validation. /// If true, implies that contains a valid value. If @@ -55,24 +62,55 @@ private ConfigurationResult(T? result, ConfigurationLoadResult loadResult) /// /// The value to use /// The - public static ConfigurationResult Valid(T result) => new(result, ConfigurationLoadResult.Valid); + public static ConfigurationResult Valid(int result) => new(result, null, ConfigurationLoadResult.Valid); + + /// + /// Creates an instance of with a value + /// + /// The value to use + /// The + public static ConfigurationResult Valid(bool result) => new(result, null, ConfigurationLoadResult.Valid); + + /// + /// Creates an instance of with a value + /// + /// The value to use + /// The + public static ConfigurationResult Valid(double result) => new(result, null, ConfigurationLoadResult.Valid); + + /// + /// Creates an instance of with a value + /// + /// The value to use + /// The + public static ConfigurationResult Valid(string result) => new(result, null, ConfigurationLoadResult.Valid); + + /// + /// Creates an instance of with a value + /// + /// The value to use + /// The telemetry value to use where the value of cannot be stored directly in telemetry. + /// This is required if is not an int/bool/double/string + /// The + public static ConfigurationResult Valid(T result, string? telemetryOverride) + => new(result, telemetryOverride, ConfigurationLoadResult.Valid); /// /// Creates an instance of with a value /// /// The value that failed validation /// The - public static ConfigurationResult Invalid(T result) => new(result, ConfigurationLoadResult.ValidationFailure); + public static ConfigurationResult Invalid(T result) => new(result, null, ConfigurationLoadResult.ValidationFailure); /// /// Creates an instance of with a value /// /// The - public static ConfigurationResult NotFound() => new(default, ConfigurationLoadResult.NotFound); + public static ConfigurationResult NotFound() => new(default, null, ConfigurationLoadResult.NotFound); /// /// Creates an instance of with a value /// /// The - public static ConfigurationResult ParseFailure() => new(default, ConfigurationLoadResult.ParsingError); + public static ConfigurationResult ParseFailure() => new(default, null, ConfigurationLoadResult.ParsingError); } diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/TelemetryTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/TelemetryTests.cs index 2c02445e43bd..29948bac4ba7 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/TelemetryTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/TelemetryTests.cs @@ -48,6 +48,9 @@ public TelemetryTests(ITestOutputHelper output) [MemberData(nameof(Data))] public async Task Telemetry_Agentless_IsSentOnAppClose(bool? enableDependencies) { + // This is the default, we're just setting it to test the telemetry sent + SetEnvironmentVariable(ConfigurationKeys.GitMetadataEnabled, "1"); + using var agent = MockTracerAgent.Create(Output, useTelemetry: true); Output.WriteLine($"Assigned port {agent.Port} for the agentPort."); @@ -71,6 +74,19 @@ public async Task Telemetry_Agentless_IsSentOnAppClose(bool? enableDependencies) await telemetry.AssertConfigurationAsync(ConfigurationKeys.PropagationStyleExtract, "Datadog,tracecontext,baggage"); await telemetry.AssertConfigurationAsync(ConfigurationKeys.PropagationStyleInject, "Datadog,tracecontext,baggage"); + // verify that we report both the default and explicit value to telemetry + var allConfigKeys = (await telemetry.GetConfigurationValuesAsync(ConfigurationKeys.GitMetadataEnabled)).ToList(); + allConfigKeys + .Should() + .HaveCount(2) + .And + .BeEquivalentTo( + [ + new { Name = ConfigurationKeys.GitMetadataEnabled, Value = true, Origin = "default" }, + new { Name = ConfigurationKeys.GitMetadataEnabled, Value = true, Origin = "env_var" } + ]); + allConfigKeys[0].SeqId.Should().BeLessThan(allConfigKeys[1].SeqId); + await AssertServiceAsync(telemetry, "Samples.Telemetry", ServiceVersion); await AssertDependenciesAsync(telemetry, enableDependencies); await AssertNoRedactedErrorLogsAsync(telemetry); diff --git a/tracer/test/Datadog.Trace.Tests/Configuration/CompositeConfigurationSourceTests.cs b/tracer/test/Datadog.Trace.Tests/Configuration/CompositeConfigurationSourceTests.cs index f4322d5401c3..211cca8b9cda 100644 --- a/tracer/test/Datadog.Trace.Tests/Configuration/CompositeConfigurationSourceTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Configuration/CompositeConfigurationSourceTests.cs @@ -131,15 +131,15 @@ public void GetsTheExpectedStringInAllCases(string key, string expected) } [Theory] - [InlineData("string1")] - [InlineData("string2")] - [InlineData("string3")] - [InlineData("string4")] - public void AttemptsToGrabStringFromEverySource(string key) + [InlineData("string1", 4)] + [InlineData("string2", 3)] + [InlineData("string3", 2)] + [InlineData("string4", 1)] + public void AttemptsToGrabStringFromEverySourceAndRecordsAllOccurrences(string key, int occurrences) { var telemetry = new StubTelemetry(); var actual = _source.GetString(key, telemetry, validator: null, recordValue: true); - telemetry.GetInstanceCount(key).Should().Be(1); + telemetry.GetInstanceCount(key).Should().Be(occurrences); } [Theory] @@ -154,15 +154,15 @@ public void GetsTheExpectedIntInAllCases(string key, int expected) } [Theory] - [InlineData("int1")] - [InlineData("int2")] - [InlineData("int3")] - [InlineData("int4")] - public void AttemptsToGrabIntFromEverySource(string key) + [InlineData("int1", 4)] + [InlineData("int2", 3)] + [InlineData("int3", 2)] + [InlineData("int4", 1)] + public void AttemptsToGrabIntFromEverySourceAndRecordsAllOccurrences(string key, int occurrences) { var telemetry = new StubTelemetry(); var actual = _source.GetInt32(key, telemetry, validator: null); - telemetry.GetInstanceCount(key).Should().Be(1); + telemetry.GetInstanceCount(key).Should().Be(occurrences); } [Theory] @@ -177,15 +177,15 @@ public void GetsTheExpectedDoubleInAllCases(string key, double expected) } [Theory] - [InlineData("double1")] - [InlineData("double2")] - [InlineData("double3")] - [InlineData("double4")] - public void AttemptsToGrabDoubleFromEverySource(string key) + [InlineData("double1", 4)] + [InlineData("double2", 3)] + [InlineData("double3", 2)] + [InlineData("double4", 1)] + public void AttemptsToGrabDoubleFromEverySourceAndRecordsAllOccurrences(string key, int occurrences) { var telemetry = new StubTelemetry(); var actual = _source.GetDouble(key, telemetry, validator: null); - telemetry.GetInstanceCount(key).Should().Be(1); + telemetry.GetInstanceCount(key).Should().Be(occurrences); } [Theory] @@ -200,15 +200,15 @@ public void GetsTheExpectedBoolInAllCases(string key, bool expected) } [Theory] - [InlineData("bool1")] - [InlineData("bool2")] - [InlineData("bool3")] - [InlineData("bool4")] - public void AttemptsToGrabBoolFromEverySource(string key) + [InlineData("bool1", 4)] + [InlineData("bool2", 3)] + [InlineData("bool3", 2)] + [InlineData("bool4", 1)] + public void AttemptsToGrabBoolFromEverySourceAndRecordsAllOccurrences(string key, int occurrences) { var telemetry = new StubTelemetry(); var actual = _source.GetBool(key, telemetry, validator: null); - telemetry.GetInstanceCount(key).Should().Be(1); + telemetry.GetInstanceCount(key).Should().Be(occurrences); } [Theory] @@ -223,15 +223,15 @@ public void GetsTheExpectedDictionaryInAllCases(string key, params string[] expe } [Theory] - [InlineData("dict1")] - [InlineData("dict2")] - [InlineData("dict3")] - [InlineData("dict4")] - public void AttemptsToGrabDictionaryFromEverySource(string key) + [InlineData("dict1", 4)] + [InlineData("dict2", 3)] + [InlineData("dict3", 2)] + [InlineData("dict4", 1)] + public void AttemptsToGrabDictionaryFromEverySourceAndRecordsAllOccurrences(string key, int occurrences) { var telemetry = new StubTelemetry(); var actual = _source.GetDictionary(key, telemetry, validator: null); - telemetry.GetInstanceCount(key).Should().Be(1); + telemetry.GetInstanceCount(key).Should().Be(occurrences); } [Fact] @@ -300,10 +300,13 @@ public void RecordsTelemetry_WhenPresentInMultipleSources() // final telemetry value should be the "real" value telemetry.Telemetry.Last().Value.Should().Be(expected); - // telemetry records the first error, and then the successful value + // telemetry records everything where a value was found. The last value is the "current" value telemetry.Telemetry.Should() .BeEquivalentTo( [ + new ConfigurationTelemetryTests.ConfigDto(key, 456, ConfigurationOrigins.EnvVars, true, null), + new ConfigurationTelemetryTests.ConfigDto(key, "not_an_int", ConfigurationOrigins.RemoteConfig, true, TelemetryErrorCode.ParsingInt32Error), + new ConfigurationTelemetryTests.ConfigDto(key, 123, ConfigurationOrigins.Code, true, null), new ConfigurationTelemetryTests.ConfigDto(key, "not_an_int", ConfigurationOrigins.DdConfig, true, TelemetryErrorCode.ParsingInt32Error), new ConfigurationTelemetryTests.ConfigDto(key, 123, ConfigurationOrigins.Code, true, null), ]); diff --git a/tracer/test/Datadog.Trace.Tests/Configuration/Telemetry/ConfigurationBuilderTests.cs b/tracer/test/Datadog.Trace.Tests/Configuration/Telemetry/ConfigurationBuilderTests.cs index b810c0901547..65ae8597f9c0 100644 --- a/tracer/test/Datadog.Trace.Tests/Configuration/Telemetry/ConfigurationBuilderTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Configuration/Telemetry/ConfigurationBuilderTests.cs @@ -158,11 +158,13 @@ public void RecordsTelemetryCorrectly(object value) { "" => new List { + Entry.String(Key, Default, ConfigurationOrigins.Default, error: null), Entry.String(Key, string.Empty, ConfigurationOrigins.Code, error: TelemetryErrorCode.FailedValidation), Entry.String(Key, Default, ConfigurationOrigins.Default, error: null), }, string s => new List { + Entry.String(Key, Default, ConfigurationOrigins.Default, error: null), Entry.String(Key, s, ConfigurationOrigins.Code, error: null), }, null => new() @@ -171,6 +173,7 @@ public void RecordsTelemetryCorrectly(object value) }, { } i => new List { + Entry.String(Key, Default, ConfigurationOrigins.Default, error: null), Entry.String(Key, Convert.ToString(i, CultureInfo.InvariantCulture), ConfigurationOrigins.Code, error: null), }, }; @@ -210,10 +213,12 @@ public void RecordsTelemetryCorrectly(object value) { true or "True" or "true" => new List() { + Entry.Bool(Key, true, ConfigurationOrigins.Default, error: null), Entry.Bool(Key, true, ConfigurationOrigins.Code, error: null), }, false or "False" or "false" => new() { + Entry.Bool(Key, true, ConfigurationOrigins.Default, error: null), Entry.Bool(Key, false, ConfigurationOrigins.Code, TelemetryErrorCode.FailedValidation), Entry.Bool(Key, true, ConfigurationOrigins.Default, error: null), }, @@ -223,6 +228,7 @@ public void RecordsTelemetryCorrectly(object value) }, string x1 => new() { + Entry.Bool(Key, true, ConfigurationOrigins.Default, error: null), Entry.String(Key, x1, ConfigurationOrigins.Code, TelemetryErrorCode.JsonBooleanError), }, _ => throw new InvalidOperationException("Unexpected value " + value), @@ -258,23 +264,28 @@ public void RecordsTelemetryCorrectly(object value) { int i and > 0 => new List { + Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), Entry.Number(Key, i, ConfigurationOrigins.Code, error: null), }, "123" => new List // Note the implicit conversion, but not for 23.3! { + Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), Entry.Number(Key, 123, ConfigurationOrigins.Code, error: null), }, int i => new List { + Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), Entry.Number(Key, i, ConfigurationOrigins.Code, error: TelemetryErrorCode.FailedValidation), Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), }, double d and > 0 => new List // Note the implicit conversion! { + Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), Entry.Number(Key, (int)d, ConfigurationOrigins.Code, error: null), }, double d => new List // Note the implicit conversion! { + Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), Entry.Number(Key, (int)d, ConfigurationOrigins.Code, error: TelemetryErrorCode.FailedValidation), Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), }, @@ -284,6 +295,7 @@ public void RecordsTelemetryCorrectly(object value) }, string x1 => new() { + Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), Entry.String(Key, x1, ConfigurationOrigins.Code, TelemetryErrorCode.JsonInt32Error), }, _ => throw new InvalidOperationException("Unexpected value " + value), @@ -321,28 +333,34 @@ public void RecordsTelemetryCorrectly(object value) { int i and > 0 => new List { + Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), Entry.Number(Key, (double)i, ConfigurationOrigins.Code, error: null), }, int i => new List { + Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), Entry.Number(Key, (double)i, ConfigurationOrigins.Code, error: TelemetryErrorCode.FailedValidation), Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), }, double d and > 0 => new List { + Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), Entry.Number(Key, d, ConfigurationOrigins.Code, error: null), }, double d => new List // Note the implicit conversion! { + Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), Entry.Number(Key, d, ConfigurationOrigins.Code, error: TelemetryErrorCode.FailedValidation), Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), }, string s when TryParse(s, out var d) && d > 0 => new List { + Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), Entry.Number(Key, d, ConfigurationOrigins.Code, error: null), }, string s when TryParse(s, out var d) => new List { + Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), Entry.Number(Key, d, ConfigurationOrigins.Code, error: TelemetryErrorCode.FailedValidation), Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), }, @@ -352,6 +370,7 @@ public void RecordsTelemetryCorrectly(object value) }, string x => new() { + Entry.Number(Key, Default, ConfigurationOrigins.Default, error: null), Entry.String(Key, x, ConfigurationOrigins.Code, TelemetryErrorCode.JsonDoubleError), }, _ => throw new InvalidOperationException("Unexpected value " + value),