Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds telemetry pipeline #259

Merged
merged 43 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
16 changes: 16 additions & 0 deletions Microsoft.FeatureManagement.sln
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TargetingConsoleApp", "exam
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RazorPages", "examples\RazorPages\RazorPages.csproj", "{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement.AppInsightsTelemetryPublisher", "src\Microsoft.FeatureManagement.AppInsightsTelemetryPublisher\Microsoft.FeatureManagement.AppInsightsTelemetryPublisher.csproj", "{F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -55,6 +57,18 @@ 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
{A7F4F8CE-50FD-4450-83F6-30384335000C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A7F4F8CE-50FD-4450-83F6-30384335000C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A7F4F8CE-50FD-4450-83F6-30384335000C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A7F4F8CE-50FD-4450-83F6-30384335000C}.Release|Any CPU.Build.0 = Release|Any CPU
{F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7CAA3C0-BBAE-4271-B08C-29C9A279DED1}.Release|Any CPU.Build.0 = Release|Any CPU
{9F21A42B-99B7-4AEB-A7E2-D90DB62A5050}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9F21A42B-99B7-4AEB-A7E2-D90DB62A5050}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F21A42B-99B7-4AEB-A7E2-D90DB62A5050}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F21A42B-99B7-4AEB-A7E2-D90DB62A5050}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -65,6 +79,8 @@ Global
{E50FB931-7A42-440E-AC47-B8DFE5E15394} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
{6558C21E-CF20-4278-AA08-EB9D1DF29D66} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
{BA29A1BB-81D5-4EB1-AF37-6ECF64AF27E2} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
{A7F4F8CE-50FD-4450-83F6-30384335000C} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
{9F21A42B-99B7-4AEB-A7E2-D90DB62A5050} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {84DA6C54-F140-4518-A1B4-E4CF42117FBD}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<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>
<LangVersion>8.0</LangVersion>
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved
</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,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using Microsoft.ApplicationInsights;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.FeatureManagement.Telemetry;

namespace Microsoft.FeatureManagement.AppInsightsTelemetryPublisher
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved
{
/// <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 AddFeatureManagementTelemetryPublisherAppInsights(this IServiceCollection services)
{
//
// Add required services
services.AddSingleton<ITelemetryPublisher>(serviceProvider =>
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved
new TelemetryPublisherAppInsights(serviceProvider.GetRequiredService<TelemetryClient>())
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved
);

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

namespace Microsoft.FeatureManagement.AppInsightsTelemetryPublisher
{
/// <summary>
/// Used to publish data from evaluation events to Application Insights
/// </summary>
public class TelemetryPublisherAppInsights : ITelemetryPublisher
{
private readonly string _eventName = "FeatureEvaluation";
private readonly TelemetryClient _telemetryClient;

public TelemetryPublisherAppInsights(TelemetryClient telemetryClient)
{
_telemetryClient = telemetryClient;
}

/// <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;

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

foreach (KeyValuePair<string, string> kvp in featureDefinition.Tags)
{
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved
properties["Tag." + kvp.Key] = kvp.Value;
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved
}

_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
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved
};
}

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 configuration settings.
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved
/// A <see cref="Label"/> is used together with a <see cref="Name"/> to uniquely identify a feature.
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public string Label { get; set; }
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// An ETag indicating the state of a feature within a configuration store.
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved
/// </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.
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public IReadOnlyDictionary<string,string> Tags { get; set; }
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// A flag to enable or disable sending telemetry events to the ITelemetryProvider implementation.
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public bool EnableTelemetry { get; set; }
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved
}
}
24 changes: 24 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 @@ private class ConfigurationCacheItem
_parametersCache = new MemoryCache(new MemoryCacheOptions());
}

public ITelemetryPublisher _telemetryPublisher { get; init; }
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved

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

if (featureDefinition.EnableTelemetry)
{
if (_telemetryPublisher == null)
{
string errorMessage = $"The feature declaration enabled telemetry but no instance of ITelemetryPublisher was initialized.";
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved

_logger.LogWarning(errorMessage);
}
else
{
EvaluationEvent evaluationEvent = new EvaluationEvent()
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved
{
FeatureDefinition = featureDefinition,
IsEnabled = enabled
};

await _telemetryPublisher.PublishEvent(evaluationEvent, 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
rossgrambo marked this conversation as resolved.
Show resolved Hide resolved

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);
}

rossgrambo marked this conversation as resolved.
Show resolved Hide resolved
}
Loading