Skip to content

Commit 7b8127e

Browse files
authored
Implement "enahanced telemetry reporting" (#7360)
## Summary of changes Implements "enhance telemetry reporting" for .NET ## Reason for change We want to report the telemetry found from _all_ sources, to implement [this RFC](https://docs.google.com/document/d/1vhIimn2vt4tDRSxsHn6vWSc8zYHl0Lv0Fk7CQps04C4/edit?tab=t.0#heading=h.7tw832mj0ztp), so that customers can more easily understand where their telemetry comes from. ## Implementation details The .NET implementation was already pretty close to the desired result, so there were just some main changes to make - Read sources in lowest to highest priority, and always report all found values. - This differs from previously, where we would read from highest to lowest and stop on first found values. - In order to ensure that the "last" reported telemetry value is the _actual_ value we use, we have to record the telemetry value _twice_ in cases where we have a parse failure (which should be rare in practice). - To do this properly, we had to explicitly expose the `Origin` on `IConfigurationSource` and captuate the "value for telemetry" where required (for non-simple types). - Report the "default" value to be used, even if it's going to be overridden by an explicit value. - This is in keeping with the general "report all sources" principle. - We have to do the same "fixing" of values when we have parsing errors as above. - To avoid expensive unnecessary computation where a default value is not "simple" we go against this approach when a `Func<T>` is provided for calculating the default. - For "fallback" keys, we just treat them the same as we do today. The backend will handle merging the keys ## Test coverage Mostly covered by existing tests, and added/updated unit tests to meet new expectations ## Other details Spec: https://docs.google.com/document/d/1vhIimn2vt4tDRSxsHn6vWSc8zYHl0Lv0Fk7CQps04C4/edit?tab=t.0#heading=h.7tw832mj0ztp Dependent (stacked) on - #7327 - #7354 - #7345
1 parent 2fb886e commit 7b8127e

15 files changed

+460
-136
lines changed

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

Lines changed: 236 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public CompositeConfigurationSource(IEnumerable<IConfigurationSource> sources)
3131
_sources = [..sources];
3232
}
3333

34+
public ConfigurationOrigins Origin => ConfigurationOrigins.Unknown;
35+
3436
/// <summary>
3537
/// Adds a new configuration source to this instance.
3638
/// </summary>
@@ -51,50 +53,260 @@ public void Add(IConfigurationSource source)
5153

5254
/// <inheritdoc />
5355
public ConfigurationResult<string> GetString(string key, IConfigurationTelemetry telemetry, Func<string, bool>? validator, bool recordValue)
54-
=> _sources
55-
.Select(source => source.GetString(key, telemetry, validator, recordValue))
56-
.FirstOrDefault(value => value.IsValid, ConfigurationResult<string>.NotFound());
56+
{
57+
// We iterate in reverse order, and keep the last successful value
58+
// because we need to record the data for all the sources in telemetry
59+
// We also have to keep track of whether the last value was the last _found_ value
60+
// as we need to "restore" the telemetry if so.
61+
var result = ConfigurationResult<string>.NotFound();
62+
var isLastFound = false;
63+
var origin = ConfigurationOrigins.Unknown;
64+
for (var i = _sources.Count - 1; i >= 0; i--)
65+
{
66+
var source = _sources[i];
67+
var value = source.GetString(key, telemetry, validator, recordValue);
68+
if (value.IsValid)
69+
{
70+
result = value;
71+
isLastFound = true;
72+
origin = source.Origin;
73+
}
74+
else if (value.IsPresent)
75+
{
76+
isLastFound = false;
77+
}
78+
}
79+
80+
if (result.IsValid && !isLastFound)
81+
{
82+
telemetry.Record(key, result.Result, recordValue, origin);
83+
}
84+
85+
return result;
86+
}
5787

5888
/// <inheritdoc />
5989
public ConfigurationResult<int> GetInt32(string key, IConfigurationTelemetry telemetry, Func<int, bool>? validator)
60-
=> _sources
61-
.Select(source => source.GetInt32(key, telemetry, validator))
62-
.FirstOrDefault(value => value.IsValid, ConfigurationResult<int>.NotFound());
90+
{
91+
// We iterate in reverse order, and keep the last successful value
92+
// because we need to record the data for all the sources in telemetry
93+
var result = ConfigurationResult<int>.NotFound();
94+
var isLastFound = false;
95+
var origin = ConfigurationOrigins.Unknown;
96+
for (var i = _sources.Count - 1; i >= 0; i--)
97+
{
98+
var source = _sources[i];
99+
var value = source.GetInt32(key, telemetry, validator);
100+
if (value.IsValid)
101+
{
102+
result = value;
103+
isLastFound = true;
104+
origin = source.Origin;
105+
}
106+
else if (value.IsPresent)
107+
{
108+
isLastFound = false;
109+
}
110+
}
111+
112+
if (result.IsValid && !isLastFound)
113+
{
114+
telemetry.Record(key, result.Result, origin);
115+
}
116+
117+
return result;
118+
}
63119

