Skip to content

Commit ec5415f

Browse files
authored
Merge pull request #495 from microsoft/rossgrambo-extra-telemetry-fields
Adds more detailed telemetry fields
2 parents 8603981 + 4cb6a17 commit ec5415f

File tree

5 files changed

+151
-47
lines changed

5 files changed

+151
-47
lines changed

src/Microsoft.FeatureManagement/FeatureManager.cs

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -373,56 +373,13 @@ private async ValueTask<EvaluationEvent> EvaluateFeature<TContext>(string featur
373373
Activity.Current != null &&
374374
Activity.Current.IsAllDataRequested)
375375
{
376-
AddEvaluationActivityEvent(evaluationEvent);
376+
FeatureEvaluationTelemetry.Publish(evaluationEvent, Logger);
377377
}
378378
}
379379

380380
return evaluationEvent;
381381
}
382382

383-
private void AddEvaluationActivityEvent(EvaluationEvent evaluationEvent)
384-
{
385-
Debug.Assert(evaluationEvent != null);
386-
Debug.Assert(evaluationEvent.FeatureDefinition != null);
387-
388-
var tags = new ActivityTagsCollection()
389-
{
390-
{ "FeatureName", evaluationEvent.FeatureDefinition.Name },
391-
{ "Enabled", evaluationEvent.Enabled },
392-
{ "VariantAssignmentReason", evaluationEvent.VariantAssignmentReason },
393-
{ "Version", ActivitySource.Version }
394-
};
395-
396-
if (!string.IsNullOrEmpty(evaluationEvent.TargetingContext?.UserId))
397-
{
398-
tags["TargetingId"] = evaluationEvent.TargetingContext.UserId;
399-
}
400-
401-
if (!string.IsNullOrEmpty(evaluationEvent.Variant?.Name))
402-
{
403-
tags["Variant"] = evaluationEvent.Variant.Name;
404-
}
405-
406-
if (evaluationEvent.FeatureDefinition.Telemetry.Metadata != null)
407-
{
408-
foreach (KeyValuePair<string, string> kvp in evaluationEvent.FeatureDefinition.Telemetry.Metadata)
409-
{
410-
if (tags.ContainsKey(kvp.Key))
411-
{
412-
Logger?.LogWarning($"{kvp.Key} from telemetry metadata will be ignored, as it would override an existing key.");
413-
414-
continue;
415-
}
416-
417-
tags[kvp.Key] = kvp.Value;
418-
}
419-
}
420-
421-
var activityEvent = new ActivityEvent("FeatureFlag", DateTimeOffset.UtcNow, tags);
422-
423-
Activity.Current.AddEvent(activityEvent);
424-
}
425-
426383
private async ValueTask<bool> IsEnabledAsync<TContext>(FeatureDefinition featureDefinition, TContext appContext, bool useAppContext, CancellationToken cancellationToken)
427384
{
428385
Debug.Assert(featureDefinition != null);
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using Microsoft.Extensions.Logging;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Diagnostics;
5+
using System.Linq;
6+
7+
namespace Microsoft.FeatureManagement.Telemetry
8+
{
9+
internal static class FeatureEvaluationTelemetry
10+
{
11+
private static readonly string EvaluationEventVersion = "1.0.0";
12+
13+
/// <summary>
14+
/// Handles an evaluation event by adding it as an activity event to the current Activity.
15+
/// </summary>
16+
/// <param name="evaluationEvent">The <see cref="EvaluationEvent"/> to publish as an <see cref="ActivityEvent"/></param>
17+
/// <param name="logger">Optional logger to log warnings to</param>
18+
public static void Publish(EvaluationEvent evaluationEvent, ILogger logger)
19+
{
20+
if (Activity.Current == null)
21+
{
22+
throw new InvalidOperationException("An Activity must be created before calling this method.");
23+
}
24+
25+
if (evaluationEvent == null)
26+
{
27+
throw new ArgumentNullException(nameof(evaluationEvent));
28+
}
29+
30+
if (evaluationEvent.FeatureDefinition == null)
31+
{
32+
throw new ArgumentNullException(nameof(evaluationEvent.FeatureDefinition));
33+
}
34+
35+
var tags = new ActivityTagsCollection()
36+
{
37+
{ "FeatureName", evaluationEvent.FeatureDefinition.Name },
38+
{ "Enabled", evaluationEvent.Enabled },
39+
{ "VariantAssignmentReason", evaluationEvent.VariantAssignmentReason },
40+
{ "Version", EvaluationEventVersion }
41+
};
42+
43+
if (!string.IsNullOrEmpty(evaluationEvent.TargetingContext?.UserId))
44+
{
45+
tags["TargetingId"] = evaluationEvent.TargetingContext.UserId;
46+
}
47+
48+
if (!string.IsNullOrEmpty(evaluationEvent.Variant?.Name))
49+
{
50+
tags["Variant"] = evaluationEvent.Variant.Name;
51+
}
52+
53+
if (evaluationEvent.FeatureDefinition.Telemetry.Metadata != null)
54+
{
55+
foreach (KeyValuePair<string, string> kvp in evaluationEvent.FeatureDefinition.Telemetry.Metadata)
56+
{
57+
if (tags.ContainsKey(kvp.Key))
58+
{
59+
logger?.LogWarning($"{kvp.Key} from telemetry metadata will be ignored, as it would override an existing key.");
60+
61+
continue;
62+
}
63+
64+
tags[kvp.Key] = kvp.Value;
65+
}
66+
}
67+
68+
// VariantAssignmentPercentage
69+
if (evaluationEvent.VariantAssignmentReason == VariantAssignmentReason.DefaultWhenEnabled)
70+
{
71+
// If the variant was assigned due to DefaultWhenEnabled, the percentage reflects the unallocated percentiles
72+
double allocatedPercentage = evaluationEvent.FeatureDefinition.Allocation?.Percentile?.Sum(p => p.To - p.From) ?? 0;
73+
74+
tags["VariantAssignmentPercentage"] = 100 - allocatedPercentage;
75+
}
76+
else if (evaluationEvent.VariantAssignmentReason == VariantAssignmentReason.Percentile)
77+
{
78+
// If the variant was assigned due to Percentile, the percentage is the sum of the allocated percentiles for the given variant
79+
if (evaluationEvent.FeatureDefinition.Allocation?.Percentile != null)
80+
{
81+
tags["VariantAssignmentPercentage"] = evaluationEvent.FeatureDefinition.Allocation.Percentile
82+
.Where(p => p.Variant == evaluationEvent.Variant?.Name)
83+
.Sum(p => p.To - p.From);
84+
}
85+
}
86+
87+
// DefaultWhenEnabled
88+
if (evaluationEvent.FeatureDefinition.Allocation?.DefaultWhenEnabled != null)
89+
{
90+
tags["DefaultWhenEnabled"] = evaluationEvent.FeatureDefinition.Allocation.DefaultWhenEnabled;
91+
}
92+
93+
var activityEvent = new ActivityEvent("FeatureFlag", DateTimeOffset.UtcNow, tags);
94+
95+
Activity.Current.AddEvent(activityEvent);
96+
}
97+
}
98+
}

tests/Tests.FeatureManagement/FeatureManagementTest.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,6 +1756,9 @@ public async Task TelemetryPublishing()
17561756
string label = evaluationEvent.Tags.FirstOrDefault(kvp => kvp.Key == "Label").Value?.ToString();
17571757
string firstTag = evaluationEvent.Tags.FirstOrDefault(kvp => kvp.Key == "Tags.Tag1").Value?.ToString();
17581758

1759+
string variantAssignmentPercentage = evaluationEvent.Tags.FirstOrDefault(kvp => kvp.Key == "VariantAssignmentPercentage").Value?.ToString();
1760+
string defaultWhenEnabled = evaluationEvent.Tags.FirstOrDefault(kvp => kvp.Key == "DefaultWhenEnabled").Value?.ToString();
1761+
17591762
// Test telemetry cases
17601763
switch (featureName)
17611764
{
@@ -1783,6 +1786,8 @@ public async Task TelemetryPublishing()
17831786
Assert.Equal("True", enabled);
17841787
Assert.Equal("Medium", variantName);
17851788
Assert.Equal(VariantAssignmentReason.DefaultWhenEnabled.ToString(), variantAssignmentReason);
1789+
Assert.Equal("100", variantAssignmentPercentage);
1790+
Assert.Equal("Medium", defaultWhenEnabled);
17861791
break;
17871792

17881793
case Features.VariantFeatureDefaultDisabled:
@@ -1791,6 +1796,8 @@ public async Task TelemetryPublishing()
17911796
Assert.Equal("False", enabled);
17921797
Assert.Equal("Small", variantName);
17931798
Assert.Equal(VariantAssignmentReason.DefaultWhenDisabled.ToString(), variantAssignmentReason);
1799+
Assert.Null(variantAssignmentPercentage);
1800+
Assert.Null(defaultWhenEnabled);
17941801
break;
17951802

17961803
case Features.VariantFeaturePercentileOn:
@@ -1813,41 +1820,62 @@ public async Task TelemetryPublishing()
18131820
currentTest = 0;
18141821
Assert.Null(variantName);
18151822
Assert.Equal(VariantAssignmentReason.DefaultWhenDisabled.ToString(), variantAssignmentReason);
1823+
Assert.Null(variantAssignmentPercentage);
1824+
Assert.Null(defaultWhenEnabled);
18161825
break;
18171826

18181827
case Features.VariantFeatureUser:
18191828
Assert.Equal(8, currentTest);
18201829
currentTest = 0;
18211830
Assert.Equal("Small", variantName);
18221831
Assert.Equal(VariantAssignmentReason.User.ToString(), variantAssignmentReason);
1832+
Assert.Null(variantAssignmentPercentage);
1833+
Assert.Null(defaultWhenEnabled);
18231834
break;
18241835

18251836
case Features.VariantFeatureGroup:
18261837
Assert.Equal(9, currentTest);
18271838
currentTest = 0;
18281839
Assert.Equal("Small", variantName);
18291840
Assert.Equal(VariantAssignmentReason.Group.ToString(), variantAssignmentReason);
1841+
Assert.Null(variantAssignmentPercentage);
1842+
Assert.Null(defaultWhenEnabled);
18301843
break;
18311844

18321845
case Features.VariantFeatureNoVariants:
18331846
Assert.Equal(10, currentTest);
18341847
currentTest = 0;
18351848
Assert.Null(variantName);
18361849
Assert.Equal(VariantAssignmentReason.None.ToString(), variantAssignmentReason);
1850+
Assert.Null(variantAssignmentPercentage);
1851+
Assert.Null(defaultWhenEnabled);
18371852
break;
18381853

18391854
case Features.VariantFeatureNoAllocation:
18401855
Assert.Equal(11, currentTest);
18411856
currentTest = 0;
18421857
Assert.Null(variantName);
18431858
Assert.Equal(VariantAssignmentReason.DefaultWhenEnabled.ToString(), variantAssignmentReason);
1859+
Assert.Equal("100", variantAssignmentPercentage);
1860+
Assert.Null(defaultWhenEnabled);
18441861
break;
18451862

18461863
case Features.VariantFeatureAlwaysOffNoAllocation:
18471864
Assert.Equal(12, currentTest);
18481865
currentTest = 0;
18491866
Assert.Null(variantName);
18501867
Assert.Equal(VariantAssignmentReason.DefaultWhenDisabled.ToString(), variantAssignmentReason);
1868+
Assert.Null(variantAssignmentPercentage);
1869+
Assert.Null(defaultWhenEnabled);
1870+
break;
1871+
1872+
case Features.VariantFeatureIncorrectDefaultWhenEnabled:
1873+
Assert.Equal(13, currentTest);
1874+
currentTest = 0;
1875+
Assert.Null(variantName);
1876+
Assert.Equal(VariantAssignmentReason.DefaultWhenEnabled.ToString(), variantAssignmentReason);
1877+
Assert.Equal("100", variantAssignmentPercentage);
1878+
Assert.Equal("Foo", defaultWhenEnabled);
18511879
break;
18521880

18531881
default:
@@ -1912,6 +1940,10 @@ public async Task TelemetryPublishing()
19121940
await featureManager.GetVariantAsync(Features.VariantFeatureAlwaysOffNoAllocation, cancellationToken);
19131941
Assert.Equal(0, currentTest);
19141942

1943+
currentTest = 13;
1944+
await featureManager.GetVariantAsync(Features.VariantFeatureIncorrectDefaultWhenEnabled, cancellationToken);
1945+
Assert.Equal(0, currentTest);
1946+
19151947
// Test a feature with telemetry disabled- should throw if the listener hits it
19161948
bool result = await featureManager.IsEnabledAsync(Features.OnTestFeature, cancellationToken);
19171949

tests/Tests.FeatureManagement/Features.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ static class Features
2323
public const string VariantFeatureGroup = "VariantFeatureGroup";
2424
public const string VariantFeatureNoVariants = "VariantFeatureNoVariants";
2525
public const string VariantFeatureNoAllocation = "VariantFeatureNoAllocation";
26+
public const string VariantFeatureIncorrectDefaultWhenEnabled = "VariantFeatureIncorrectDefaultWhenEnabled";
2627
public const string VariantFeatureAlwaysOffNoAllocation = "VariantFeatureAlwaysOffNoAllocation";
2728
public const string VariantFeatureInvalidStatusOverride = "VariantFeatureInvalidStatusOverride";
2829
public const string VariantFeatureInvalidFromTo = "VariantFeatureInvalidFromTo";

tests/Tests.FeatureManagement/appsettings.json

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@
209209
"to": 50
210210
}
211211
],
212-
"seed": 1234
212+
"seed": "1234"
213213
},
214214
"telemetry": {
215215
"enabled": true
@@ -231,7 +231,7 @@
231231
"to": 50
232232
}
233233
],
234-
"seed": 12345
234+
"seed": "12345"
235235
},
236236
"telemetry": {
237237
"enabled": true
@@ -253,7 +253,7 @@
253253
"to": 100
254254
}
255255
],
256-
"seed": 12345
256+
"seed": "12345"
257257
},
258258
"telemetry": {
259259
"enabled": true
@@ -383,6 +383,22 @@
383383
"enabled": true
384384
}
385385
},
386+
{
387+
"id": "VariantFeatureIncorrectDefaultWhenEnabled",
388+
"enabled": true,
389+
"variants": [
390+
{
391+
"name": "Small",
392+
"configuration_value": "300px"
393+
}
394+
],
395+
"allocation": {
396+
"default_when_enabled": "Foo"
397+
},
398+
"telemetry": {
399+
"enabled": true
400+
}
401+
},
386402
{
387403
"id": "VariantFeatureAlwaysOffNoAllocation",
388404
"enabled": false,

0 commit comments

Comments
 (0)