Skip to content

Commit 85760da

Browse files
CopilotcaptainsafiaJamesNKCopilot
authored
Enable Identity telemetry by instantiating metrics classes directly (#64265)
* Initial plan * Register UserManagerMetrics and SignInManagerMetrics in DI container Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com> * Add InternalsVisibleTo and remove duplicate shared source files to enable UserManagerMetrics registration Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com> * Remove InternalsVisibleTo between product assemblies per feedback Co-authored-by: JamesNK <303201+JamesNK@users.noreply.github.com> * Instantiate metrics classes directly instead of registering in DI Co-authored-by: JamesNK <303201+JamesNK@users.noreply.github.com> * Add AddMetrics() call when registering UserManager and SignInManager Co-authored-by: JamesNK <303201+JamesNK@users.noreply.github.com> * Add test to verify metrics are recorded for UserManager and SignInManager Co-authored-by: JamesNK <303201+JamesNK@users.noreply.github.com> * Update * Update * Update src/Identity/Extensions.Core/src/UserManager.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/Identity/Core/src/SignInManager.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @JamesNK * Apply suggestion from @JamesNK --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com> Co-authored-by: JamesNK <303201+JamesNK@users.noreply.github.com> Co-authored-by: James Newton-King <james@newtonking.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 90b3332 commit 85760da

File tree

9 files changed

+53
-6
lines changed

9 files changed

+53
-6
lines changed

src/Identity/Core/src/IdentityServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public static class IdentityServiceCollectionExtensions
8989

9090
// Hosting doesn't add IHttpContextAccessor by default
9191
services.AddHttpContextAccessor();
92+
services.AddMetrics();
9293
// Identity services
9394
services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>();
9495
services.TryAddScoped<IPasswordValidator<TUser>, PasswordValidator<TUser>>();

src/Identity/Core/src/Microsoft.AspNetCore.Identity.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<Reference Include="Microsoft.AspNetCore.Authentication.BearerToken" />
2424
<Reference Include="Microsoft.AspNetCore.Authorization.Policy" />
2525
<Reference Include="Microsoft.AspNetCore.Http.Results" />
26+
<Reference Include="Microsoft.Extensions.Diagnostics" />
2627
<Reference Include="Microsoft.Extensions.Identity.Core" />
2728
<Reference Include="System.Formats.Cbor" />
2829
</ItemGroup>

src/Identity/Core/src/SignInManager.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Diagnostics;
55
using System.Diagnostics.CodeAnalysis;
6+
using System.Diagnostics.Metrics;
67
using System.Linq;
78
using System.Security.Claims;
89
using System.Text;
@@ -63,7 +64,8 @@ public SignInManager(UserManager<TUser> userManager,
6364
Logger = logger;
6465
_schemes = schemes;
6566
_confirmation = confirmation;
66-
_metrics = userManager.ServiceProvider?.GetService<SignInManagerMetrics>();
67+
// SignInManagerMetrics created from constructor because of difficulties registering internal type.
68+
_metrics = userManager.ServiceProvider?.GetService<IMeterFactory>() is { } factory ? new SignInManagerMetrics(factory) : null;
6769
_passkeyHandler = userManager.ServiceProvider?.GetService<IPasskeyHandler<TUser>>();
6870
}
6971

src/Identity/Extensions.Core/src/IdentityServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public static IdentityBuilder AddIdentityCore<TUser>(this IServiceCollection ser
3535
{
3636
// Services identity depends on
3737
services.AddOptions().AddLogging();
38+
services.AddMetrics();
3839

3940
// Services used by identity
4041
services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>();

src/Identity/Extensions.Core/src/Microsoft.Extensions.Identity.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
<ItemGroup>
1414
<Reference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" />
15+
<Reference Include="Microsoft.Extensions.Diagnostics" />
1516
<Reference Include="Microsoft.Extensions.Logging" />
1617
<Reference Include="Microsoft.Extensions.Options" />
1718

src/Identity/Extensions.Core/src/UserManager.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Diagnostics;
77
using System.Diagnostics.CodeAnalysis;
8+
using System.Diagnostics.Metrics;
89
using System.Linq;
910
using System.Runtime.InteropServices;
1011
using System.Security.Claims;
@@ -85,7 +86,8 @@ public UserManager(IUserStore<TUser> store,
8586
ErrorDescriber = errors;
8687
Logger = logger;
8788
ServiceProvider = services;
88-
_metrics = services?.GetService<UserManagerMetrics>();
89+
// UserManagerMetrics created from constructor because of difficulties registering internal type.
90+
_metrics = services?.GetService<IMeterFactory>() is { } factory ? new UserManagerMetrics(factory) : null;
8991

9092
if (userValidators != null)
9193
{

src/Identity/test/Identity.FunctionalTests/MapIdentityApiTests.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#nullable enable
55

6+
using System.Diagnostics.Metrics;
67
using System.Globalization;
78
using System.Net;
89
using System.Net.Http;
@@ -23,6 +24,8 @@
2324
using Microsoft.Data.Sqlite;
2425
using Microsoft.EntityFrameworkCore;
2526
using Microsoft.Extensions.DependencyInjection;
27+
using Microsoft.Extensions.Diagnostics.Metrics;
28+
using Microsoft.Extensions.Diagnostics.Metrics.Testing;
2629
using Microsoft.Extensions.Logging;
2730
using Microsoft.Extensions.Time.Testing;
2831
using Microsoft.Net.Http.Headers;
@@ -1274,6 +1277,44 @@ public async Task MustSendValidRequestToSendEmailChangeConfirmation()
12741277
AssertOk(await client.PostAsJsonAsync("/identity/login", new { Email = newEmail, Password = newPassword }));
12751278
}
12761279

1280+
[Fact]
1281+
public async Task MetricsAreRecordedForUserManagerAndSignInManager()
1282+
{
1283+
// Arrange
1284+
await using var app = await CreateAppAsync(services =>
1285+
{
1286+
AddIdentityApiEndpoints(services);
1287+
});
1288+
var meterFactory = app.Services.GetRequiredService<IMeterFactory>();
1289+
1290+
using var client = app.GetTestClient();
1291+
1292+
using var userCreateCollector = new MetricCollector<double>(meterFactory, "Microsoft.AspNetCore.Identity", "aspnetcore.identity.user.create.duration");
1293+
using var signInAuthenticateCollector = new MetricCollector<double>(meterFactory, "Microsoft.AspNetCore.Identity", "aspnetcore.identity.sign_in.authenticate.duration");
1294+
1295+
// Act - Register a new user
1296+
AssertOkAndEmpty(await client.PostAsJsonAsync("/identity/register", new { Email, Password }));
1297+
1298+
// Assert - UserManager metrics are recorded
1299+
var userCreateMeasurement = Assert.Single(userCreateCollector.GetMeasurementSnapshot());
1300+
Assert.True(userCreateMeasurement.Value > 0);
1301+
Assert.Equal("Identity.DefaultUI.WebSite.ApplicationUser", (string?)userCreateMeasurement.Tags["aspnetcore.identity.user_type"]);
1302+
Assert.Equal("success", (string?)userCreateMeasurement.Tags["aspnetcore.identity.result"]);
1303+
1304+
// Act - Login with the user
1305+
AssertOk(await client.PostAsJsonAsync("/identity/login", new { Email, Password }));
1306+
1307+
// Assert - SignInManager metrics are recorded
1308+
var signInMeasurements = signInAuthenticateCollector.GetMeasurementSnapshot();
1309+
Assert.NotEmpty(signInMeasurements);
1310+
var signInMeasurement = signInMeasurements.Last();
1311+
Assert.True(signInMeasurement.Value > 0);
1312+
Assert.Equal("Identity.DefaultUI.WebSite.ApplicationUser", (string?)signInMeasurement.Tags["aspnetcore.identity.user_type"]);
1313+
Assert.Equal("Identity.Bearer", (string?)signInMeasurement.Tags["aspnetcore.authentication.scheme"]);
1314+
Assert.Equal("success", (string?)signInMeasurement.Tags["aspnetcore.identity.sign_in.result"]);
1315+
Assert.Equal("password", (string?)signInMeasurement.Tags["aspnetcore.identity.sign_in.type"]);
1316+
}
1317+
12771318
private async Task<WebApplication> CreateAppAsync<TUser, TContext>(Action<IServiceCollection>? configureServices, bool autoStart = true)
12781319
where TUser : class, new()
12791320
where TContext : DbContext

src/Identity/test/Identity.FunctionalTests/Microsoft.AspNetCore.Identity.FunctionalTests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<ContainsFunctionalTestAssets>true</ContainsFunctionalTestAssets>
44
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@@ -22,6 +22,7 @@
2222

2323
<Reference Include="Microsoft.AspNetCore.Mvc.Testing" />
2424
<Reference Include="Microsoft.EntityFrameworkCore.Sqlite" />
25+
<Reference Include="Microsoft.Extensions.Diagnostics.Testing" />
2526
<Reference Include="Microsoft.Extensions.TimeProvider.Testing" />
2627
<Reference Include="AngleSharp" />
2728

src/Identity/test/Shared/MockHelpers.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ public static Mock<UserManager<TUser>> MockUserManager<TUser>(
2222
var services = new ServiceCollection();
2323
if (meterFactory != null)
2424
{
25-
services.AddSingleton<SignInManagerMetrics>();
2625
services.AddSingleton(meterFactory);
2726
}
2827
if (passkeyHandler != null)
@@ -61,8 +60,6 @@ public static UserManager<TUser> TestUserManager<TUser>(IUserStore<TUser> store
6160
var services = new ServiceCollection();
6261
if (meterFactory != null)
6362
{
64-
services.AddSingleton<UserManagerMetrics>();
65-
services.AddSingleton<SignInManagerMetrics>();
6663
services.AddSingleton(meterFactory);
6764
}
6865
var userManager = new UserManager<TUser>(store, options.Object, new PasswordHasher<TUser>(),

0 commit comments

Comments
 (0)