Skip to content

Commit f735091

Browse files
Merge branch 'main' of https://github.com/microsoft/FeatureManagement-Dotnet into zhiyuanliang/merge-ff-source
2 parents 1d3897b + 139296d commit f735091

File tree

8 files changed

+154
-51
lines changed

8 files changed

+154
-51
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<!-- Official Version -->
66
<PropertyGroup>
77
<MajorVersion>4</MajorVersion>
8-
<MinorVersion>0</MinorVersion>
8+
<MinorVersion>1</MinorVersion>
99
<PatchVersion>0</PatchVersion>
1010
</PropertyGroup>
1111

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<!-- Official Version -->
55
<PropertyGroup>
66
<MajorVersion>4</MajorVersion>
7-
<MinorVersion>0</MinorVersion>
7+
<MinorVersion>1</MinorVersion>
88
<PatchVersion>0</PatchVersion>
99
</PropertyGroup>
1010

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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<!-- Official Version -->
66
<PropertyGroup>
77
<MajorVersion>4</MajorVersion>
8-
<MinorVersion>0</MinorVersion>
8+
<MinorVersion>1</MinorVersion>
99
<PatchVersion>0</PatchVersion>
1010
</PropertyGroup>
1111

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
@@ -1750,6 +1750,9 @@ public async Task TelemetryPublishing()
17501750
string label = evaluationEvent.Tags.FirstOrDefault(kvp => kvp.Key == "Label").Value?.ToString();
17511751
string firstTag = evaluationEvent.Tags.FirstOrDefault(kvp => kvp.Key == "Tags.Tag1").Value?.ToString();
17521752

1753+
string variantAssignmentPercentage = evaluationEvent.Tags.FirstOrDefault(kvp => kvp.Key == "VariantAssignmentPercentage").Value?.ToString();
1754+
string defaultWhenEnabled = evaluationEvent.Tags.FirstOrDefault(kvp => kvp.Key == "DefaultWhenEnabled").Value?.ToString();
1755+
17531756
// Test telemetry cases
17541757
switch (featureName)
17551758
{
@@ -1777,6 +1780,8 @@ public async Task TelemetryPublishing()
17771780
Assert.Equal("True", enabled);
17781781
Assert.Equal("Medium", variantName);
17791782
Assert.Equal(VariantAssignmentReason.DefaultWhenEnabled.ToString(), variantAssignmentReason);
1783+
Assert.Equal("100", variantAssignmentPercentage);
1784+
Assert.Equal("Medium", defaultWhenEnabled);
17801785
break;
17811786

17821787
case Features.VariantFeatureDefaultDisabled:
@@ -1785,6 +1790,8 @@ public async Task TelemetryPublishing()
17851790
Assert.Equal("False", enabled);
17861791
Assert.Equal("Small", variantName);
17871792
Assert.Equal(VariantAssignmentReason.DefaultWhenDisabled.ToString(), variantAssignmentReason);
1793+
Assert.Null(variantAssignmentPercentage);
1794+
Assert.Null(defaultWhenEnabled);
17881795
break;
17891796

17901797
case Features.VariantFeaturePercentileOn:
@@ -1807,41 +1814,62 @@ public async Task TelemetryPublishing()
18071814
currentTest = 0;
18081815
Assert.Null(variantName);
18091816
Assert.Equal(VariantAssignmentReason.DefaultWhenDisabled.ToString(), variantAssignmentReason);
1817+
Assert.Null(variantAssignmentPercentage);
1818+
Assert.Null(defaultWhenEnabled);
18101819
break;
18111820

18121821
case Features.VariantFeatureUser:
18131822
Assert.Equal(8, currentTest);
18141823
currentTest = 0;
18151824
Assert.Equal("Small", variantName);
18161825
Assert.Equal(VariantAssignmentReason.User.ToString(), variantAssignmentReason);
1826+
Assert.Null(variantAssignmentPercentage);
1827+
Assert.Null(defaultWhenEnabled);
18171828
break;
18181829

18191830
case Features.VariantFeatureGroup:
18201831
Assert.Equal(9, currentTest);
18211832
currentTest = 0;
18221833
Assert.Equal("Small", variantName);
18231834
Assert.Equal(VariantAssignmentReason.Group.ToString(), variantAssignmentReason);
1835+
Assert.Null(variantAssignmentPercentage);
1836+
Assert.Null(defaultWhenEnabled);
18241837
break;
18251838

18261839
case Features.VariantFeatureNoVariants:
18271840
Assert.Equal(10, currentTest);
18281841
currentTest = 0;
18291842
Assert.Null(variantName);
18301843
Assert.Equal(VariantAssignmentReason.None.ToString(), variantAssignmentReason);
1844+
Assert.Null(variantAssignmentPercentage);
1845+
Assert.Null(defaultWhenEnabled);
18311846
break;
18321847

18331848
case Features.VariantFeatureNoAllocation:
18341849
Assert.Equal(11, currentTest);
18351850
currentTest = 0;
18361851
Assert.Null(variantName);
18371852
Assert.Equal(VariantAssignmentReason.DefaultWhenEnabled.ToString(), variantAssignmentReason);
1853+
Assert.Equal("100", variantAssignmentPercentage);
1854+
Assert.Null(defaultWhenEnabled);
18381855
break;
18391856

18401857
case Features.VariantFeatureAlwaysOffNoAllocation:
18411858
Assert.Equal(12, currentTest);
18421859
currentTest = 0;
18431860
Assert.Null(variantName);
18441861
Assert.Equal(VariantAssignmentReason.DefaultWhenDisabled.ToString(), variantAssignmentReason);
1862+
Assert.Null(variantAssignmentPercentage);
1863+
Assert.Null(defaultWhenEnabled);
1864+
break;
1865+
1866+
case Features.VariantFeatureIncorrectDefaultWhenEnabled:
1867+
Assert.Equal(13, currentTest);
1868+
currentTest = 0;
1869+
Assert.Null(variantName);
1870+
Assert.Equal(VariantAssignmentReason.DefaultWhenEnabled.ToString(), variantAssignmentReason);
1871+
Assert.Equal("100", variantAssignmentPercentage);
1872+
Assert.Equal("Foo", defaultWhenEnabled);
18451873
break;
18461874

18471875
default:
@@ -1906,6 +1934,10 @@ public async Task TelemetryPublishing()
19061934
await featureManager.GetVariantAsync(Features.VariantFeatureAlwaysOffNoAllocation, cancellationToken);
19071935
Assert.Equal(0, currentTest);
19081936

1937+
currentTest = 13;
1938+
await featureManager.GetVariantAsync(Features.VariantFeatureIncorrectDefaultWhenEnabled, cancellationToken);
1939+
Assert.Equal(0, currentTest);
1940+
19091941
// Test a feature with telemetry disabled- should throw if the listener hits it
19101942
bool result = await featureManager.IsEnabledAsync(Features.OnTestFeature, cancellationToken);
19111943

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)