64120
/// <inheritdoc />
65121
public ConfigurationResult<double> GetDouble(string key, IConfigurationTelemetry telemetry, Func<double, bool>? validator)
66-
=> _sources
67-
.Select(source => source.GetDouble(key, telemetry, validator))
68-
.FirstOrDefault(value => value.IsValid, ConfigurationResult<double>.NotFound());
122+
{
123+
// We iterate in reverse order, and keep the last successful value
124+
// because we need to record the data for all the sources in telemetry
125+
var result = ConfigurationResult<double>.NotFound();
126+
var isLastFound = false;
127+
var origin = ConfigurationOrigins.Unknown;
128+
for (var i = _sources.Count - 1; i >= 0; i--)
129+
{
130+
var source = _sources[i];
131+
var value = source.GetDouble(key, telemetry, validator);
132+
if (value.IsValid)
133+
{
134+
result = value;
135+
isLastFound = true;
136+
origin = source.Origin;
137+
}
138+
else if (value.IsPresent)
139+
{
140+
isLastFound = false;
141+
}
142+
}
143+
144+
if (result.IsValid && !isLastFound)
145+
{
146+
telemetry.Record(key, result.Result, origin);
147+
}
148+
149+
return result;
150+
}
69151

70152
/// <inheritdoc />
71153
public ConfigurationResult<bool> GetBool(string key, IConfigurationTelemetry telemetry, Func<bool, bool>? validator)
72-
=> _sources
73-
.Select(source => source.GetBool(key, telemetry, validator))
74-
.FirstOrDefault(value => value.IsValid, ConfigurationResult<bool>.NotFound());
154+
{
155+
// We iterate in reverse order, and keep the last successful value
156+
// because we need to record the data for all the sources in telemetry
157+
var result = ConfigurationResult<bool>.NotFound();
158+
var isLastFound = false;
159+
var origin = ConfigurationOrigins.Unknown;
160+
for (var i = _sources.Count - 1; i >= 0; i--)
161+
{
162+
var source = _sources[i];
163+
var value = source.GetBool(key, telemetry, validator);
164+
if (value.IsValid)
165+
{
166+
result = value;
167+
isLastFound = true;
168+
origin = source.Origin;
169+
}
170+
else if (value.IsPresent)
171+
{
172+
isLastFound = false;
173+
}
174+
}
175+
176+
if (result.IsValid && !isLastFound)
177+
{
178+
telemetry.Record(key, result.Result, origin);
179+
}
180+
181+
return result;
182+
}
75183

76184
/// <inheritdoc />
77185
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator)
78-
=> _sources
79-
.Select(source => source.GetDictionary(key, telemetry, validator))
80-
.FirstOrDefault(value => value.IsValid, ConfigurationResult<IDictionary<string, string>>.NotFound());
186+
{
187+
// We iterate in reverse order, and keep the last successful value
188+
// because we need to record the data for all the sources in telemetry
189+
var result = ConfigurationResult<IDictionary<string, string>>.NotFound();
190+
var isLastFound = false;
191+
var origin = ConfigurationOrigins.Unknown;
192+
for (var i = _sources.Count - 1; i >= 0; i--)
193+
{
194+
var source = _sources[i];
195+
var value = source.GetDictionary(key, telemetry, validator);
196+
if (value.IsValid)
197+
{
198+
result = value;
199+
isLastFound = true;
200+
origin = source.Origin;
201+
}
202+
else if (value.IsPresent)
203+
{
204+
isLastFound = false;
205+
}
206+
}
207+
208+
if (result.IsValid && !isLastFound)
209+
{
210+
telemetry.Record(key, result.TelemetryOverride ?? result.Result?.ToString(), recordValue: true, origin);
211+
}
212+
213+
return result;
214+
}
81215

