diff --git a/src/Microsoft.IdentityModel.Tokens/AppContextSwitches.cs b/src/Microsoft.IdentityModel.Tokens/AppContextSwitches.cs
index 7f32a2fb85..97dc7fcceb 100644
--- a/src/Microsoft.IdentityModel.Tokens/AppContextSwitches.cs
+++ b/src/Microsoft.IdentityModel.Tokens/AppContextSwitches.cs
@@ -98,6 +98,14 @@ internal static class AppContextSwitches
private static bool? _useCapitalizedXMLTypeAttr;
internal static bool UseCapitalizedXMLTypeAttr => _useCapitalizedXMLTypeAttr ??= (AppContext.TryGetSwitch(UseCapitalizedXMLTypeAttrSwitch, out bool useCapitalizedXMLTypeAttr) && useCapitalizedXMLTypeAttr);
+ ///
+ /// When enabled, telemetry will use the full metadata address instead of just the domain name for IdentityModelConfiguration metrics.
+ /// By default (when disabled), only the domain name is used for successful operations to reduce OpenTelemetry cardinality.
+ ///
+ internal const string UseFullMetadataAddressForTelemetrySwitch = "Switch.Microsoft.IdentityModel.UseFullMetadataAddressForTelemetry";
+ private static bool? _useFullMetadataAddressForTelemetry;
+ internal static bool UseFullMetadataAddressForTelemetry => _useFullMetadataAddressForTelemetry ??= (AppContext.TryGetSwitch(UseFullMetadataAddressForTelemetrySwitch, out bool useFullMetadataAddressForTelemetry) && useFullMetadataAddressForTelemetry);
+
///
/// Used for testing to reset all switches to its default value.
///
@@ -123,6 +131,9 @@ internal static void ResetAllSwitches()
_useCapitalizedXMLTypeAttr = null;
AppContext.SetSwitch(UseCapitalizedXMLTypeAttrSwitch, false);
+
+ _useFullMetadataAddressForTelemetry = null;
+ AppContext.SetSwitch(UseFullMetadataAddressForTelemetrySwitch, false);
}
}
}
diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs
index 2a56774454..73ad2100a1 100644
--- a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs
+++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs
@@ -22,12 +22,33 @@ internal class TelemetryClient : ITelemetryClient
AppContextSwitches.UpdateConfigAsBlocking.ToString()
);
+ ///
+ /// Extracts the domain name from a metadata address for telemetry purposes.
+ /// Returns the full address if domain extraction fails or if the UseFullMetadataAddressForTelemetry switch is enabled.
+ /// In error cases, always returns the full address for better debugging.
+ ///
+ /// The full metadata address
+ /// True if this is a successful operation, false for error cases
+ /// Domain name for success cases (when switch is disabled), full address otherwise
+ internal static string GetMetadataAddressForTelemetry(string metadataAddress, bool isSuccessCase = true)
+ {
+ // Always use full address for error cases or when the switch is enabled
+ if (!isSuccessCase || AppContextSwitches.UseFullMetadataAddressForTelemetry || string.IsNullOrEmpty(metadataAddress))
+ return metadataAddress;
+
+
+ if (Uri.TryCreate(metadataAddress, UriKind.Absolute, out Uri result))
+ return result.Host;
+
+ return metadataAddress;
+ }
+
public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, string configurationSource)
{
var tagList = new TagList()
{
{ TelemetryConstants.IdentityModelVersionTag, ClientVer },
- { TelemetryConstants.MetadataAddressTag, metadataAddress },
+ { TelemetryConstants.MetadataAddressTag, GetMetadataAddressForTelemetry(metadataAddress, isSuccessCase: true) },
{ TelemetryConstants.OperationStatusTag, operationStatus },
{ TelemetryConstants.ConfigurationSourceTag, configurationSource },
_blockingTagValue
@@ -41,7 +62,7 @@ public void IncrementConfigurationRefreshRequestCounter(string metadataAddress,
var tagList = new TagList()
{
{ TelemetryConstants.IdentityModelVersionTag, ClientVer },
- { TelemetryConstants.MetadataAddressTag, metadataAddress },
+ { TelemetryConstants.MetadataAddressTag, GetMetadataAddressForTelemetry(metadataAddress, isSuccessCase: false) },
{ TelemetryConstants.OperationStatusTag, operationStatus },
{ TelemetryConstants.ConfigurationSourceTag, configurationSource },
{ TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString() },
@@ -56,7 +77,7 @@ public void LogConfigurationRetrievalDuration(string metadataAddress, string con
var tagList = new TagList()
{
{ TelemetryConstants.IdentityModelVersionTag, ClientVer },
- { TelemetryConstants.MetadataAddressTag, metadataAddress },
+ { TelemetryConstants.MetadataAddressTag, GetMetadataAddressForTelemetry(metadataAddress, isSuccessCase: true) },
{ TelemetryConstants.ConfigurationSourceTag, configurationSource },
};
@@ -69,7 +90,7 @@ public void LogConfigurationRetrievalDuration(string metadataAddress, string con
var tagList = new TagList()
{
{ TelemetryConstants.IdentityModelVersionTag, ClientVer },
- { TelemetryConstants.MetadataAddressTag, metadataAddress },
+ { TelemetryConstants.MetadataAddressTag, GetMetadataAddressForTelemetry(metadataAddress, isSuccessCase: false) },
{ TelemetryConstants.ConfigurationSourceTag, configurationSource },
{ TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString() },
_blockingTagValue
@@ -87,7 +108,7 @@ public void LogBackgroundConfigurationRefreshFailure(
var tagList = new TagList()
{
{ TelemetryConstants.IdentityModelVersionTag, ClientVer },
- { TelemetryConstants.MetadataAddressTag, metadataAddress },
+ { TelemetryConstants.MetadataAddressTag, GetMetadataAddressForTelemetry(metadataAddress, isSuccessCase: false) },
{ TelemetryConstants.ConfigurationSourceTag, configurationSource },
{ TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString() },
_blockingTagValue
diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/ClaimsIdentityFactoryTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/ClaimsIdentityFactoryTests.cs
index f1878a638b..1a1cf0da39 100644
--- a/test/Microsoft.IdentityModel.Tokens.Tests/ClaimsIdentityFactoryTests.cs
+++ b/test/Microsoft.IdentityModel.Tokens.Tests/ClaimsIdentityFactoryTests.cs
@@ -9,14 +9,10 @@
namespace Microsoft.IdentityModel.Tokens.Tests
{
- [Collection(nameof(ClaimsIdentityFactoryTests))]
+ [ResetAppContextSwitches]
+ [Collection("AppContextSwitches")]
public class ClaimsIdentityFactoryTests
{
- public ClaimsIdentityFactoryTests()
- {
- AppContextSwitches.ResetAllSwitches();
- }
-
[Theory]
[InlineData(true)]
[InlineData(false)]
@@ -47,7 +43,7 @@ public void Create_FromTokenValidationParameters_ReturnsCorrectClaimsIdentity(bo
Assert.Equal(jsonWebToken, ((CaseSensitiveClaimsIdentity)actualClaimsIdentity).SecurityToken);
}
- AppContextSwitches.ResetAllSwitches();
+ AppContext.SetSwitch(AppContextSwitches.UseClaimsIdentityTypeSwitch, false);
}
[Theory]
diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/TelemetryClientDomainExtractionTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/TelemetryClientDomainExtractionTests.cs
new file mode 100644
index 0000000000..4cba8625b5
--- /dev/null
+++ b/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/TelemetryClientDomainExtractionTests.cs
@@ -0,0 +1,157 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using Microsoft.IdentityModel.TestUtils;
+using Microsoft.IdentityModel.Tokens;
+using Xunit;
+
+namespace Microsoft.IdentityModel.Telemetry.Tests;
+
+[ResetAppContextSwitches]
+[Collection("AppContextSwitches")]
+public class TelemetryClientDomainExtractionTests
+{
+ [Theory]
+ [InlineData("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration", "login.microsoftonline.com")]
+ [InlineData("https://www.login.microsoftonline.com/common/v2.0/.well-known/openid-configuration", "www.login.microsoftonline.com")]
+ [InlineData("https://accounts.google.com/.well-known/openid-configuration", "accounts.google.com")]
+ [InlineData("https://login.windows.net/common/.well-known/openid-configuration", "login.windows.net")]
+ [InlineData("http://localhost:8080/.well-known/openid-configuration", "localhost")]
+ [InlineData("https://example.com/path/to/config", "example.com")]
+ [InlineData("https://subdomain.example.org/config.json", "subdomain.example.org")]
+ public void GetMetadataAddressForTelemetry_SuccessCase_ReturnsExpectedDomain(string fullAddress, string expectedDomain)
+ {
+ // Act
+ var result = TelemetryClient.GetMetadataAddressForTelemetry(fullAddress, true);
+
+ // Assert
+ Assert.Equal(expectedDomain, result);
+ }
+
+ [Fact]
+ public void DebugAppContextSwitch()
+ {
+ // Check if the AppContext switch is causing issues
+ var switchValue = AppContextSwitches.UseFullMetadataAddressForTelemetry;
+
+ var testUrl = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
+ var result = TelemetryClient.GetMetadataAddressForTelemetry(testUrl);
+
+ // The switch should be false by default, so we should get the host name
+ Assert.False(switchValue, $"Switch value: {switchValue}");
+ Assert.Equal("login.microsoftonline.com", result);
+ }
+
+ [Theory]
+ [InlineData("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration")]
+ [InlineData("https://accounts.google.com/.well-known/openid-configuration")]
+ [InlineData("https://login.windows.net/common/.well-known/openid-configuration")]
+ public void GetMetadataAddressForTelemetry_ErrorCase_ReturnsFullAddress(string fullAddress)
+ {
+ // Act
+ var result = TelemetryClient.GetMetadataAddressForTelemetry(fullAddress, false);
+
+ // Assert
+ Assert.Equal(fullAddress, result);
+ }
+
+ [Theory]
+ [InlineData("invalid-url")]
+ [InlineData("")]
+ [InlineData(null)]
+ public void GetMetadataAddressForTelemetry_InvalidUrl_ReturnsOriginalString(string invalidUrl)
+ {
+ // Act
+ var result = TelemetryClient.GetMetadataAddressForTelemetry(invalidUrl, true);
+
+ // Assert
+ Assert.Equal(invalidUrl, result);
+ }
+
+ [Fact]
+ public void GetMetadataAddressForTelemetry_WithAppContextSwitch_ReturnsFullAddress()
+ {
+ // Arrange
+ var fullAddress = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
+
+ try
+ {
+ // Enable the switch to use full metadata address
+ AppContext.SetSwitch(AppContextSwitches.UseFullMetadataAddressForTelemetrySwitch, true);
+
+ // Act
+ var result = TelemetryClient.GetMetadataAddressForTelemetry(fullAddress, true);
+
+ // Assert
+ Assert.Equal(fullAddress, result);
+ }
+ finally
+ {
+ // Cleanup is handled by ResetAppContextSwitches attribute
+ AppContext.SetSwitch(AppContextSwitches.UseFullMetadataAddressForTelemetrySwitch, false);
+ }
+ }
+
+ [Fact]
+ public void TelemetryClient_Methods_DoNotThrow()
+ {
+ // This test ensures that the modified telemetry methods don't break existing functionality
+ // We cannot easily verify the exact values sent to the telemetry system without complex setup,
+ // but we can ensure the methods don't throw exceptions when called with valid parameters.
+
+ // Arrange
+ var telemetryClient = new TelemetryClient();
+ var testAddress = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
+ var testDuration = TimeSpan.FromMilliseconds(100);
+ var testException = new InvalidOperationException("Test exception");
+
+ // Act & Assert - these should not throw
+ var ex1 = Record.Exception(() => telemetryClient.LogConfigurationRetrievalDuration(testAddress, "Retriever", testDuration));
+ var ex2 = Record.Exception(() => telemetryClient.LogConfigurationRetrievalDuration(testAddress, "Retriever", testDuration, testException));
+ var ex3 = Record.Exception(() => telemetryClient.IncrementConfigurationRefreshRequestCounter(testAddress, "FirstRefresh", "Retriever"));
+ var ex4 = Record.Exception(() => telemetryClient.IncrementConfigurationRefreshRequestCounter(testAddress, "ConfigurationRetrievalFailed", "Retriever", testException));
+ var ex5 = Record.Exception(() => telemetryClient.LogBackgroundConfigurationRefreshFailure(testAddress, "Retriever", testException));
+
+ Assert.Null(ex1);
+ Assert.Null(ex2);
+ Assert.Null(ex3);
+ Assert.Null(ex4);
+ Assert.Null(ex5);
+ }
+
+ [Fact]
+ public void TelemetryClient_WithAppContextSwitch_DoesNotThrow()
+ {
+ // Test that telemetry methods work correctly when the backward compatibility switch is enabled
+ try
+ {
+ // Enable the switch to use full metadata address
+ AppContext.SetSwitch(AppContextSwitches.UseFullMetadataAddressForTelemetrySwitch, true);
+
+ // Arrange
+ var telemetryClient = new TelemetryClient();
+ var testAddress = "https://accounts.google.com/.well-known/openid-configuration";
+ var testDuration = TimeSpan.FromMilliseconds(150);
+ var testException = new InvalidOperationException("Test exception");
+
+ // Act & Assert - these should not throw even with the switch enabled
+ var ex1 = Record.Exception(() => telemetryClient.LogConfigurationRetrievalDuration(testAddress, "Retriever", testDuration));
+ var ex2 = Record.Exception(() => telemetryClient.LogConfigurationRetrievalDuration(testAddress, "Retriever", testDuration, testException));
+ var ex3 = Record.Exception(() => telemetryClient.IncrementConfigurationRefreshRequestCounter(testAddress, "FirstRefresh", "Retriever"));
+ var ex4 = Record.Exception(() => telemetryClient.IncrementConfigurationRefreshRequestCounter(testAddress, "ConfigurationRetrievalFailed", "Retriever", testException));
+ var ex5 = Record.Exception(() => telemetryClient.LogBackgroundConfigurationRefreshFailure(testAddress, "Retriever", testException));
+
+ Assert.Null(ex1);
+ Assert.Null(ex2);
+ Assert.Null(ex3);
+ Assert.Null(ex4);
+ Assert.Null(ex5);
+ }
+ finally
+ {
+ // Cleanup
+ AppContext.SetSwitch(AppContextSwitches.UseFullMetadataAddressForTelemetrySwitch, false);
+ }
+ }
+}