Skip to content

Commit b5fd2d0

Browse files
zhiyuanliang-msrossgrambosamsadsam
authored
Merge preview to main (#539)
* Working allocationId * Adds Base64Url byte extension, adjusts TelemetryEventHandler logic, adjusts and adds tests. * Update comments * Removed allocation id and fixed some tests * Removed bytes extension * Update src/Microsoft.FeatureManagement/Telemetry/TelemetryEventHandler.cs Co-authored-by: Sami Sadfa <71456174+samsadsam@users.noreply.github.com> * Resolving comments * Fix formatting * Version Bump --------- Co-authored-by: Ross Grambo <rossgrambo@microsoft.com> Co-authored-by: Sami Sadfa <71456174+samsadsam@users.noreply.github.com>
2 parents 58ae781 + 77fee5d commit b5fd2d0

8 files changed

Lines changed: 154 additions & 48 deletions

File tree

src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<MajorVersion>4</MajorVersion>
88
<MinorVersion>0</MinorVersion>
99
<PatchVersion>0</PatchVersion>
10+
<PreviewVersion>-preview5</PreviewVersion>
1011
</PropertyGroup>
1112

1213
<Import Project="..\..\build\Versioning.props" />

src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<MajorVersion>4</MajorVersion>
77
<MinorVersion>0</MinorVersion>
88
<PatchVersion>0</PatchVersion>
9+
<PreviewVersion>-preview5</PreviewVersion>
910
</PropertyGroup>
1011

1112
<Import Project="..\..\build\Versioning.props" />

src/Microsoft.FeatureManagement/FeatureManager.cs

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -368,57 +368,13 @@ private async ValueTask<EvaluationEvent> EvaluateFeature<TContext>(string featur
368368
Activity.Current != null &&
369369
Activity.Current.IsAllDataRequested)
370370
{
371-
AddEvaluationActivityEvent(evaluationEvent);
371+
FeatureEvaluationTelemetry.Publish(evaluationEvent, Logger);
372372
}
373373
}
374374

375375
return evaluationEvent;
376376
}
377377

