diff --git a/build/dependencies.props b/build/dependencies.props
index 9438d4d70e..aa3c765a75 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -8,6 +8,7 @@
1.0.0
2.0.3
13.0.3
+ 6.0.2
4.5.5
4.5.0
8.0.5
diff --git a/build/dependenciesTest.props b/build/dependenciesTest.props
index 65daedcf38..4e5f755ef4 100644
--- a/build/dependenciesTest.props
+++ b/build/dependenciesTest.props
@@ -7,6 +7,7 @@
17.11.1
2.0.3
13.0.3
+ 1.6.0
4.3.4
4.3.0
8.0.5
diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt
index cb23d777b2..2cfd5e0431 100644
--- a/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt
@@ -1,3 +1,4 @@
+Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler._telemetryClient -> Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient
static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.CreateToken(string payload, Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor tokenDescriptor) -> string
static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.EncryptToken(byte[] innerTokenUtf8Bytes, Microsoft.IdentityModel.Tokens.EncryptingCredentials encryptingCredentials, string compressionAlgorithm, System.Collections.Generic.IDictionary additionalHeaderClaims, string tokenType, bool includeKeyIdInHeader) -> string
static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.EncryptToken(byte[] innerTokenUtf8Bytes, Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor tokenDescriptor) -> string
diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs
index 918788932f..c37fb5d876 100644
--- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs
+++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.cs
@@ -10,6 +10,7 @@
using Microsoft.IdentityModel.Abstractions;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;
+using Microsoft.IdentityModel.Tokens.Telemetry;
using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;
namespace Microsoft.IdentityModel.JsonWebTokens
@@ -17,6 +18,8 @@ namespace Microsoft.IdentityModel.JsonWebTokens
/// This partial class contains methods and logic related to the validation of tokens.
public partial class JsonWebTokenHandler : TokenHandler
{
+ internal Tokens.Telemetry.ITelemetryClient _telemetryClient = new TelemetryClient();
+
///
/// Returns a value that indicates if this handler can validate a .
///
@@ -511,6 +514,10 @@ await ValidateJWEAsync(jsonWebToken, validationParameters, currentConfiguration)
// where a new valid configuration was somehow published during validation time.
if (currentConfiguration != null)
{
+ _telemetryClient.IncrementConfigurationRefreshRequestCounter(
+ validationParameters.ConfigurationManager.MetadataAddress,
+ TelemetryConstants.Protocols.Lkg);
+
validationParameters.ConfigurationManager.RequestRefresh();
validationParameters.RefreshBeforeValidation = true;
var lastConfig = currentConfiguration;
diff --git a/src/Microsoft.IdentityModel.Logging/Microsoft.IdentityModel.Logging.csproj b/src/Microsoft.IdentityModel.Logging/Microsoft.IdentityModel.Logging.csproj
index e6a1110b05..4b9733a6f5 100644
--- a/src/Microsoft.IdentityModel.Logging/Microsoft.IdentityModel.Logging.csproj
+++ b/src/Microsoft.IdentityModel.Logging/Microsoft.IdentityModel.Logging.csproj
@@ -32,6 +32,10 @@
+
+
+
+
diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs
index 1f38022f33..1e5455861d 100644
--- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs
+++ b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs
@@ -8,6 +8,7 @@
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Protocols.Configuration;
using Microsoft.IdentityModel.Tokens;
+using Microsoft.IdentityModel.Tokens.Telemetry;
namespace Microsoft.IdentityModel.Protocols
{
@@ -35,6 +36,9 @@ public class ConfigurationManager : BaseConfigurationManager, IConfigurationM
private const int ConfigurationRetrieverRunning = 1;
private int _configurationRetrieverState = ConfigurationRetrieverIdle;
+ internal TimeProvider TimeProvider = TimeProvider.System;
+ internal ITelemetryClient TelemetryClient = new TelemetryClient();
+
///
/// Instantiates a new that manages automatic and controls refreshing on configuration data.
///
@@ -172,7 +176,7 @@ public virtual async Task GetConfigurationAsync(CancellationToken cancel)
try
{
// Don't use the individual CT here, this is a shared operation that shouldn't be affected by an individual's cancellation.
- // The transport should have it's own timeouts, etc.
+ // The transport should have its own timeouts, etc.
T configuration = await _configRetriever.GetConfigurationAsync(
MetadataAddress,
_docRetriever,
@@ -183,18 +187,29 @@ public virtual async Task GetConfigurationAsync(CancellationToken cancel)
ConfigurationValidationResult result = _configValidator.Validate(configuration);
// in this case we have never had a valid configuration, so we will throw an exception if the validation fails
if (!result.Succeeded)
- throw LogHelper.LogExceptionMessage(
- new InvalidConfigurationException(
- LogHelper.FormatInvariant(
- LogMessages.IDX20810,
- result.ErrorMessage)));
+ {
+ var ex = new InvalidConfigurationException(
+ LogHelper.FormatInvariant(
+ LogMessages.IDX20810,
+ result.ErrorMessage));
+
+ throw LogHelper.LogExceptionMessage(ex);
+ }
}
+ TelemetryClient.IncrementConfigurationRefreshRequestCounter(
+ MetadataAddress,
+ TelemetryConstants.Protocols.FirstRefresh);
+
UpdateConfiguration(configuration);
}
catch (Exception ex)
{
fetchMetadataFailure = ex;
+ TelemetryClient.IncrementConfigurationRefreshRequestCounter(
+ MetadataAddress,
+ TelemetryConstants.Protocols.FirstRefresh,
+ ex);
LogHelper.LogExceptionMessage(
new InvalidOperationException(
@@ -214,6 +229,10 @@ public virtual async Task GetConfigurationAsync(CancellationToken cancel)
{
if (Interlocked.CompareExchange(ref _configurationRetrieverState, ConfigurationRetrieverRunning, ConfigurationRetrieverIdle) == ConfigurationRetrieverIdle)
{
+ TelemetryClient.IncrementConfigurationRefreshRequestCounter(
+ MetadataAddress,
+ TelemetryConstants.Protocols.Automatic);
+
_ = Task.Run(UpdateCurrentConfiguration, CancellationToken.None);
}
}
@@ -240,6 +259,8 @@ public virtual async Task GetConfigurationAsync(CancellationToken cancel)
private void UpdateCurrentConfiguration()
{
#pragma warning disable CA1031 // Do not catch general exception types
+ long startTimestamp = TimeProvider.GetTimestamp();
+
try
{
T configuration = _configRetriever.GetConfigurationAsync(
@@ -247,6 +268,11 @@ private void UpdateCurrentConfiguration()
_docRetriever,
CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult();
+ var elapsedTime = TimeProvider.GetElapsedTime(startTimestamp);
+ TelemetryClient.LogConfigurationRetrievalDuration(
+ MetadataAddress,
+ elapsedTime);
+
if (_configValidator == null)
{
UpdateConfiguration(configuration);
@@ -267,6 +293,12 @@ private void UpdateCurrentConfiguration()
}
catch (Exception ex)
{
+ var elapsedTime = TimeProvider.GetElapsedTime(startTimestamp);
+ TelemetryClient.LogConfigurationRetrievalDuration(
+ MetadataAddress,
+ elapsedTime,
+ ex);
+
LogHelper.LogExceptionMessage(
new InvalidOperationException(
LogHelper.FormatInvariant(
@@ -293,7 +325,7 @@ private void UpdateConfiguration(T configuration)
/// Obtains an updated version of Configuration.
///
/// CancellationToken
- /// Configuration of type BaseConfiguration .
+ /// Configuration of type BaseConfiguration.
/// If the time since the last call is less than then is not called and the current Configuration is returned.
public override async Task GetBaseConfigurationAsync(CancellationToken cancel)
{
@@ -311,6 +343,10 @@ public override void RequestRefresh()
{
DateTimeOffset now = DateTimeOffset.UtcNow;
+ TelemetryClient.IncrementConfigurationRefreshRequestCounter(
+ MetadataAddress,
+ TelemetryConstants.Protocols.Manual);
+
if (now >= DateTimeUtil.Add(_lastRequestRefresh.UtcDateTime, RefreshInterval) || _isFirstRefreshRequest)
{
_isFirstRefreshRequest = false;
diff --git a/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt
index e69de29bb2..5d0bb6b3c4 100644
--- a/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.IdentityModel.Protocols/InternalAPI.Unshipped.txt
@@ -0,0 +1,2 @@
+Microsoft.IdentityModel.Protocols.ConfigurationManager.TelemetryClient -> Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient
+Microsoft.IdentityModel.Protocols.ConfigurationManager.TimeProvider -> System.TimeProvider
diff --git a/src/Microsoft.IdentityModel.Protocols/InternalsVisibleTo.cs b/src/Microsoft.IdentityModel.Protocols/InternalsVisibleTo.cs
new file mode 100644
index 0000000000..5e00856ea6
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Protocols/InternalsVisibleTo.cs
@@ -0,0 +1,4 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt
index 14d2d3493a..d3311f516f 100644
--- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt
@@ -8,7 +8,20 @@ const Microsoft.IdentityModel.Tokens.LogMessages.IDX10273 = "IDX10273: Algorithm
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10274 = "IDX10274: IssuerSigningKeyValidationDelegate threw an exception, see inner exception." -> string
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10275 = "IDX10275: TokenTypeValidationDelegate threw an exception, see inner exception." -> string
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10276 = "IDX10276: TokenReplayValidationDelegate threw an exception, see inner exception." -> string
+const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.ExceptionTypeTag = "ExceptionType" -> string
+const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.IdentityModelVersionTag = "IdentityModelVersion" -> string
+const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.MetadataAddressTag = "MetadataAddress" -> string
+const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.OperationStatusTag = "OperationStatus" -> string
+const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.Protocols.Automatic = "Automatic" -> string
+const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.Protocols.ConfigurationInvalid = "ConfigurationInvalid" -> string
+const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.Protocols.ConfigurationRetrievalFailed = "ConfigurationRetrievalFailed" -> string
+const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.Protocols.FirstRefresh = "FirstRefresh" -> string
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10277 = "IDX10277: RequireAudience property on ValidationParameters is set to false. Exiting without validating the audience." -> string
+const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.Protocols.Lkg = "LastKnownGood" -> string
+const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.Protocols.Manual = "Manual" -> string
+const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder.IdentityModelConfigurationManagerCounterDescription = "Counter capturing configuration manager operations." -> string
+const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder.IdentityModelConfigurationManagerCounterName = "IdentityModelConfigurationManager" -> string
+const Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder.TotalDurationHistogramName = "IdentityModelConfigurationRequestTotalDurationInMS" -> string
Microsoft.IdentityModel.Tokens.AlgorithmValidationError
Microsoft.IdentityModel.Tokens.AlgorithmValidationError.AlgorithmValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidAlgorithm, System.Exception innerException = null) -> void
Microsoft.IdentityModel.Tokens.AlgorithmValidationError.InvalidAlgorithm.get -> string
@@ -37,9 +50,25 @@ Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.SecurityTo
Microsoft.IdentityModel.Tokens.SignatureValidationError
Microsoft.IdentityModel.Tokens.SignatureValidationError.InnerValidationError.get -> Microsoft.IdentityModel.Tokens.ValidationError
Microsoft.IdentityModel.Tokens.SignatureValidationError.SignatureValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, Microsoft.IdentityModel.Tokens.ValidationError innerValidationError = null, System.Exception innerException = null) -> void
+Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient
+Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) -> void
+Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, System.Exception exception) -> void
+Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration) -> void
+Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration, System.Exception exception) -> void
+Microsoft.IdentityModel.Tokens.Telemetry.TelemetryClient
+Microsoft.IdentityModel.Tokens.Telemetry.TelemetryClient.ClientVer -> string
+Microsoft.IdentityModel.Tokens.Telemetry.TelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus) -> void
+Microsoft.IdentityModel.Tokens.Telemetry.TelemetryClient.IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, System.Exception exception) -> void
+Microsoft.IdentityModel.Tokens.Telemetry.TelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration) -> void
+Microsoft.IdentityModel.Tokens.Telemetry.TelemetryClient.LogConfigurationRetrievalDuration(string metadataAddress, System.TimeSpan operationDuration, System.Exception exception) -> void
+Microsoft.IdentityModel.Tokens.Telemetry.TelemetryClient.TelemetryClient() -> void
+Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder
+Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder.TelemetryDataRecorder() -> void
Microsoft.IdentityModel.Tokens.TokenReplayValidationError
Microsoft.IdentityModel.Tokens.TokenReplayValidationError.ExpirationTime.get -> System.DateTime?
Microsoft.IdentityModel.Tokens.TokenReplayValidationError.TokenReplayValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.DateTime? expirationTime, System.Exception innerException = null) -> void
+Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants
+Microsoft.IdentityModel.Tokens.Telemetry.TelemetryConstants.Protocols
Microsoft.IdentityModel.Tokens.TokenTypeValidationError
Microsoft.IdentityModel.Tokens.TokenTypeValidationError.InvalidTokenType.get -> string
Microsoft.IdentityModel.Tokens.TokenTypeValidationError.TokenTypeValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidTokenType, System.Exception innerException = null) -> void
@@ -63,6 +92,8 @@ override Microsoft.IdentityModel.Tokens.TokenReplayValidationError.GetException(
override Microsoft.IdentityModel.Tokens.TokenTypeValidationError.GetException() -> System.Exception
static Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError
static Microsoft.IdentityModel.Tokens.SignatureValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.SignatureValidationError
+static Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder.IncrementConfigurationRefreshRequestCounter(in System.Diagnostics.TagList tagList) -> void
+static Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder.RecordConfigurationRetrievalDurationHistogram(long requestDurationInMs, in System.Diagnostics.TagList tagList) -> void
static Microsoft.IdentityModel.Tokens.TokenReplayValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.TokenReplayValidationError
static Microsoft.IdentityModel.Tokens.TokenTypeValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.TokenTypeValidationError
static Microsoft.IdentityModel.Tokens.Base64UrlEncoder.Decode(System.ReadOnlySpan strSpan, System.Span output) -> int
@@ -70,6 +101,8 @@ static Microsoft.IdentityModel.Tokens.Utility.SerializeAsSingleCommaDelimitedStr
static Microsoft.IdentityModel.Tokens.ValidationError.GetCurrentStackFrame(string filePath = "", int lineNumber = 0, int skipFrames = 1) -> System.Diagnostics.StackFrame
static readonly Microsoft.IdentityModel.Tokens.LoggingEventId.TokenValidationFailed -> Microsoft.Extensions.Logging.EventId
static readonly Microsoft.IdentityModel.Tokens.LoggingEventId.TokenValidationSucceeded -> Microsoft.Extensions.Logging.EventId
+static readonly Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder.ConfigurationManagerCounter -> System.Diagnostics.Metrics.Counter
+static readonly Microsoft.IdentityModel.Tokens.Telemetry.TelemetryDataRecorder.TotalDurationHistogram -> System.Diagnostics.Metrics.Histogram
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.AlgorithmValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.AudienceValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.IssuerSigningKeyValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType
diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs
new file mode 100644
index 0000000000..350a0076ae
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/ITelemetryClient.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+
+namespace Microsoft.IdentityModel.Tokens.Telemetry
+{
+ internal interface ITelemetryClient
+ {
+ internal void LogConfigurationRetrievalDuration(
+ string metadataAddress,
+ TimeSpan operationDuration);
+
+ internal void LogConfigurationRetrievalDuration(
+ string metadataAddress,
+ TimeSpan operationDuration,
+ Exception exception);
+
+ internal void IncrementConfigurationRefreshRequestCounter(
+ string metadataAddress,
+ string operationStatus);
+
+ internal void IncrementConfigurationRefreshRequestCounter(
+ string metadataAddress,
+ string operationStatus,
+ Exception exception);
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs
new file mode 100644
index 0000000000..92026976cc
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryClient.cs
@@ -0,0 +1,67 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using Microsoft.IdentityModel.Logging;
+
+namespace Microsoft.IdentityModel.Tokens.Telemetry
+{
+ ///
+ /// Prepares s using the provided data and sends them to for recording.
+ ///
+ internal class TelemetryClient : ITelemetryClient
+ {
+ public string ClientVer = IdentityModelTelemetryUtil.ClientVer;
+
+ public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus)
+ {
+ var tagList = new TagList()
+ {
+ { TelemetryConstants.IdentityModelVersionTag, ClientVer },
+ { TelemetryConstants.MetadataAddressTag, metadataAddress },
+ { TelemetryConstants.OperationStatusTag, operationStatus }
+ };
+
+ TelemetryDataRecorder.IncrementConfigurationRefreshRequestCounter(tagList);
+ }
+
+ public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, Exception exception)
+ {
+ var tagList = new TagList()
+ {
+ { TelemetryConstants.IdentityModelVersionTag, ClientVer },
+ { TelemetryConstants.MetadataAddressTag, metadataAddress },
+ { TelemetryConstants.OperationStatusTag, operationStatus },
+ { TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString() }
+ };
+
+ TelemetryDataRecorder.IncrementConfigurationRefreshRequestCounter(tagList);
+ }
+
+ public void LogConfigurationRetrievalDuration(string metadataAddress, TimeSpan operationDuration)
+ {
+ var tagList = new TagList()
+ {
+ { TelemetryConstants.IdentityModelVersionTag, ClientVer },
+ { TelemetryConstants.MetadataAddressTag, metadataAddress },
+ };
+
+ long durationInMilliseconds = (long)operationDuration.TotalMilliseconds;
+ TelemetryDataRecorder.RecordConfigurationRetrievalDurationHistogram(durationInMilliseconds, tagList);
+ }
+
+ public void LogConfigurationRetrievalDuration(string metadataAddress, TimeSpan operationDuration, Exception exception)
+ {
+ var tagList = new TagList()
+ {
+ { TelemetryConstants.IdentityModelVersionTag, ClientVer },
+ { TelemetryConstants.MetadataAddressTag, metadataAddress },
+ { TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString() }
+ };
+
+ long durationInMilliseconds = (long)operationDuration.TotalMilliseconds;
+ TelemetryDataRecorder.RecordConfigurationRetrievalDurationHistogram(durationInMilliseconds, tagList);
+ }
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs
new file mode 100644
index 0000000000..2e49b8774e
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryConstants.cs
@@ -0,0 +1,67 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+namespace Microsoft.IdentityModel.Tokens.Telemetry
+{
+ internal static class TelemetryConstants
+ {
+ // Static attribute tags
+
+ ///
+ /// Telemetry tag indicating the version of the IdentityModel library.
+ ///
+ public const string IdentityModelVersionTag = "IdentityModelVersion";
+
+ ///
+ /// Telemetry tag indicating the endpoint from which a configuration is retrieved.
+ ///
+ public const string MetadataAddressTag = "MetadataAddress";
+
+ ///
+ /// Telemetry tag describing the operation being performed.
+ ///
+ public const string OperationStatusTag = "OperationStatus";
+
+ ///
+ /// Telemetry tag indicating the type of exception that occurred.
+ ///
+ public const string ExceptionTypeTag = "ExceptionType";
+
+ public static class Protocols
+ {
+ // Configuration manager refresh statuses
+
+ ///
+ /// Telemetry tag indicating configuration retrieval after the refresh interval has expired.
+ ///
+ public const string Automatic = "Automatic";
+
+ ///
+ /// Telemetry tag indicating configuration retrieval per a call to RequestRefresh.
+ ///
+ public const string Manual = "Manual";
+
+ ///
+ /// Telemetry tag indicating configuration retrieval when there is no previously cached configuration.
+ ///
+ public const string FirstRefresh = "FirstRefresh";
+
+ ///
+ /// Telemetry tag indicating configuration retrieval when the last known good configuration is needed.
+ ///
+ public const string Lkg = "LastKnownGood";
+
+ // Configuration manager exception types
+
+ ///
+ /// Telemetry tag indicating that configuration could not be sucessfully validated after retrieval.
+ ///
+ public const string ConfigurationInvalid = "ConfigurationInvalid";
+
+ ///
+ /// Telemetry tag indicating that configuration could not be retrieved successfully.
+ ///
+ public const string ConfigurationRetrievalFailed = "ConfigurationRetrievalFailed";
+ }
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs
new file mode 100644
index 0000000000..dbf449baf9
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Telemetry/TelemetryDataRecorder.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.Diagnostics;
+using System.Diagnostics.Metrics;
+
+namespace Microsoft.IdentityModel.Tokens.Telemetry
+{
+ ///
+ /// Pushes telemetry data to the configured or .
+ ///
+ internal class TelemetryDataRecorder
+ {
+ ///
+ /// Meter name for MicrosoftIdentityModel.
+ ///
+ private const string MeterName = "MicrosoftIdentityModel_Meter";
+
+ ///
+ /// The meter responsible for creating instruments.
+ ///
+ private static readonly Meter IdentityModelMeter = new(MeterName, "1.0.0");
+
+ internal const string TotalDurationHistogramName = "IdentityModelConfigurationRequestTotalDurationInMS";
+
+ ///
+ /// Counter to capture configuration refresh requests to ConfigurationManager.
+ ///
+ internal const string IdentityModelConfigurationManagerCounterName = "IdentityModelConfigurationManager";
+ internal const string IdentityModelConfigurationManagerCounterDescription = "Counter capturing configuration manager operations.";
+ internal static readonly Counter ConfigurationManagerCounter = IdentityModelMeter.CreateCounter(IdentityModelConfigurationManagerCounterName, description: IdentityModelConfigurationManagerCounterDescription);
+
+ ///
+ /// Histogram to capture total duration of configuration retrieval by ConfigurationManager in milliseconds.
+ ///
+ internal static readonly Histogram TotalDurationHistogram = IdentityModelMeter.CreateHistogram(
+ TotalDurationHistogramName,
+ unit: "ms",
+ description: "Configuration retrieval latency during configuration manager operations.");
+
+ internal static void RecordConfigurationRetrievalDurationHistogram(long requestDurationInMs, in TagList tagList)
+ {
+ TotalDurationHistogram.Record(requestDurationInMs, tagList);
+ }
+
+ internal static void IncrementConfigurationRefreshRequestCounter(in TagList tagList)
+ {
+ ConfigurationManagerCounter.Add(1, tagList);
+ }
+ }
+}
diff --git a/src/System.IdentityModel.Tokens.Jwt/InternalAPI.Unshipped.txt b/src/System.IdentityModel.Tokens.Jwt/InternalAPI.Unshipped.txt
index 47d5d10352..bfbb59b541 100644
--- a/src/System.IdentityModel.Tokens.Jwt/InternalAPI.Unshipped.txt
+++ b/src/System.IdentityModel.Tokens.Jwt/InternalAPI.Unshipped.txt
@@ -1,2 +1,3 @@
System.IdentityModel.Tokens.Jwt.JwtHeader.JwtHeader(Microsoft.IdentityModel.Tokens.EncryptingCredentials encryptingCredentials, System.Collections.Generic.IDictionary outboundAlgorithmMap, string tokenType, System.Collections.Generic.IDictionary additionalHeaderClaims, bool includeKeyIdInHeader) -> void
-System.IdentityModel.Tokens.Jwt.JwtHeader.JwtHeader(Microsoft.IdentityModel.Tokens.SigningCredentials signingCredentials, System.Collections.Generic.IDictionary outboundAlgorithmMap, string tokenType, System.Collections.Generic.IDictionary additionalInnerHeaderClaims, bool includeKeyIdInHeader) -> void
\ No newline at end of file
+System.IdentityModel.Tokens.Jwt.JwtHeader.JwtHeader(Microsoft.IdentityModel.Tokens.SigningCredentials signingCredentials, System.Collections.Generic.IDictionary outboundAlgorithmMap, string tokenType, System.Collections.Generic.IDictionary additionalInnerHeaderClaims, bool includeKeyIdInHeader) -> void
+System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.TelemetryClient -> Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient
diff --git a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs
index f108b9dbf5..23ab48108d 100644
--- a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs
+++ b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs
@@ -15,6 +15,7 @@
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;
+using Microsoft.IdentityModel.Tokens.Telemetry;
using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;
namespace System.IdentityModel.Tokens.Jwt
@@ -36,6 +37,8 @@ public class JwtSecurityTokenHandler : SecurityTokenHandler
private static string _shortClaimType = _namespace + "/ShortTypeName";
private bool _mapInboundClaims = DefaultMapInboundClaims;
+ internal Microsoft.IdentityModel.Tokens.Telemetry.ITelemetryClient TelemetryClient = new TelemetryClient();
+
///
/// Default claim type mapping for inbound claims.
///
@@ -887,6 +890,10 @@ private ClaimsPrincipal ValidateToken(string token, JwtSecurityToken outerToken,
// where a new valid configuration was somehow published during validation time.
if (currentConfiguration != null)
{
+ TelemetryClient.IncrementConfigurationRefreshRequestCounter(
+ validationParameters.ConfigurationManager.MetadataAddress,
+ TelemetryConstants.Protocols.Lkg);
+
validationParameters.ConfigurationManager.RequestRefresh();
validationParameters.RefreshBeforeValidation = true;
var lastConfig = currentConfiguration;
diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTelemetryTests.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTelemetryTests.cs
new file mode 100644
index 0000000000..556e182a41
--- /dev/null
+++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandlerTelemetryTests.cs
@@ -0,0 +1,69 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.IdentityModel.Logging;
+using Microsoft.IdentityModel.Protocols;
+using Microsoft.IdentityModel.Protocols.OpenIdConnect;
+using Microsoft.IdentityModel.TestUtils;
+using Microsoft.IdentityModel.Tokens;
+using Microsoft.IdentityModel.Tokens.Telemetry;
+using Microsoft.IdentityModel.Tokens.Telemetry.Tests;
+using Microsoft.IdentityModel.Validators;
+using Xunit;
+
+namespace Microsoft.IdentityModel.JsonWebTokens.Tests
+{
+ public class JsonWebTokenHandlerTelemetryTests
+ {
+ [Fact]
+ public async Task ValidateJwsWithConfigAsync_ExpectedTagsExist()
+ {
+ var invalidIssuerConfig = new OpenIdConnectConfiguration()
+ {
+ TokenEndpoint = Default.Issuer + "oauth/token",
+ Issuer = Default.Issuer + "2"
+ };
+ invalidIssuerConfig.SigningKeys.Add(KeyingMaterial.DefaultX509Key_2048);
+
+ var validationParameters = new TokenValidationParameters
+ {
+ ConfigurationManager = new StaticConfigurationManager(invalidIssuerConfig),
+ ValidateIssuerSigningKey = true,
+ RequireSignedTokens = true,
+ ValidateIssuer = true,
+ ValidateAudience = false,
+ ValidateLifetime = false
+ };
+
+ var testTelemetryClient = new MockTelemetryClient();
+ try
+ {
+ var handler = new JsonWebTokenHandler()
+ {
+ _telemetryClient = testTelemetryClient
+ };
+ var jwt = handler.ReadJsonWebToken(Default.AsymmetricJws);
+ AadIssuerValidator.GetAadIssuerValidator(Default.AadV1Authority).ConfigurationManagerV1 = validationParameters.ConfigurationManager;
+ var validationResult = await handler.ValidateTokenAsync(jwt, validationParameters);
+ var rawTokenValidationResult = await handler.ValidateTokenAsync(Default.AsymmetricJws, validationParameters);
+ }
+ catch (Exception)
+ {
+ // ignore exceptions
+ }
+
+ var expectedCounterTagList = new Dictionary
+ {
+ // metadata address is null because the configuration manager is made using an invalid config to trigger an exception
+ { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer },
+ { TelemetryConstants.MetadataAddressTag, null },
+ { TelemetryConstants.OperationStatusTag, TelemetryConstants.Protocols.Lkg }
+ };
+
+ Assert.Equal(expectedCounterTagList, testTelemetryClient.ExportedItems);
+ }
+ }
+}
diff --git a/test/Microsoft.IdentityModel.Logging.Tests/Microsoft.IdentityModel.Logging.Tests.csproj b/test/Microsoft.IdentityModel.Logging.Tests/Microsoft.IdentityModel.Logging.Tests.csproj
index 0d29cd28cd..6ed8578f22 100644
--- a/test/Microsoft.IdentityModel.Logging.Tests/Microsoft.IdentityModel.Logging.Tests.csproj
+++ b/test/Microsoft.IdentityModel.Logging.Tests/Microsoft.IdentityModel.Logging.Tests.csproj
@@ -23,6 +23,8 @@
+
+
diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs
new file mode 100644
index 0000000000..6d61e16b19
--- /dev/null
+++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTelemetryTests.cs
@@ -0,0 +1,164 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.IdentityModel.Logging;
+using Microsoft.IdentityModel.Protocols.Configuration;
+using Microsoft.IdentityModel.Protocols.OpenIdConnect.Configuration;
+using Microsoft.IdentityModel.TestUtils;
+using Microsoft.IdentityModel.Tokens.Telemetry;
+using Microsoft.IdentityModel.Tokens.Telemetry.Tests;
+using Xunit;
+
+namespace Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests
+{
+ public class ConfigurationManagerTelemetryTests
+ {
+ [Fact]
+ public async Task RequestRefresh_ExpectedTagsExist()
+ {
+ // arrange
+ var testTelemetryClient = new MockTelemetryClient();
+ var configurationManager = new ConfigurationManager(
+ OpenIdConfigData.AccountsGoogle,
+ new OpenIdConnectConfigurationRetriever(),
+ new HttpDocumentRetriever(),
+ new OpenIdConnectConfigurationValidator())
+ {
+ TelemetryClient = testTelemetryClient
+ };
+
+ // act
+ configurationManager.RequestRefresh();
+ //// Wait for UpdateCurrentConfiguration to complete
+ while (TestUtilities.GetField(configurationManager, "_currentConfiguration") == null)
+ {
+ await Task.Delay(100);
+ }
+
+ // assert
+ var expectedCounterTagList = new Dictionary
+ {
+ { TelemetryConstants.MetadataAddressTag, OpenIdConfigData.AccountsGoogle },
+ { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer },
+ { TelemetryConstants.OperationStatusTag, TelemetryConstants.Protocols.Manual },
+ };
+
+ var expectedHistogramTagList = new Dictionary
+ {
+ { TelemetryConstants.MetadataAddressTag, OpenIdConfigData.AccountsGoogle },
+ { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer }
+ };
+
+ Assert.Equal(expectedCounterTagList, testTelemetryClient.ExportedItems);
+ Assert.Equal(expectedHistogramTagList, testTelemetryClient.ExportedHistogramItems);
+ }
+
+ [Theory, MemberData(nameof(GetConfiguration_ExpectedTagList_TheoryData), DisableDiscoveryEnumeration = true)]
+ public async Task GetConfigurationAsync_ExpectedTagsExist(ConfigurationManagerTelemetryTheoryData theoryData)
+ {
+ var testTelemetryClient = new MockTelemetryClient();
+
+ var configurationManager = new ConfigurationManager(
+ theoryData.MetadataAddress,
+ new OpenIdConnectConfigurationRetriever(),
+ theoryData.DocumentRetriever,
+ theoryData.ConfigurationValidator)
+ {
+ TelemetryClient = testTelemetryClient
+ };
+
+ try
+ {
+ await configurationManager.GetConfigurationAsync();
+ if (theoryData.SyncAfter != null)
+ {
+ testTelemetryClient.ClearExportedItems();
+ TestUtilities.SetField(configurationManager, "_syncAfter", theoryData.SyncAfter);
+ await configurationManager.GetConfigurationAsync();
+ }
+
+ }
+ catch (Exception)
+ {
+ // Ignore exceptions
+ }
+
+ Assert.Equal(theoryData.ExpectedTagList, testTelemetryClient.ExportedItems);
+ }
+
+ public static TheoryData> GetConfiguration_ExpectedTagList_TheoryData()
+ {
+ return new TheoryData>
+ {
+ new ConfigurationManagerTelemetryTheoryData("Success-retrieve from endpoint")
+ {
+ MetadataAddress = OpenIdConfigData.AccountsGoogle,
+ ConfigurationValidator = new OpenIdConnectConfigurationValidator(),
+ ExpectedTagList = new Dictionary
+ {
+ { TelemetryConstants.MetadataAddressTag, OpenIdConfigData.AccountsGoogle },
+ { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer },
+ { TelemetryConstants.OperationStatusTag, TelemetryConstants.Protocols.FirstRefresh },
+ }
+ },
+ new ConfigurationManagerTelemetryTheoryData("Failure-invalid metadata address")
+ {
+ MetadataAddress = OpenIdConfigData.HttpsBadUri,
+ ConfigurationValidator = new OpenIdConnectConfigurationValidator(),
+ ExpectedTagList = new Dictionary
+ {
+ { TelemetryConstants.MetadataAddressTag, OpenIdConfigData.HttpsBadUri },
+ { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer },
+ { TelemetryConstants.OperationStatusTag, TelemetryConstants.Protocols.FirstRefresh },
+ { TelemetryConstants.ExceptionTypeTag, new IOException().GetType().ToString() },
+ }
+ },
+ new ConfigurationManagerTelemetryTheoryData("Failure-invalid config")
+ {
+ MetadataAddress = OpenIdConfigData.JsonFile,
+ DocumentRetriever = new FileDocumentRetriever(),
+ // The config being loaded has two keys; require three to force invalidity
+ ConfigurationValidator = new OpenIdConnectConfigurationValidator() { MinimumNumberOfKeys = 3 },
+ ExpectedTagList = new Dictionary
+ {
+ { TelemetryConstants.MetadataAddressTag, OpenIdConfigData.JsonFile },
+ { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer },
+ { TelemetryConstants.OperationStatusTag, TelemetryConstants.Protocols.FirstRefresh },
+ { TelemetryConstants.ExceptionTypeTag, new InvalidConfigurationException().GetType().ToString() },
+ }
+ },
+ new ConfigurationManagerTelemetryTheoryData("Success-refresh")
+ {
+ MetadataAddress = OpenIdConfigData.AADCommonUrl,
+ ConfigurationValidator = new OpenIdConnectConfigurationValidator(),
+ SyncAfter = DateTime.UtcNow - TimeSpan.FromDays(2),
+ ExpectedTagList = new Dictionary
+ {
+ { TelemetryConstants.MetadataAddressTag, OpenIdConfigData.AADCommonUrl },
+ { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer },
+ { TelemetryConstants.OperationStatusTag, TelemetryConstants.Protocols.Automatic },
+ }
+ },
+ };
+ }
+ }
+
+ public class ConfigurationManagerTelemetryTheoryData : TheoryDataBase where T : class
+ {
+ public ConfigurationManagerTelemetryTheoryData(string testId) : base(testId) { }
+
+ public string MetadataAddress { get; set; }
+
+ public IDocumentRetriever DocumentRetriever { get; set; } = new HttpDocumentRetriever();
+
+ public IConfigurationValidator ConfigurationValidator { get; set; }
+
+ public DateTimeOffset? SyncAfter { get; set; } = null;
+
+ public Dictionary ExpectedTagList { get; set; }
+ }
+}
diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests.csproj b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests.csproj
index 1cbfb9eed3..c7913cbd45 100644
--- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests.csproj
+++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests.csproj
@@ -19,14 +19,15 @@
+
-
-
+
+
diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs
new file mode 100644
index 0000000000..c7cd1b71fc
--- /dev/null
+++ b/test/Microsoft.IdentityModel.Tokens.Tests/Telemetry/MockTelemetryClient.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.IdentityModel.Logging;
+
+namespace Microsoft.IdentityModel.Tokens.Telemetry.Tests
+{
+ public class MockTelemetryClient : ITelemetryClient
+ {
+ public Dictionary ExportedItems = new Dictionary();
+ public Dictionary ExportedHistogramItems = new Dictionary();
+
+ public void ClearExportedItems()
+ {
+ ExportedItems.Clear();
+ }
+
+ public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus)
+ {
+ ExportedItems.Add(TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer);
+ ExportedItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress);
+ ExportedItems.Add(TelemetryConstants.OperationStatusTag, operationStatus);
+ }
+
+ public void IncrementConfigurationRefreshRequestCounter(string metadataAddress, string operationStatus, Exception exception)
+ {
+ ExportedItems.Add(TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer);
+ ExportedItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress);
+ ExportedItems.Add(TelemetryConstants.OperationStatusTag, operationStatus);
+ ExportedItems.Add(TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString());
+ }
+
+ public void LogConfigurationRetrievalDuration(string metadataAddress, TimeSpan operationDuration)
+ {
+ ExportedHistogramItems.Add(TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer);
+ ExportedHistogramItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress);
+ }
+
+ public void LogConfigurationRetrievalDuration(string metadataAddress, TimeSpan operationDuration, Exception exception)
+ {
+ ExportedHistogramItems.Add(TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer);
+ ExportedHistogramItems.Add(TelemetryConstants.MetadataAddressTag, metadataAddress);
+ ExportedHistogramItems.Add(TelemetryConstants.ExceptionTypeTag, exception.GetType().ToString());
+ }
+ }
+}
diff --git a/test/System.IdentityModel.Tokens.Jwt.Tests/JwtSecurityTokenHandlerTelemetryTests.cs b/test/System.IdentityModel.Tokens.Jwt.Tests/JwtSecurityTokenHandlerTelemetryTests.cs
new file mode 100644
index 0000000000..f181c00961
--- /dev/null
+++ b/test/System.IdentityModel.Tokens.Jwt.Tests/JwtSecurityTokenHandlerTelemetryTests.cs
@@ -0,0 +1,65 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.Collections.Generic;
+using Microsoft.IdentityModel.Logging;
+using Microsoft.IdentityModel.Protocols;
+using Microsoft.IdentityModel.Protocols.OpenIdConnect;
+using Microsoft.IdentityModel.TestUtils;
+using Microsoft.IdentityModel.Tokens;
+using Microsoft.IdentityModel.Tokens.Telemetry;
+using Microsoft.IdentityModel.Tokens.Telemetry.Tests;
+using Microsoft.IdentityModel.Validators;
+using Xunit;
+
+namespace System.IdentityModel.Tokens.Jwt.Tests
+{
+ public class JwtSecurityTokenHandlerTelemetryTests
+ {
+ [Fact]
+ public void ValidateToken_ExpectedTagsExist()
+ {
+ var invalidIssuerConfig = new OpenIdConnectConfiguration()
+ {
+ TokenEndpoint = Default.Issuer + "oauth/token",
+ Issuer = Default.Issuer + "2"
+ };
+ invalidIssuerConfig.SigningKeys.Add(KeyingMaterial.DefaultX509Key_2048);
+
+ var validationParameters = new TokenValidationParameters
+ {
+ ConfigurationManager = new StaticConfigurationManager(invalidIssuerConfig),
+ ValidateIssuerSigningKey = true,
+ RequireSignedTokens = true,
+ ValidateIssuer = true,
+ ValidateAudience = false,
+ ValidateLifetime = false
+ };
+
+ var testTelemetryClient = new MockTelemetryClient();
+ try
+ {
+ AadIssuerValidator.GetAadIssuerValidator(Default.AadV1Authority).ConfigurationManagerV1 = validationParameters.ConfigurationManager;
+ var handler = new JwtSecurityTokenHandler()
+ {
+ TelemetryClient = testTelemetryClient
+ };
+ handler.ValidateToken(Default.AsymmetricJws, validationParameters, out _);
+ }
+ catch (Exception)
+ {
+ // ignore exceptions
+ }
+
+ var expectedCounterTagList = new Dictionary
+ {
+ // metadata address is null because the configuration manager is made using an invalid config to trigger an exception
+ { TelemetryConstants.IdentityModelVersionTag, IdentityModelTelemetryUtil.ClientVer },
+ { TelemetryConstants.MetadataAddressTag, null },
+ { TelemetryConstants.OperationStatusTag, TelemetryConstants.Protocols.Lkg },
+ };
+
+ Assert.Equal(expectedCounterTagList, testTelemetryClient.ExportedItems);
+ }
+ }
+}
diff --git a/test/System.IdentityModel.Tokens.Jwt.Tests/System.IdentityModel.Tokens.Jwt.Tests.csproj b/test/System.IdentityModel.Tokens.Jwt.Tests/System.IdentityModel.Tokens.Jwt.Tests.csproj
index e68913b3b0..55c2caee3c 100644
--- a/test/System.IdentityModel.Tokens.Jwt.Tests/System.IdentityModel.Tokens.Jwt.Tests.csproj
+++ b/test/System.IdentityModel.Tokens.Jwt.Tests/System.IdentityModel.Tokens.Jwt.Tests.csproj
@@ -13,8 +13,10 @@
+
+