diff --git a/BREAKINGCHANGES.md b/BREAKINGCHANGES.md index 102bbea5..cf18bf16 100644 --- a/BREAKINGCHANGES.md +++ b/BREAKINGCHANGES.md @@ -7,6 +7,7 @@ ___Note:___ We might, and will probably, miss to document some of this - if so - ## TOC +* [Version 13.0.0 (TBD)](#version-1300-tbd) * [Version 12.0.0](#version-1200) * [Version 11.0.0](#version-1100) * [Version 10.0.0](#version-1000) @@ -20,6 +21,30 @@ ___Note:___ We might, and will probably, miss to document some of this - if so - ------ +## Version 13.0.0 (TBD) + +Breaking changes between version 12.0.0 and 13.0.0 + +### Upgrade to Application Insights 3.x + +`ActiveLogin.Authentication.BankId.AzureMonitor` now depends on `Microsoft.ApplicationInsights` 3.x. Application Insights 2.x and 3.x can't be mixed in the same application, so if you use `AddApplicationInsightsEventListener()` you must also upgrade the Application Insights packages in your application (for example `Microsoft.ApplicationInsights.AspNetCore`) to 3.x. Application Insights 3.x requires a connection string (not an instrumentation key) and fails at startup without one. See the [Application Insights migration guide](https://learn.microsoft.com/en-us/azure/azure-monitor/app/migrate-to-opentelemetry) for details. + +### Age hint moved from customMeasurements to customDimensions + +`AL_User_AgeHint` is no longer sent as a metric (`customMeasurements`). It is now sent as a string property in `customDimensions` on the same event. Update any queries or dashboards that read it: + +```kql +// Before +UserAgeHint = toint(customMeasurements.AL_User_AgeHint) + +// After +UserAgeHint = toint(customDimensions.AL_User_AgeHint) +``` + +The sample queries in [the monitor documentation](https://docs.activelogin.net/articles/monitor.html) have been updated. + +--- + ## Version 12.0.0 Breaking changes between version 11.0.0 and 12.0.0 diff --git a/docs/articles/bankid.md b/docs/articles/bankid.md index 3cd8f665..7854802d 100644 --- a/docs/articles/bankid.md +++ b/docs/articles/bankid.md @@ -1091,7 +1091,7 @@ services }); ``` -By default it will use whatever InstrumentationKey is registered with the application. There are overloads available so you can customize this: +By default it will use whatever Application Insights connection string is registered with the application. There are overloads available so you can customize this: ```csharp services diff --git a/docs/articles/monitor.md b/docs/articles/monitor.md index 002d99fd..1d32b377 100644 --- a/docs/articles/monitor.md +++ b/docs/articles/monitor.md @@ -127,7 +127,7 @@ customEvents customEvents | where name == "ActiveLogin_BankId_CollectCompleted" | project - UserAgeHint = toint(customMeasurements.AL_User_AgeHint) + UserAgeHint = toint(customDimensions.AL_User_AgeHint) | summarize AverageUserAge = avg(UserAgeHint) ``` diff --git a/samples/AzureProvisioningSample/ActiveLogin.json b/samples/AzureProvisioningSample/ActiveLogin.json index a89f01bc..2f24c5a4 100644 --- a/samples/AzureProvisioningSample/ActiveLogin.json +++ b/samples/AzureProvisioningSample/ActiveLogin.json @@ -369,7 +369,7 @@ "[resourceId('Microsoft.Insights/components', variables('insights_resource_name'))]" ], "properties": { - "APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(resourceId('Microsoft.Insights/components', variables('insights_resource_name'))).InstrumentationKey]", + "APPLICATIONINSIGHTS_CONNECTION_STRING": "[reference(resourceId('Microsoft.Insights/components', variables('insights_resource_name'))).ConnectionString]", "APPINSIGHTS_PORTALINFO": "ASP.NETCORE", "APPINSIGHTS_PROFILERFEATURE_VERSION": "disabled", "APPINSIGHTS_SNAPSHOTFEATURE_VERSION": "disabled" diff --git a/samples/IdentityServer.ClientSample/IdentityServer.ClientSample.csproj b/samples/IdentityServer.ClientSample/IdentityServer.ClientSample.csproj index 0087ca21..8289ba00 100644 --- a/samples/IdentityServer.ClientSample/IdentityServer.ClientSample.csproj +++ b/samples/IdentityServer.ClientSample/IdentityServer.ClientSample.csproj @@ -19,7 +19,7 @@ - + diff --git a/samples/IdentityServer.ClientSample/Program.cs b/samples/IdentityServer.ClientSample/Program.cs index 01c49a3d..0c462322 100644 --- a/samples/IdentityServer.ClientSample/Program.cs +++ b/samples/IdentityServer.ClientSample/Program.cs @@ -26,8 +26,16 @@ var environment = builder.Environment; -// Add telemetry -services.AddApplicationInsightsTelemetry(configuration); +// Add telemetry (Application Insights 3.x requires a connection string) +var applicationInsightsConnectionString = configuration["ApplicationInsights:ConnectionString"]; +if (string.IsNullOrWhiteSpace(applicationInsightsConnectionString)) +{ + applicationInsightsConnectionString = configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]; +} +if (!string.IsNullOrWhiteSpace(applicationInsightsConnectionString)) +{ + services.AddApplicationInsightsTelemetry(configuration); +} // Add authentication JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); diff --git a/samples/IdentityServer.ClientSample/appsettings.json b/samples/IdentityServer.ClientSample/appsettings.json index 0c007234..219c6b1d 100644 --- a/samples/IdentityServer.ClientSample/appsettings.json +++ b/samples/IdentityServer.ClientSample/appsettings.json @@ -7,7 +7,7 @@ } }, "ApplicationInsights": { - "InstrumentationKey": "" + "ConnectionString": "" }, "ActiveLogin": { "IdentityProvider": { diff --git a/samples/IdentityServer.ServerSample/IdentityServer.ServerSample.csproj b/samples/IdentityServer.ServerSample/IdentityServer.ServerSample.csproj index 094782ea..54a00bef 100644 --- a/samples/IdentityServer.ServerSample/IdentityServer.ServerSample.csproj +++ b/samples/IdentityServer.ServerSample/IdentityServer.ServerSample.csproj @@ -20,9 +20,8 @@ - + - diff --git a/samples/IdentityServer.ServerSample/Program.cs b/samples/IdentityServer.ServerSample/Program.cs index b73599f0..e0133857 100644 --- a/samples/IdentityServer.ServerSample/Program.cs +++ b/samples/IdentityServer.ServerSample/Program.cs @@ -40,8 +40,17 @@ var environment = builder.Environment; -// Add telemetry -services.AddApplicationInsightsTelemetry(configuration); +// Add telemetry (Application Insights 3.x requires a connection string) +var applicationInsightsConnectionString = configuration["ApplicationInsights:ConnectionString"]; +if (string.IsNullOrWhiteSpace(applicationInsightsConnectionString)) +{ + applicationInsightsConnectionString = configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]; +} +var useApplicationInsights = !string.IsNullOrWhiteSpace(applicationInsightsConnectionString); +if (useApplicationInsights) +{ + services.AddApplicationInsightsTelemetry(configuration); +} // Configure cookie policy services.Configure(options => @@ -83,17 +92,20 @@ .AddBankId(bankId => { bankId.AddDebugEventListener(); - bankId.AddApplicationInsightsEventListener(options => + if (useApplicationInsights) { - options.LogUserPersonalIdentityNumber = false; - options.LogUserPersonalIdentityNumberHints = true; + bankId.AddApplicationInsightsEventListener(options => + { + options.LogUserPersonalIdentityNumber = false; + options.LogUserPersonalIdentityNumberHints = true; - options.LogUserNames = false; - options.LogUserBankIdIssueDate = false; + options.LogUserNames = false; + options.LogUserBankIdIssueDate = false; - options.LogDeviceIpAddress = false; - options.LogDeviceUniqueHardwareId = false; - }); + options.LogDeviceIpAddress = false; + options.LogDeviceUniqueHardwareId = false; + }); + } bankId.UseQrCoderQrCodeGenerator(); bankId.UseUaParserDeviceDetection(); diff --git a/samples/IdentityServer.ServerSample/Views/Shared/_LayoutMaster.cshtml b/samples/IdentityServer.ServerSample/Views/Shared/_LayoutMaster.cshtml index 55cc812c..f314355e 100644 --- a/samples/IdentityServer.ServerSample/Views/Shared/_LayoutMaster.cshtml +++ b/samples/IdentityServer.ServerSample/Views/Shared/_LayoutMaster.cshtml @@ -1,4 +1,8 @@ -@inject Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet JavaScriptSnippet +@using Microsoft.Extensions.DependencyInjection +@{ + // Only registered when an Application Insights connection string is configured + var JavaScriptSnippet = Context.RequestServices.GetService(); +} @@ -56,7 +60,10 @@ - @Html.Raw(JavaScriptSnippet.FullScript) + @if (JavaScriptSnippet != null) + { + @Html.Raw(JavaScriptSnippet.FullScript) + } @RenderBody() diff --git a/samples/IdentityServer.ServerSample/appsettings.json b/samples/IdentityServer.ServerSample/appsettings.json index 9364b03c..fc4c7e54 100644 --- a/samples/IdentityServer.ServerSample/appsettings.json +++ b/samples/IdentityServer.ServerSample/appsettings.json @@ -7,7 +7,7 @@ } }, "ApplicationInsights": { - "InstrumentationKey": "" + "ConnectionString": "" }, "ActiveLogin": { "Clients": { diff --git a/samples/Standalone.MvcSample/Program.cs b/samples/Standalone.MvcSample/Program.cs index 49e271e4..f98d946c 100644 --- a/samples/Standalone.MvcSample/Program.cs +++ b/samples/Standalone.MvcSample/Program.cs @@ -38,8 +38,17 @@ var configuration = builder.Configuration; var environment = builder.Environment; -// Add telemetry -services.AddApplicationInsightsTelemetry(configuration); +// Add telemetry (Application Insights 3.x requires a connection string) +var applicationInsightsConnectionString = configuration["ApplicationInsights:ConnectionString"]; +if (string.IsNullOrWhiteSpace(applicationInsightsConnectionString)) +{ + applicationInsightsConnectionString = configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]; +} +var useApplicationInsights = !string.IsNullOrWhiteSpace(applicationInsightsConnectionString); +if (useApplicationInsights) +{ + services.AddApplicationInsightsTelemetry(configuration); +} // Configure cookie policy services.Configure(options => @@ -54,18 +63,21 @@ .AddBankId(bankId => { bankId.AddDebugEventListener(); - bankId.AddApplicationInsightsEventListener(options => + if (useApplicationInsights) { - options.LogUserPersonalIdentityNumber = false; - options.LogUserPersonalIdentityNumberHints = true; + bankId.AddApplicationInsightsEventListener(options => + { + options.LogUserPersonalIdentityNumber = false; + options.LogUserPersonalIdentityNumberHints = true; - options.LogUserNames = false; + options.LogUserNames = false; - options.LogUserBankIdIssueDate = false; + options.LogUserBankIdIssueDate = false; - options.LogDeviceIpAddress = false; - options.LogDeviceUniqueHardwareId = false; - }); + options.LogDeviceIpAddress = false; + options.LogDeviceUniqueHardwareId = false; + }); + } bankId.UseDeviceData(config => { diff --git a/samples/Standalone.MvcSample/Standalone.MvcSample.csproj b/samples/Standalone.MvcSample/Standalone.MvcSample.csproj index f7bd8945..b8aac693 100644 --- a/samples/Standalone.MvcSample/Standalone.MvcSample.csproj +++ b/samples/Standalone.MvcSample/Standalone.MvcSample.csproj @@ -31,6 +31,6 @@ - + diff --git a/samples/Standalone.MvcSample/appsettings.json b/samples/Standalone.MvcSample/appsettings.json index c2b34dc7..b270ce50 100644 --- a/samples/Standalone.MvcSample/appsettings.json +++ b/samples/Standalone.MvcSample/appsettings.json @@ -1,7 +1,7 @@ { "AllowedHosts": "*", "ApplicationInsights": { - "InstrumentationKey": "" + "ConnectionString": "" }, "Logging": { "LogLevel": { diff --git a/src/ActiveLogin.Authentication.BankId.AzureMonitor/ActiveLogin.Authentication.BankId.AzureMonitor.csproj b/src/ActiveLogin.Authentication.BankId.AzureMonitor/ActiveLogin.Authentication.BankId.AzureMonitor.csproj index 587f4cdc..e2ccf32d 100644 --- a/src/ActiveLogin.Authentication.BankId.AzureMonitor/ActiveLogin.Authentication.BankId.AzureMonitor.csproj +++ b/src/ActiveLogin.Authentication.BankId.AzureMonitor/ActiveLogin.Authentication.BankId.AzureMonitor.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/ActiveLogin.Authentication.BankId.AzureMonitor/BankIdApplicationInsightsEventListener.cs b/src/ActiveLogin.Authentication.BankId.AzureMonitor/BankIdApplicationInsightsEventListener.cs index 6304768b..223732d7 100644 --- a/src/ActiveLogin.Authentication.BankId.AzureMonitor/BankIdApplicationInsightsEventListener.cs +++ b/src/ActiveLogin.Authentication.BankId.AzureMonitor/BankIdApplicationInsightsEventListener.cs @@ -1,3 +1,5 @@ +using System.Globalization; + using ActiveLogin.Authentication.BankId.Api; using ActiveLogin.Authentication.BankId.Core.Events; using ActiveLogin.Authentication.BankId.Core.Events.Infrastructure; @@ -257,10 +259,9 @@ public override Task HandleCancelFailureEvent(BankIdCancelErrorEvent e) // Helpers - private Task Track(BankIdEvent e, Dictionary? properties = null, Dictionary? metrics = null, PersonalIdentityNumber? personalIdentityNumber = null, Exception? exception = null, BankIdSupportedDevice? detectedDevice = null, BankIdFlowOptions? bankIdOptions = null) + private Task Track(BankIdEvent e, Dictionary? properties = null, PersonalIdentityNumber? personalIdentityNumber = null, Exception? exception = null, BankIdSupportedDevice? detectedDevice = null, BankIdFlowOptions? bankIdOptions = null) { var allProperties = properties == null ? new Dictionary() : new Dictionary(properties); - var allMetrics = metrics == null ? new Dictionary() : new Dictionary(metrics); allProperties.Add(PropertyName_ProductName, e.ActiveLoginProductName); allProperties.Add(PropertyName_ProductVersion, e.ActiveLoginProductVersion); @@ -278,7 +279,7 @@ private Task Track(BankIdEvent e, Dictionary? properties = null, if (personalIdentityNumber != null) { - AddPersonalIdentityNumberProperties(allProperties, allMetrics, personalIdentityNumber); + AddPersonalIdentityNumberProperties(allProperties, personalIdentityNumber); } if (detectedDevice != null) @@ -294,15 +295,15 @@ private Task Track(BankIdEvent e, Dictionary? properties = null, allProperties.Add(PropertyName_BankIdErrorDetails, bankIdApiException.ErrorDetails); } - _telemetryClient.TrackException(exception, allProperties, metrics); + _telemetryClient.TrackException(exception, allProperties); } - _telemetryClient.TrackEvent(e.EventTypeName, allProperties, allMetrics); + _telemetryClient.TrackEvent(e.EventTypeName, allProperties); return Task.CompletedTask; } - private void AddPersonalIdentityNumberProperties(Dictionary properties, Dictionary metrics, PersonalIdentityNumber personalIdentityNumber) + private void AddPersonalIdentityNumberProperties(Dictionary properties, PersonalIdentityNumber personalIdentityNumber) { if (_options.LogUserPersonalIdentityNumber) { @@ -317,7 +318,7 @@ private void AddPersonalIdentityNumberProperties(Dictionary prop var ageHint = personalIdentityNumber?.GetAgeHint(); if (ageHint != null) { - metrics.Add(PropertyName_UserAgeHint, ageHint.Value); + properties.Add(PropertyName_UserAgeHint, ageHint.Value.ToString(CultureInfo.InvariantCulture)); } } } diff --git a/src/ActiveLogin.Authentication.BankId.AzureMonitor/BankIdApplicationInsightsTelemetryClientProvider.cs b/src/ActiveLogin.Authentication.BankId.AzureMonitor/BankIdApplicationInsightsTelemetryClientProvider.cs new file mode 100644 index 00000000..454e4ea1 --- /dev/null +++ b/src/ActiveLogin.Authentication.BankId.AzureMonitor/BankIdApplicationInsightsTelemetryClientProvider.cs @@ -0,0 +1,43 @@ +using Microsoft.ApplicationInsights; +using Microsoft.ApplicationInsights.Extensibility; + +namespace ActiveLogin.Authentication.BankId.AzureMonitor; + +/// +/// Owns a and created from a connection string, +/// so that buffered telemetry is flushed and the configuration is disposed when the container is disposed. +/// +internal sealed class BankIdApplicationInsightsTelemetryClientProvider : IDisposable +{ + private readonly TelemetryConfiguration _telemetryConfiguration; + private bool _disposed; + + public BankIdApplicationInsightsTelemetryClientProvider(string connectionString) + { + _telemetryConfiguration = new TelemetryConfiguration + { + ConnectionString = connectionString + }; + TelemetryClient = new TelemetryClient(_telemetryConfiguration); + } + + public TelemetryClient TelemetryClient { get; } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + try + { + TelemetryClient.Flush(); + } + finally + { + _telemetryConfiguration.Dispose(); + } + } +} diff --git a/src/ActiveLogin.Authentication.BankId.AzureMonitor/BankIdBuilderAzureMonitorExtensions.cs b/src/ActiveLogin.Authentication.BankId.AzureMonitor/BankIdBuilderAzureMonitorExtensions.cs index 74257ca8..e6fd53ee 100644 --- a/src/ActiveLogin.Authentication.BankId.AzureMonitor/BankIdBuilderAzureMonitorExtensions.cs +++ b/src/ActiveLogin.Authentication.BankId.AzureMonitor/BankIdBuilderAzureMonitorExtensions.cs @@ -2,7 +2,6 @@ using ActiveLogin.Authentication.BankId.Core.Events.Infrastructure; using Microsoft.ApplicationInsights; -using Microsoft.ApplicationInsights.Extensibility; using Microsoft.Extensions.DependencyInjection; namespace ActiveLogin.Authentication.BankId.AzureMonitor; @@ -80,12 +79,12 @@ public static IBankIdBuilder AddApplicationInsightsEventListener(this IBankIdBui /// public static IBankIdBuilder AddApplicationInsightsEventListener(this IBankIdBuilder builder, string connectionString, ApplicationInsightsBankIdEventListenerOptions options) { - builder.Services.AddTransient(x => - { - var telemetryConfiguration = new TelemetryConfiguration(); - telemetryConfiguration.ConnectionString = connectionString; - return new BankIdApplicationInsightsEventListener(new TelemetryClient(telemetryConfiguration), options); - }); + // The listener is transient, so the configuration and client are owned by a singleton provider. + // The container disposes it on shutdown, which flushes buffered telemetry. + // The provider is keyed per call so that multiple registrations each use their own connection string. + var providerKey = new object(); + builder.Services.AddKeyedSingleton(providerKey, (_, _) => new BankIdApplicationInsightsTelemetryClientProvider(connectionString)); + builder.Services.AddTransient(x => new BankIdApplicationInsightsEventListener(x.GetRequiredKeyedService(providerKey).TelemetryClient, options)); return builder; }