Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1f0644d
Adds telemetry pipeline. Adds evaluationevent, publisher interface, a…
rossgrambo Sep 11, 2023
614e9af
Use await insead of .result
rossgrambo Sep 12, 2023
4db49a3
Reverting unused dependency removal
rossgrambo Sep 12, 2023
75cab57
Update src/Microsoft.FeatureManagement/FeatureDefinition.cs
rossgrambo Sep 12, 2023
f8e4f7b
Update src/Microsoft.FeatureManagement/FeatureDefinition.cs
rossgrambo Sep 12, 2023
7c77718
Update src/Microsoft.FeatureManagement.AppInsightsTelemetryPublisher/…
rossgrambo Sep 12, 2023
52930c0
Removed factory pattern for publisher DI
rossgrambo Sep 12, 2023
cb18586
Update src/Microsoft.FeatureManagement/FeatureManager.cs
rossgrambo Sep 13, 2023
121ca35
Resolve misc. comments
rossgrambo Sep 13, 2023
20b5f82
Merge branch 'rossgrambo/telemetry' of https://github.com/microsoft/F…
rossgrambo Sep 13, 2023
e025642
Adjusts descriptions for FeatureDefinition properties
rossgrambo Sep 13, 2023
8ad67b7
Rename AppInsights to ApplicationInsights
rossgrambo Sep 15, 2023
fce7274
Resolves project reference in sln
rossgrambo Sep 15, 2023
d23357c
Adjusts ServiceCollectionExtensions to use TryAddSingleton
rossgrambo Sep 15, 2023
e43e114
Removes langversion from .csproj
rossgrambo Sep 15, 2023
93a88a3
Temp
rossgrambo Sep 15, 2023
ba22ed1
Adds explicit null checkts before publishing fields to app insights
rossgrambo Sep 18, 2023
0b5bea4
Addressing misc. comments
rossgrambo Sep 18, 2023
2bfcbc8
Removing project reference for example project
rossgrambo Sep 18, 2023
6cf2ee6
Merge with preview
rossgrambo Sep 20, 2023
5b1842f
Syncing telemetry and variants
rossgrambo Sep 20, 2023
ecc898d
Adds null check for feature definitions
rossgrambo Sep 21, 2023
39dd606
Adjusted TelemetryPublishers to no longer be inserted into DI
rossgrambo Sep 29, 2023
9cf586a
Resolving comments
rossgrambo Sep 29, 2023
78d3ba2
Removing Application Insights project
rossgrambo Sep 29, 2023
ac3630c
Converts some variables to inline
rossgrambo Sep 30, 2023
0388134
Resolving misc. comments
rossgrambo Oct 2, 2023
2ff83ac
Moves AddTelemetryPublisher to be an extension method
rossgrambo Oct 2, 2023
4a5b135
Removing IVariantFeatureManager GetFeatureNamesAsync definition, as i…
rossgrambo Oct 3, 2023
b6e8df0
Remove unused dependency
rossgrambo Oct 3, 2023
f70513f
Resolving comments
rossgrambo Oct 3, 2023
bf151ac
Remove invisible character changes
rossgrambo Oct 4, 2023
edbb8d5
Remove Dependency Injection package
rossgrambo Oct 4, 2023
e94ad7f
Update src/Microsoft.FeatureManagement/FeatureManagerSnapshot.cs
rossgrambo Oct 4, 2023
8b0f3ba
Update src/Microsoft.FeatureManagement/IVariantFeatureManager.cs
rossgrambo Oct 4, 2023
5df39c8
Persists cancellation token to PublishEvent and adds null check for n…
rossgrambo Oct 4, 2023
982f313
Adds TargetingContext to the EvaluationResult
rossgrambo Oct 4, 2023
505d834
Resolving comments
rossgrambo Oct 5, 2023
fb055fc
Removes TargetingContext from EvaluationEvent for now
rossgrambo Oct 5, 2023
6c3a3ea
Merge branch 'preview' into rossgrambo/telemetry
rossgrambo Oct 5, 2023
7d00c6d
Moves tags, etag, and label under 'TelemetryMetadata'
rossgrambo Oct 6, 2023
9386f6a
Adjusts telemetry metadata to already be a flattened dictionary
rossgrambo Oct 6, 2023
8b8cb74
Removes bind in favor of ToDictionary. Removes unused using
rossgrambo Oct 9, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Microsoft.FeatureManagement.sln
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp", "examples\Cons
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TargetingConsoleApp", "examples\TargetingConsoleApp\TargetingConsoleApp.csproj", "{6558C21E-CF20-4278-AA08-EB9D1DF29D66}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RazorPages", "examples\RazorPages\RazorPages.csproj", "{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPages", "examples\RazorPages\RazorPages.csproj", "{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement.Telemetry.ApplicationInsights", "src\Microsoft.FeatureManagement.Telemetry.ApplicationInsights\Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj", "{0ADB5CFC-D3FA-48E5-910A-51770C8E608C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -55,6 +57,10 @@ Global
{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}.Release|Any CPU.Build.0 = Release|Any CPU
{0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0ADB5CFC-D3FA-48E5-910A-51770C8E608C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Comment thread
rossgrambo marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.21.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.FeatureManagement.AspNetCore\Microsoft.FeatureManagement.AspNetCore.csproj" />
<ProjectReference Include="..\..\src\Microsoft.FeatureManagement.Telemetry.ApplicationInsights\Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<SignAssembly>true</SignAssembly>
<DelaySign>false</DelaySign>
<AssemblyOriginatorKeyFile>..\..\build\Microsoft.FeatureManagement.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.21.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Microsoft.FeatureManagement\Microsoft.FeatureManagement.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Microsoft.FeatureManagement.Telemetry.ApplicationInsights
{
/// <summary>
/// Extensions used to add feature management publisher functionality.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds an event publisher that publishes feature evaluation events to Application Insights.
/// </summary>
/// <param name="services">The service collection that feature management services are added to.</param>
/// <returns>The <see cref="IServiceCollection"/> that was given as a parameter, with the publisher added.</returns>
public static IServiceCollection AddFeatureManagementTelemetryPublisherApplicationInsights(this IServiceCollection services)
Comment thread
rossgrambo marked this conversation as resolved.
Outdated
{
//
// Add required services
services.TryAddSingleton<ITelemetryPublisher, TelemetryPublisherApplicationInsights>();

return services;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using Microsoft.ApplicationInsights;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.FeatureManagement.Telemetry.ApplicationInsights
{
/// <summary>
/// Used to publish data from evaluation events to Application Insights
/// </summary>
public class TelemetryPublisherApplicationInsights : ITelemetryPublisher
Comment thread
rossgrambo marked this conversation as resolved.
Outdated
Comment thread
rossgrambo marked this conversation as resolved.
Outdated
{
private readonly string _eventName = "FeatureEvaluation";
private readonly TelemetryClient _telemetryClient;

public TelemetryPublisherApplicationInsights(TelemetryClient telemetryClient)
{
_telemetryClient = telemetryClient;
Comment thread
rossgrambo marked this conversation as resolved.
Outdated
}

/// <summary>
/// Publishes a custom event to Application Insights using data from the given evaluation event.
/// </summary>
/// <param name="evaluationEvent"> The event to publish.</param>
/// <param name="cancellationToken"> A cancellation token.</param>
/// <returns>Returns a ValueTask that represents the asynchronous operation</returns>
public ValueTask PublishEvent(EvaluationEvent evaluationEvent, CancellationToken cancellationToken)
{
FeatureDefinition featureDefinition = evaluationEvent.FeatureDefinition;
Comment thread
rossgrambo marked this conversation as resolved.
Outdated

Dictionary<string, string> properties = new Dictionary<string, string>()
{
{ "FeatureName", featureDefinition.Name },
{ "IsEnabled", evaluationEvent.IsEnabled.ToString() }
};

if (featureDefinition.ETag != null)
{
properties.Add("ETag", featureDefinition.ETag);
}

if (featureDefinition.Label != null)
{
properties.Add("Label", featureDefinition.Label);
}

if (featureDefinition.Tags != null)
{
foreach (KeyValuePair<string, string> kvp in featureDefinition.Tags)
{
properties["Tags." + kvp.Key] = kvp.Value;
}
}

_telemetryClient.TrackEvent(_eventName, properties);


return new ValueTask();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ We support
*/

RequirementType requirementType = RequirementType.Any;
string label = null;
string eTag = null;
bool enableTelemetry = false;
IReadOnlyDictionary<string, string> tags = null;

var enabledFor = new List<FeatureFilterConfiguration>();

Expand Down Expand Up @@ -167,7 +171,7 @@ We support
if (!string.IsNullOrEmpty(rawRequirementType) && !Enum.TryParse(rawRequirementType, ignoreCase: true, out requirementType))
{
throw new FeatureManagementException(
FeatureManagementError.InvalidConfigurationSetting,
FeatureManagementError.InvalidConfigurationSetting,
$"Invalid requirement type '{rawRequirementType}' for feature '{configurationSection.Key}'.");
}

Expand All @@ -187,13 +191,26 @@ We support
});
}
}

IConfigurationSection tagsSection = configurationSection.GetSection("Tags");
if (tagsSection.Exists()) {
tags = tagsSection.GetChildren().ToDictionary(s => s.Key, s => s.Value);
}

label = configurationSection["Label"];
eTag = configurationSection["ETag"];
enableTelemetry = configurationSection.GetValue<bool>("EnableTelemetry");
}

return new FeatureDefinition()
{
Name = configurationSection.Key,
EnabledFor = enabledFor,
RequirementType = requirementType
RequirementType = requirementType,
Label = label,
ETag = eTag,
EnableTelemetry = enableTelemetry,
Tags = tags
Comment thread
rossgrambo marked this conversation as resolved.
Outdated
};
}

Expand Down
22 changes: 22 additions & 0 deletions src/Microsoft.FeatureManagement/FeatureDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,27 @@ public class FeatureDefinition
/// The default value is <see cref="RequirementType.Any"/>.
/// </summary>
public RequirementType RequirementType { get; set; } = RequirementType.Any;

/// <summary>
/// A value used to group feature flags.
/// A <see cref="Label"/> is used together with a <see cref="Name"/> to uniquely identify a feature.
Comment thread
rossgrambo marked this conversation as resolved.
Outdated
/// </summary>
public string Label { get; set; }
Comment thread
rossgrambo marked this conversation as resolved.
Outdated

/// <summary>
/// An ETag indicating the state of a feature. This value is used to determine whether a feature has changed.
Comment thread
rossgrambo marked this conversation as resolved.
Outdated
/// </summary>
public string ETag { get; set; }

/// <summary>
/// A dictionary of tags used to assign additional properties to a feature.
/// These can be used to indicate how a feature may be applied.
Comment thread
rossgrambo marked this conversation as resolved.
Outdated
/// </summary>
public IReadOnlyDictionary<string, string> Tags { get; set; }

/// <summary>
/// A flag to enable or disable sending telemetry events to the registered <see cref="ITelemetryProvider">.
Comment thread
rossgrambo marked this conversation as resolved.
Outdated
/// </summary>
public bool EnableTelemetry { get; set; }
Comment thread
rossgrambo marked this conversation as resolved.
Outdated
}
}
22 changes: 22 additions & 0 deletions src/Microsoft.FeatureManagement/FeatureManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.FeatureManagement.Telemetry;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.FeatureManagement
Expand Down Expand Up @@ -54,6 +56,8 @@ public FeatureManager(
_parametersCache = new MemoryCache(new MemoryCacheOptions());
}

public ITelemetryPublisher TelemetryPublisher { get; init; }

public Task<bool> IsEnabledAsync(string feature)
{
return IsEnabledAsync<object>(feature, null, false);
Expand Down Expand Up @@ -214,6 +218,24 @@ await contextualFilter.EvaluateAsync(context, appContext).ConfigureAwait(false)
await sessionManager.SetAsync(feature, enabled).ConfigureAwait(false);
}

if (featureDefinition.EnableTelemetry)
{
if (TelemetryPublisher == null)
{
_logger.LogWarning("The feature declaration enabled telemetry but no telemetry publisher was registered.");
}
else
{
await TelemetryPublisher.PublishEvent(
new EvaluationEvent
{
FeatureDefinition = featureDefinition,
IsEnabled = enabled
},
CancellationToken.None);
}
}

return enabled;
}