82216
/// <inheritdoc />
83217
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator)
84-
=> _sources
85-
.Select(source => source.GetDictionary(key, telemetry, validator, allowOptionalMappings, separator))
86-
.FirstOrDefault(value => value.IsValid, ConfigurationResult<IDictionary<string, string>>.NotFound());
218+
{
219+
// We iterate in reverse order, and keep the last successful value
220+
// because we need to record the data for all the sources in telemetry
221+
var result = ConfigurationResult<IDictionary<string, string>>.NotFound();
222+
var isLastFound = false;
223+
var origin = ConfigurationOrigins.Unknown;
224+
for (var i = _sources.Count - 1; i >= 0; i--)
225+
{
226+
var source = _sources[i];
227+
var value = source.GetDictionary(key, telemetry, validator, allowOptionalMappings, separator);
228+
if (value.IsValid)
229+
{
230+
result = value;
231+
isLastFound = true;
232+
origin = source.Origin;
233+
}
234+
else if (value.IsPresent)
235+
{
236+
isLastFound = false;
237+
}
238+
}
239+
240+
if (result.IsValid && !isLastFound)
241+
{
242+
telemetry.Record(key, result.TelemetryOverride ?? result.Result?.ToString(), recordValue: true, origin);
243+
}
244+
245+
return result;
246+
}
87247

88248
/// <inheritdoc />
89249
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, Func<string, IDictionary<string, string>> parser)
90-
=> _sources
91-
.Select(source => source.GetDictionary(key, telemetry, validator, parser))
92-
.FirstOrDefault(value => value.IsValid, ConfigurationResult<IDictionary<string, string>>.NotFound());
250+
{
251+
// We iterate in reverse order, and keep the last successful value
252+
// because we need to record the data for all the sources in telemetry
253+
var result = ConfigurationResult<IDictionary<string, string>>.NotFound();
254+
var isLastFound = false;
255+
var origin = ConfigurationOrigins.Unknown;
256+
for (var i = _sources.Count - 1; i >= 0; i--)
257+
{
258+
var source = _sources[i];
259+
var value = source.GetDictionary(key, telemetry, validator, parser);
260+
if (value.IsValid)
261+
{
262+
result = value;
263+
isLastFound = true;
264+
origin = source.Origin;
265+
}
266+
else if (value.IsPresent)
267+
{
268+
isLastFound = false;
269+
}
270+
}
271+
272+
if (result.IsValid && !isLastFound)
273+
{
274+
telemetry.Record(key, result.TelemetryOverride ?? result.Result?.ToString(), recordValue: true, origin);
275+
}
276+
277+
return result;
278+
}
93279

94280
/// <inheritdoc />
95281
public ConfigurationResult<T> GetAs<T>(string key, IConfigurationTelemetry telemetry, Func<string, ParsingResult<T>> converter, Func<T, bool>? validator, bool recordValue)
96-
=> _sources
97-
.Select(source => source.GetAs<T>(key, telemetry, converter, validator, recordValue))
98-
.FirstOrDefault(value => value.IsValid, ConfigurationResult<T>.NotFound());
282+
{
283+
// We iterate in reverse order, and keep the last successful value
284+
// because we need to record the data for all the sources in telemetry
285+
var result = ConfigurationResult<T>.NotFound();
286+
var isLastFound = false;
287+
var origin = ConfigurationOrigins.Unknown;
288+
for (var i = _sources.Count - 1; i >= 0; i--)
289+
{
290+
var source = _sources[i];
291+
var value = source.GetAs(key, telemetry, converter, validator, recordValue);
292+
if (value.IsValid)
293+
{
294+
result = value;
295+
isLastFound = true;
296+
origin = source.Origin;
297+
}
298+
else if (value.IsPresent)
299+
{
300+
isLastFound = false;
301+
}
302+
}
303+
304+
if (result.IsValid && !isLastFound)
305+
{
306+
telemetry.Record(key, result.TelemetryOverride ?? result.Result?.ToString(), recordValue: true, origin);
307+
}
308+
309+
return result;
310+
}
99311
}
100312
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public DictionaryConfigurationSource(IReadOnlyDictionary<string, string> diction
1919
_dictionary = dictionary;
2020
}
2121

22-
internal override ConfigurationOrigins Origin => ConfigurationOrigins.Code;
22+
public override ConfigurationOrigins Origin => ConfigurationOrigins.Code;
2323

2424
protected override string? GetString(string key)
2525
{

0 commit comments

Comments
 (0)