378-
private void AddEvaluationActivityEvent(EvaluationEvent evaluationEvent)
379-
{
380-
Debug.Assert(evaluationEvent != null);
381-
Debug.Assert(evaluationEvent.FeatureDefinition != null);
382-
383-
// FeatureEvaluation event schema: https://github.com/microsoft/FeatureManagement/blob/main/Schema/FeatureEvaluationEvent/FeatureEvaluationEvent.v1.0.0.schema.json
384-
var tags = new ActivityTagsCollection()
385-
{
386-
{ "FeatureName", evaluationEvent.FeatureDefinition.Name },
387-
{ "Enabled", evaluationEvent.Enabled },
388-
{ "VariantAssignmentReason", evaluationEvent.VariantAssignmentReason },
389-
{ "Version", ActivitySource.Version }
390-
};
391-
392-
if (!string.IsNullOrEmpty(evaluationEvent.TargetingContext?.UserId))
393-
{
394-
tags["TargetingId"] = evaluationEvent.TargetingContext.UserId;
395-
}
396-
397-
if (!string.IsNullOrEmpty(evaluationEvent.Variant?.Name))
398-
{
399-
tags["Variant"] = evaluationEvent.Variant.Name;
400-
}
401-
402-
if (evaluationEvent.FeatureDefinition.Telemetry.Metadata != null)
403-
{
404-
foreach (KeyValuePair<string, string> kvp in evaluationEvent.FeatureDefinition.Telemetry.Metadata)
405-
{
406-
if (tags.ContainsKey(kvp.Key))
407-
{
408-
Logger?.LogWarning("{key} from telemetry metadata will be ignored, as it would override an existing key.", kvp.Key);
409-
410-
continue;
411-
}
412-
413-
tags[kvp.Key] = kvp.Value;
414-
}
415-
}
416-
417-
var activityEvent = new ActivityEvent("FeatureFlag", DateTimeOffset.UtcNow, tags);
418-
419-
Activity.Current.AddEvent(activityEvent);
420-
}
421-
422378
private async ValueTask<bool> IsEnabledAsync<TContext>(FeatureDefinition featureDefinition, TContext appContext, bool useAppContext, CancellationToken cancellationToken)
423379
{
424380
Debug.Assert(featureDefinition != null);

src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<MajorVersion>4</MajorVersion>
88
<MinorVersion>0</MinorVersion>
99
<PatchVersion>0</PatchVersion>
10+
<PreviewVersion>-preview5</PreviewVersion>
1011
</PropertyGroup>
1112

1213
<Import Project="..\..\build\Versioning.props" />
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
@@ -1706,6 +1706,9 @@ public async Task TelemetryPublishing()
17061706
string label = evaluationEvent.Tags.FirstOrDefault(kvp => kvp.Key == "Label").Value?.ToString();
17071707
string firstTag = evaluationEvent.Tags.FirstOrDefault(kvp => kvp.Key == "Tags.Tag1").Value?.ToString();
17081708

1709+
string variantAssignmentPercentage = evaluationEvent.Tags.FirstOrDefault(kvp => kvp.Key == "VariantAssignmentPercentage").Value?.ToString();
1710+
string defaultWhenEnabled = evaluationEvent.Tags.FirstOrDefault(kvp => kvp.Key == "DefaultWhenEnabled").Value?.ToString();
1711+
17091712
// Test telemetry cases
17101713
switch (featureName)
17111714
{
@@ -1733,6 +1736,8 @@ public async Task TelemetryPublishing()
17331736
Assert.Equal("True", enabled);
17341737
Assert.Equal("Medium", variantName);
17351738
Assert.Equal(VariantAssignmentReason.DefaultWhenEnabled.ToString(), variantAssignmentReason);
1739+
Assert.Equal("100", variantAssignmentPercentage);
1740+
Assert.Equal("Medium", defaultWhenEnabled);
17361741
break;
17371742

17381743
case Features.VariantFeatureDefaultDisabled:
@@ -1741,6 +1746,8 @@ public async Task TelemetryPublishing()
17411746
Assert.Equal("False", enabled);
17421747
Assert.Equal("Small", variantName);
17431748
Assert.Equal(VariantAssignmentReason.DefaultWhenDisabled.ToString(), variantAssignmentReason);
1749+
Assert.Null(variantAssignmentPercentage);
1750+
Assert.Null(defaultWhenEnabled);
17441751
break;
17451752

17461753
case Features.VariantFeaturePercentileOn:
@@ -1763,41 +1770,62 @@ public async Task TelemetryPublishing()
17631770
currentTest = 0;
17641771
Assert.Null(variantName);
17651772
Assert.Equal(VariantAssignmentReason.DefaultWhenDisabled.ToString(), variantAssignmentReason);
1773+
Assert.Null(variantAssignmentPercentage);
1774+
Assert.Null(defaultWhenEnabled);
17661775
break;
17671776

17681777
case Features.VariantFeatureUser:
17691778
Assert.Equal(8, currentTest);
17701779
currentTest = 0;
17711780
Assert.Equal("Small", variantName);
17721781
Assert.Equal(VariantAssignmentReason.User.ToString(), variantAssignmentReason);
1782+
Assert.Null(variantAssignmentPercentage);
1783+
Assert.Null(defaultWhenEnabled);
17731784
break;
17741785

17751786
case Features.VariantFeatureGroup:
17761787
Assert.Equal(9, currentTest);
17771788
currentTest = 0;
17781789
Assert.Equal("Small", variantName);
17791790
Assert.Equal(VariantAssignmentReason.Group.ToString(), variantAssignmentReason);
1791+
Assert.Null(variantAssignmentPercentage);
1792+
Assert.Null(defaultWhenEnabled);
17801793
break;
17811794

17821795
case Features.VariantFeatureNoVariants:
17831796
Assert.Equal(10, currentTest);
17841797
currentTest = 0;
17851798
Assert.Null(variantName);
17861799
Assert.Equal(VariantAssignmentReason.None.ToString(), variantAssignmentReason);
1800+
Assert.Null(variantAssignmentPercentage);
1801+
Assert.Null(defaultWhenEnabled);
17871802
break;
17881803

17891804
case Features.VariantFeatureNoAllocation:
17901805
Assert.Equal(11, currentTest);
17911806
currentTest = 0;
17921807
Assert.Null(variantName);
17931808
Assert.Equal(VariantAssignmentReason.DefaultWhenEnabled.ToString(), variantAssignmentReason);
1809+
Assert.Equal("100", variantAssignmentPercentage);
1810+
Assert.Null(defaultWhenEnabled);
17941811
break;
17951812

17961813
case Features.VariantFeatureAlwaysOffNoAllocation:
17971814
Assert.Equal(12, currentTest);
17981815
currentTest = 0;
17991816
Assert.Null(variantName);
18001817
Assert.Equal(VariantAssignmentReason.DefaultWhenDisabled.ToString(), variantAssignmentReason);
1818+
Assert.Null(variantAssignmentPercentage);
1819+
Assert.Null(defaultWhenEnabled);
1820+
break;
1821+
1822+
case Features.VariantFeatureIncorrectDefaultWhenEnabled:
1823+
Assert.Equal(13, currentTest);
1824+
currentTest = 0;
1825+
Assert.Null(variantName);
1826+
Assert.Equal(VariantAssignmentReason.DefaultWhenEnabled.ToString(), variantAssignmentReason);
1827+
Assert.Equal("100", variantAssignmentPercentage);
1828+
Assert.Equal("Foo", defaultWhenEnabled);
18011829
break;
18021830

18031831
default:
@@ -1862,6 +1890,10 @@ public async Task TelemetryPublishing()
18621890
await featureManager.GetVariantAsync(Features.VariantFeatureAlwaysOffNoAllocation, cancellationToken);
18631891
Assert.Equal(0, currentTest);
18641892

1893+
currentTest = 13;
1894+
await featureManager.GetVariantAsync(Features.VariantFeatureIncorrectDefaultWhenEnabled, cancellationToken);
1895+
Assert.Equal(0, currentTest);
1896+
18651897
// Test a feature with telemetry disabled- should throw if the listener hits it
18661898
bool result = await featureManager.IsEnabledAsync(Features.OnTestFeature, cancellationToken);
18671899

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)