Expand Down
21 changes: 21 additions & 0 deletions src/Microsoft.FeatureManagement/IsExternalInit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//

// The init accessor for properties is supported in C# 9.0 and later.
// This class is used to compile .NET frameworks that don't support C# 9.0 or later while still using the init accessor for a property.
// The code referenced for this file can be found here: https://github.com/dotnet/roslyn/issues/45510#issuecomment-725091019

#if NETSTANDARD2_0 || NETCOREAPP2_1 || NETCOREAPP3_1
Comment thread
rossgrambo marked this conversation as resolved.
Outdated

using System.ComponentModel;

namespace System.Runtime.CompilerServices
{
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class IsExternalInit
{
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<SignAssembly>true</SignAssembly>
<DelaySign>false</DelaySign>
<AssemblyOriginatorKeyFile>..\..\build\Microsoft.FeatureManagement.snk</AssemblyOriginatorKeyFile>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
</PropertyGroup>

<PropertyGroup>
Expand Down
15 changes: 14 additions & 1 deletion src/Microsoft.FeatureManagement/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.FeatureManagement.Telemetry;
using System;
using System.Collections.Generic;

namespace Microsoft.FeatureManagement
{
Expand All @@ -26,7 +30,16 @@ public static IFeatureManagementBuilder AddFeatureManagement(this IServiceCollec
// Add required services
services.TryAddSingleton<IFeatureDefinitionProvider, ConfigurationFeatureDefinitionProvider>();

services.AddSingleton<IFeatureManager, FeatureManager>();
services.TryAddSingleton<IFeatureManager>(sp =>
new FeatureManager(
sp.GetRequiredService<IFeatureDefinitionProvider>(),
sp.GetRequiredService<IEnumerable<IFeatureFilterMetadata>>(),
sp.GetRequiredService<IEnumerable<ISessionManager>>(),
sp.GetRequiredService<ILoggerFactory>(),
sp.GetRequiredService<IOptions<FeatureManagementOptions>>())
{
TelemetryPublisher = sp.GetService<ITelemetryPublisher>(),
});

services.AddSingleton<ISessionManager, EmptySessionManager>();

Expand Down
21 changes: 21 additions & 0 deletions src/Microsoft.FeatureManagement/Telemetry/EvaluationEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
namespace Microsoft.FeatureManagement.Telemetry
{
/// <summary>
/// An event representing the evaluation of a feature.
/// </summary>
public class EvaluationEvent
{
/// <summary>
/// The definition of the feature that was evaluated.
/// </summary>
public FeatureDefinition FeatureDefinition { get; set; }

/// <summary>
/// The enabled state of the feature after evaluation.
/// </summary>
public bool IsEnabled { get; set; }
}
}
23 changes: 23 additions & 0 deletions src/Microsoft.FeatureManagement/Telemetry/ITelemetryPublisher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.FeatureManagement.Telemetry
{
/// <summary>
/// A publisher of telemetry events.
/// </summary>
public interface ITelemetryPublisher
{
/// <summary>
/// Handles an EvaluationEvent and publishes it to the configured telemetry channel.
/// </summary>
/// <param name="evaluationEvent"> The event to publish.</param>
/// <param name="cancellationToken"> A cancellation token.</param>
/// <returns>ValueTask</returns>
public ValueTask PublishEvent(EvaluationEvent evaluationEvent, CancellationToken cancellationToken);
}

Comment thread
rossgrambo marked this conversation as resolved.
Outdated
}
Loading