From 643a06df2907295bfbba9657d3172ca1a27ffc6a Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Thu, 11 Jan 2024 16:10:14 -0800 Subject: [PATCH 1/4] Adds targeting middleware and targeting initializer --- Microsoft.FeatureManagement.sln | 6 ++ .../TargetingHttpContextMiddleware.cs | 54 ++++++++++++++++ ...etry.ApplicationInsights.AspNetCore.csproj | 51 +++++++++++++++ .../TargetingTelemetryInitializer.cs | 62 +++++++++++++++++++ 4 files changed, 173 insertions(+) create mode 100644 src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs create mode 100644 src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore.csproj create mode 100644 src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs diff --git a/Microsoft.FeatureManagement.sln b/Microsoft.FeatureManagement.sln index 8963b77b..6d7e1f18 100644 --- a/Microsoft.FeatureManagement.sln +++ b/Microsoft.FeatureManagement.sln @@ -27,6 +27,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EvaluationDataToApplicationInsights", "examples\EvaluationDataToApplicationInsights\EvaluationDataToApplicationInsights.csproj", "{1502529E-47E9-4306-98C4-BF6CF7C7C275}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore", "src\Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore\Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore.csproj", "{C647611B-A8E5-4AD7-9DBA-60DDE276644B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -73,6 +75,10 @@ Global {1502529E-47E9-4306-98C4-BF6CF7C7C275}.Debug|Any CPU.Build.0 = Debug|Any CPU {1502529E-47E9-4306-98C4-BF6CF7C7C275}.Release|Any CPU.ActiveCfg = Release|Any CPU {1502529E-47E9-4306-98C4-BF6CF7C7C275}.Release|Any CPU.Build.0 = Release|Any CPU + {C647611B-A8E5-4AD7-9DBA-60DDE276644B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C647611B-A8E5-4AD7-9DBA-60DDE276644B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C647611B-A8E5-4AD7-9DBA-60DDE276644B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C647611B-A8E5-4AD7-9DBA-60DDE276644B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs b/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs new file mode 100644 index 00000000..7588d090 --- /dev/null +++ b/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// + +using Microsoft.AspNetCore.Http; +using Microsoft.FeatureManagement.FeatureFilters; +using System; +using System.Threading.Tasks; + +namespace EvaluationDataToApplicationInsights.Telemetry +{ + /// + /// Used to add targeting information to http context. This allows synronous code to access targeting information. + /// + public class TargetingHttpContextMiddleware + { + private readonly RequestDelegate _next; + + /// + /// Creates an instance of the TargetingHttpContextMiddleware + /// + public TargetingHttpContextMiddleware(RequestDelegate next) { + _next = next; + } + + /// + /// Adds targeting information to the http context. + /// + /// The to add the targeting information to. + /// The to retrieve the targeting information from. + /// Thrown if the provided context or targetingContextAccessor is null. + public async Task InvokeAsync(HttpContext context, ITargetingContextAccessor targetingContextAccessor) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (targetingContextAccessor == null) + { + throw new ArgumentNullException(nameof(targetingContextAccessor)); + } + + TargetingContext targetingContext = await targetingContextAccessor.GetContextAsync().ConfigureAwait(false); + + if (targetingContext != null) + { + context.Items["TargetingId"] = targetingContext.UserId; + } + + await _next(context); + } + } +} diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore.csproj b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore.csproj new file mode 100644 index 00000000..2d8e6d4f --- /dev/null +++ b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore.csproj @@ -0,0 +1,51 @@ + + + + + + 4 + 0 + 0 + -preview + + + + + + net6.0 + enable + true + false + ..\..\build\Microsoft.FeatureManagement.snk + + + + Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore provides a solution for tagging Application Insights telemetry with Microsoft.FeatureManagement targeting information. + Microsoft + Microsoft + https://licenses.nuget.org/MIT + https://github.com/Azure/AppConfiguration + Release notes can be found at https://aka.ms/MicrosoftFeatureManagementReleaseNotes + Microsoft FeatureManagement FeatureFlags ApplicationInsights + https://aka.ms/AzureAppConfigurationPackageIcon + © Microsoft Corporation. All rights reserved. + + + + + + + + + + + + + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\XMLComments\$(MSBuildProjectName).xml + + + + + + + diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs new file mode 100644 index 00000000..95c328c5 --- /dev/null +++ b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// + +using Microsoft.ApplicationInsights.AspNetCore.TelemetryInitializers; +using Microsoft.ApplicationInsights.Channel; +using Microsoft.ApplicationInsights.DataContracts; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore +{ + /// + /// Used to add targeting information to outgoing Application Insights telemetry. + /// + public class TargetingTelemetryInitializer : TelemetryInitializerBase + { + /// + /// Creates an instance of the TargetingTelemetryInitializer + /// + public TargetingTelemetryInitializer(IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor) + { + } + + /// + /// When telemetry is initialized, adds targeting information to all relevant telemetry. + /// + /// The to get the targeting information from. + /// The relevant to the telemetry. + /// The to be initialized. + /// Thrown if the any param is null. + protected override void OnInitializeTelemetry(HttpContext httpContext, RequestTelemetry requestTelemetry, ITelemetry telemetry) + { + if (telemetry == null) + { + throw new ArgumentNullException("telemetry"); + } + + if (requestTelemetry == null) + { + throw new ArgumentNullException("requestTelemetry"); + } + + if (httpContext == null) + { + throw new ArgumentNullException("httpContext"); + } + + string targetingId = (string) httpContext.Items["TargetingId"]; + + if (!string.IsNullOrEmpty(targetingId)) + { + requestTelemetry.Properties["TargetingId"] = targetingId; + + // Telemetry.Properties is deprecated in favor of ISupportProperties + if (telemetry is ISupportProperties telemetryWithSupportProperties) + { + telemetryWithSupportProperties.Properties["TargetingId"] = targetingId; + } + } + } + } +} From 393d6a92a5d4ba8593b6035bbfbd35cdcd328ff8 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Tue, 16 Jan 2024 15:49:58 -0800 Subject: [PATCH 2/4] Added logger and added prefix to targetingId --- .../TargetingHttpContextMiddleware.cs | 15 ++++++++++++--- .../TargetingTelemetryInitializer.cs | 5 ++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs b/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs index 7588d090..f06103a4 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs @@ -3,6 +3,7 @@ // using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; using Microsoft.FeatureManagement.FeatureFilters; using System; using System.Threading.Tasks; @@ -15,12 +16,16 @@ namespace EvaluationDataToApplicationInsights.Telemetry public class TargetingHttpContextMiddleware { private readonly RequestDelegate _next; + private readonly ILogger _logger; + + private const string TargetingIdKey = $"Microsoft.FeatureManagement.TargetingId"; /// /// Creates an instance of the TargetingHttpContextMiddleware /// - public TargetingHttpContextMiddleware(RequestDelegate next) { - _next = next; + public TargetingHttpContextMiddleware(RequestDelegate next, ILoggerFactory loggerFactory) { + _next = next; + _logger = loggerFactory?.CreateLogger() ?? throw new ArgumentNullException(nameof(loggerFactory)); } /// @@ -45,7 +50,11 @@ public async Task InvokeAsync(HttpContext context, ITargetingContextAccessor tar if (targetingContext != null) { - context.Items["TargetingId"] = targetingContext.UserId; + context.Items[TargetingIdKey] = targetingContext.UserId; + } + else + { + _logger.LogDebug("The targeting context accessor returned a null TargetingContext"); } await _next(context); diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs index 95c328c5..888d922c 100644 --- a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs +++ b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs @@ -14,6 +14,8 @@ namespace Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore /// public class TargetingTelemetryInitializer : TelemetryInitializerBase { + private const string TargetingIdKey = $"Microsoft.FeatureManagement.TargetingId"; + /// /// Creates an instance of the TargetingTelemetryInitializer /// @@ -45,7 +47,8 @@ protected override void OnInitializeTelemetry(HttpContext httpContext, RequestTe throw new ArgumentNullException("httpContext"); } - string targetingId = (string) httpContext.Items["TargetingId"]; + // Extract the targeting id from the http context + string targetingId = httpContext.Items[TargetingIdKey]?.ToString(); if (!string.IsNullOrEmpty(targetingId)) { From 58e07d0481bf6fbfe14892f5c3c4b91872c5edc6 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Wed, 17 Jan 2024 13:53:20 -0800 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Jimmy Campbell --- .../TargetingHttpContextMiddleware.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs b/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs index f06103a4..a77bc686 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs @@ -11,7 +11,7 @@ namespace EvaluationDataToApplicationInsights.Telemetry { /// - /// Used to add targeting information to http context. This allows synronous code to access targeting information. + /// Used to add targeting information to HTTP context. /// public class TargetingHttpContextMiddleware { @@ -29,7 +29,7 @@ public TargetingHttpContextMiddleware(RequestDelegate next, ILoggerFactory logge } /// - /// Adds targeting information to the http context. + /// Adds targeting information to the HTTP context. /// /// The to add the targeting information to. /// The to retrieve the targeting information from. From 80b3ebe489ec0fe01baa4276ab1b0a800e50b8c9 Mon Sep 17 00:00:00 2001 From: Ross Grambo Date: Wed, 17 Jan 2024 15:41:39 -0800 Subject: [PATCH 4/4] Adjustments from comments --- .../TargetingHttpContextMiddleware.cs | 4 ++-- .../TargetingTelemetryInitializer.cs | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs b/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs index a77bc686..8dd2378f 100644 --- a/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs +++ b/src/Microsoft.FeatureManagement.AspNetCore/TargetingHttpContextMiddleware.cs @@ -8,7 +8,7 @@ using System; using System.Threading.Tasks; -namespace EvaluationDataToApplicationInsights.Telemetry +namespace Microsoft.FeatureManagement { /// /// Used to add targeting information to HTTP context. @@ -24,7 +24,7 @@ public class TargetingHttpContextMiddleware /// Creates an instance of the TargetingHttpContextMiddleware /// public TargetingHttpContextMiddleware(RequestDelegate next, ILoggerFactory loggerFactory) { - _next = next; + _next = next ?? throw new ArgumentNullException(nameof(next)); _logger = loggerFactory?.CreateLogger() ?? throw new ArgumentNullException(nameof(loggerFactory)); } diff --git a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs index 888d922c..5614a3f3 100644 --- a/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs +++ b/src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.AspNetCore/TargetingTelemetryInitializer.cs @@ -37,23 +37,21 @@ protected override void OnInitializeTelemetry(HttpContext httpContext, RequestTe throw new ArgumentNullException("telemetry"); } - if (requestTelemetry == null) - { - throw new ArgumentNullException("requestTelemetry"); - } - if (httpContext == null) { throw new ArgumentNullException("httpContext"); } // Extract the targeting id from the http context - string targetingId = httpContext.Items[TargetingIdKey]?.ToString(); + string targetingId = null; - if (!string.IsNullOrEmpty(targetingId)) + if (httpContext.Items.TryGetValue(TargetingIdKey, out object value)) { - requestTelemetry.Properties["TargetingId"] = targetingId; + targetingId = value?.ToString(); + } + if (!string.IsNullOrEmpty(targetingId)) + { // Telemetry.Properties is deprecated in favor of ISupportProperties if (telemetry is ISupportProperties telemetryWithSupportProperties) {