From cea9357e7872121f0bb8f0f91d8b3310a5dd6b45 Mon Sep 17 00:00:00 2001 From: Sergei Smelov Date: Fri, 10 Apr 2026 16:05:01 +0200 Subject: [PATCH 1/6] Add raw STS error code to MsalFailure metric --- .../AppConfig/ApplicationConfiguration.cs | 2 ++ .../BaseAbstractApplicationBuilder.cs | 15 ++++++++- .../AppConfig/IAppConfig.cs | 11 +++++-- .../Internal/Requests/RequestBase.cs | 9 ++++-- .../Internal/Requests/SilentRequestHelper.cs | 3 +- .../OpenTelemetry/OtelInstrumentation.cs | 31 ++++++++++++------- .../PublicApi/net462/PublicAPI.Unshipped.txt | 2 ++ .../PublicApi/net472/PublicAPI.Unshipped.txt | 2 ++ .../net8.0-android/PublicAPI.Unshipped.txt | 2 ++ .../net8.0-ios/PublicAPI.Unshipped.txt | 2 ++ .../PublicApi/net8.0/PublicAPI.Unshipped.txt | 2 ++ .../netstandard2.0/PublicAPI.Unshipped.txt | 2 ++ .../OpenTelemetry/IOtelInstrumentation.cs | 7 +++-- .../OpenTelemetry/NullOtelInstrumentation.cs | 9 +++--- .../TelemetryCore/TelemetryConstants.cs | 2 +- 15 files changed, 75 insertions(+), 26 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs index 4364ad143f..0e71e67483 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs @@ -112,6 +112,8 @@ public string ClientVersion public bool ExperimentalFeaturesEnabled { get; set; } = false; + public bool EnableStsRawErrorCodeTelemetry { get; set; } = false; + public IEnumerable ClientCapabilities { get; set; } public bool SendX5C { get; internal set; } = false; public bool LegacyCacheCompatibilityEnabled { get; internal set; } = true; diff --git a/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs b/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs index ea20f7ee20..eeb8eda9d1 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs @@ -208,7 +208,7 @@ protected T WithOptions(BaseApplicationOptions applicationOptions) } /// - /// Allows usage of experimental features and APIs. If this flag is not set, experimental features + /// Allows usage of experimental features and APIs. If this flag is not set, experimental features /// will throw an exception. For details see https://aka.ms/msal-net-experimental-features /// /// @@ -221,6 +221,19 @@ public T WithExperimentalFeatures(bool enableExperimentalFeatures = true) return (T)this; } + /// + /// When enabled, the ESTS raw error code from the identity provider response is included as the + /// StsRawErrorCode tag on the MsalFailure OpenTelemetry counter. Opt-in only, because + /// ESTS error codes can be high-cardinality and may increase metric storage costs. + /// + /// Set to true to include the tag; false to exclude it. Default is true. + /// The builder to chain the .With methods + public T WithStsRawErrorCodeTelemetry(bool enable = true) + { + Config.EnableStsRawErrorCodeTelemetry = enable; + return (T)this; + } + /// /// Sets the name of the calling SDK API for telemetry purposes. /// diff --git a/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs b/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs index 50c31b3c2f..ba23f61e45 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs @@ -89,12 +89,19 @@ public interface IAppConfig ITelemetryConfig TelemetryConfig { get; } /// - /// Allows usage of features that are experimental and would otherwise throw a specific exception. - /// Use of experimental features in production is not recommended and are subject to be removed between builds. + /// Allows usage of features that are experimental and would otherwise throw a specific exception. + /// Use of experimental features in production is not recommended and are subject to be removed between builds. /// For details see https://aka.ms/msal-net-experimental-features. /// bool ExperimentalFeaturesEnabled { get; } + /// + /// When enabled, the ESTS raw error code returned in the error_codes array of the identity + /// provider response is included as the StsRawErrorCode tag on the MsalFailure OpenTelemetry + /// counter. Disabled by default to avoid unintended metric-cardinality growth. + /// + bool EnableStsRawErrorCodeTelemetry { get; } + /// /// Microsoft Identity specific OIDC extension that allows resource challenges to be resolved without interaction. /// Allows configuration of one or more client capabilities, e.g. "llt" diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index c8269bb7cd..50f2161e6e 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -106,7 +106,9 @@ public async Task RunAsync(CancellationToken cancellationT } AuthenticationRequestParameters.RequestContext.Logger.ErrorPii(ex); - LogFailureTelemetryToOtel(ex.ErrorCode, apiEvent, apiEvent.CacheInfo); + LogFailureTelemetryToOtel( + ex.ErrorCode, apiEvent, apiEvent.CacheInfo, + ServiceBundle.Config.EnableStsRawErrorCodeTelemetry ? (ex as MsalServiceException)?.ErrorCodes?[0] : null); throw; } catch (Exception ex) @@ -133,7 +135,7 @@ private void LogSuccessTelemetryToOtel(AuthenticationResult authenticationResult AuthenticationRequestParameters.RequestContext.Logger); } - private void LogFailureTelemetryToOtel(string errorCodeToLog, ApiEvent apiEvent, CacheRefreshReason cacheRefreshReason) + private void LogFailureTelemetryToOtel(string errorCodeToLog, ApiEvent apiEvent, CacheRefreshReason cacheRefreshReason, string stsRawErrorCode = null) { // Log metrics ServiceBundle.PlatformProxy.OtelInstrumentation.LogFailureMetrics( @@ -143,7 +145,8 @@ private void LogFailureTelemetryToOtel(string errorCodeToLog, ApiEvent apiEvent, apiEvent.CallerSdkApiId, apiEvent.CallerSdkVersion, cacheRefreshReason, - apiEvent.TokenType); + apiEvent.TokenType, + stsRawErrorCode); } private Tuple ParseScopesForTelemetry() diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs index b18eb75a29..d8f3e3aeb8 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs @@ -124,7 +124,8 @@ internal static void ProcessFetchInBackground( callerSdkId, callerSdkVersion, CacheRefreshReason.ProactivelyRefreshed, - apiEvent.TokenType); + apiEvent.TokenType, + serviceBundle.Config.EnableStsRawErrorCodeTelemetry ? ex.ErrorCodes?[0] : null); } catch (OperationCanceledException ex) { diff --git a/src/client/Microsoft.Identity.Client/Platforms/Features/OpenTelemetry/OtelInstrumentation.cs b/src/client/Microsoft.Identity.Client/Platforms/Features/OpenTelemetry/OtelInstrumentation.cs index 51f94757e6..ffb5d35b5a 100644 --- a/src/client/Microsoft.Identity.Client/Platforms/Features/OpenTelemetry/OtelInstrumentation.cs +++ b/src/client/Microsoft.Identity.Client/Platforms/Features/OpenTelemetry/OtelInstrumentation.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; +using System.Diagnostics; using System.Diagnostics.Metrics; using Microsoft.Identity.Client.Cache; using Microsoft.Identity.Client.Core; @@ -204,19 +205,27 @@ public void LogFailureMetrics(string platform, string callerSdkId, string callerSdkVersion, CacheRefreshReason cacheRefreshReason, - int tokenType) + int tokenType, + string stsRawErrorCode = null) { - if (s_failureCounter.Value.Enabled) + if (!s_failureCounter.Value.Enabled) + return; + + var tags = new TagList { - s_failureCounter.Value.Add(1, - new(TelemetryConstants.MsalVersion, MsalIdHelper.GetMsalVersion()), - new(TelemetryConstants.Platform, platform), - new(TelemetryConstants.ErrorCode, errorCode), - new(TelemetryConstants.ApiId, apiId), - new(TelemetryConstants.CallerSdkId, callerSdkId ?? string.Empty + "," + callerSdkVersion ?? string.Empty), - new(TelemetryConstants.CacheRefreshReason, cacheRefreshReason), - new(TelemetryConstants.TokenType, tokenType)); - } + { TelemetryConstants.MsalVersion, MsalIdHelper.GetMsalVersion() }, + { TelemetryConstants.Platform, platform }, + { TelemetryConstants.ErrorCode, errorCode }, + { TelemetryConstants.ApiId, apiId }, + { TelemetryConstants.CallerSdkId, callerSdkId ?? string.Empty + "," + callerSdkVersion ?? string.Empty }, + { TelemetryConstants.CacheRefreshReason, cacheRefreshReason }, + { TelemetryConstants.TokenType, tokenType } + }; + + if (!string.IsNullOrEmpty(stsRawErrorCode)) + tags.Add(TelemetryConstants.StsRawErrorCode, stsRawErrorCode); + + s_failureCounter.Value.Add(1, in tags); } } } diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt index e69de29bb2..0f291c500c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithStsRawErrorCodeTelemetry(bool enable = true) -> T +Microsoft.Identity.Client.IAppConfig.EnableStsRawErrorCodeTelemetry.get -> bool diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt index e69de29bb2..0f291c500c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithStsRawErrorCodeTelemetry(bool enable = true) -> T +Microsoft.Identity.Client.IAppConfig.EnableStsRawErrorCodeTelemetry.get -> bool diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt index e69de29bb2..0f291c500c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithStsRawErrorCodeTelemetry(bool enable = true) -> T +Microsoft.Identity.Client.IAppConfig.EnableStsRawErrorCodeTelemetry.get -> bool diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt index e69de29bb2..0f291c500c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithStsRawErrorCodeTelemetry(bool enable = true) -> T +Microsoft.Identity.Client.IAppConfig.EnableStsRawErrorCodeTelemetry.get -> bool diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt index e69de29bb2..0f291c500c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithStsRawErrorCodeTelemetry(bool enable = true) -> T +Microsoft.Identity.Client.IAppConfig.EnableStsRawErrorCodeTelemetry.get -> bool diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt index e69de29bb2..0f291c500c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithStsRawErrorCodeTelemetry(bool enable = true) -> T +Microsoft.Identity.Client.IAppConfig.EnableStsRawErrorCodeTelemetry.get -> bool diff --git a/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/IOtelInstrumentation.cs b/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/IOtelInstrumentation.cs index 7802ab4211..7cb948b60d 100644 --- a/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/IOtelInstrumentation.cs +++ b/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/IOtelInstrumentation.cs @@ -31,12 +31,13 @@ internal void IncrementSuccessCounter(string platform, ILoggerAdapter logger, int TokenType); - internal void LogFailureMetrics(string platform, - string errorCode, + internal void LogFailureMetrics(string platform, + string errorCode, ApiEvent.ApiIds apiId, string callerSdkId, string callerSdkVersion, CacheRefreshReason cacheRefreshReason, - int tokenType); + int tokenType, + string stsRawErrorCode = null); } } diff --git a/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/NullOtelInstrumentation.cs b/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/NullOtelInstrumentation.cs index 7fcdbbcf9c..c70e524eb3 100644 --- a/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/NullOtelInstrumentation.cs +++ b/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/NullOtelInstrumentation.cs @@ -27,13 +27,14 @@ public void LogSuccessMetrics( // No op } - public void LogFailureMetrics(string platform, - string errorCode, - ApiEvent.ApiIds apiId, + public void LogFailureMetrics(string platform, + string errorCode, + ApiEvent.ApiIds apiId, string callerSdkId, string callerSdkVersion, CacheRefreshReason cacheRefreshReason, - int tokenType) + int tokenType, + string stsRawErrorCode = null) { // No op } diff --git a/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryConstants.cs b/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryConstants.cs index bed6e7db55..e6b987cb7d 100644 --- a/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryConstants.cs +++ b/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryConstants.cs @@ -26,7 +26,7 @@ internal static class TelemetryConstants public const string CacheInfoTelemetry = "CacheInfoTelemetry"; public const string CacheRefreshReason = "CacheRefreshReason"; public const string ErrorCode = "ErrorCode"; - public const string StsErrorCode = "StsErrorCode"; + public const string StsRawErrorCode = "StsRawErrorCode"; public const string ErrorMessage = "ErrorMessage"; public const string Duration = "Duration"; public const string DurationInUs = "DurationInUs"; From eb331c7044a966a804130195527cbf484112f24d Mon Sep 17 00:00:00 2001 From: Sergei Smelov Date: Fri, 10 Apr 2026 22:30:16 +0200 Subject: [PATCH 2/6] Update --- .../AppConfig/ApplicationConfiguration.cs | 2 +- .../BaseAbstractApplicationBuilder.cs | 8 +- .../AppConfig/IAppConfig.cs | 6 +- .../Internal/Requests/RequestBase.cs | 6 +- .../Internal/Requests/SilentRequestHelper.cs | 2 +- .../OpenTelemetry/OtelInstrumentation.cs | 6 +- .../PublicApi/net462/PublicAPI.Unshipped.txt | 4 +- .../PublicApi/net472/PublicAPI.Unshipped.txt | 4 +- .../net8.0-android/PublicAPI.Unshipped.txt | 4 +- .../net8.0-ios/PublicAPI.Unshipped.txt | 4 +- .../PublicApi/net8.0/PublicAPI.Unshipped.txt | 4 +- .../netstandard2.0/PublicAPI.Unshipped.txt | 4 +- .../OpenTelemetry/IOtelInstrumentation.cs | 2 +- .../OpenTelemetry/NullOtelInstrumentation.cs | 2 +- .../TelemetryCore/TelemetryConstants.cs | 2 +- .../OTelInstrumentationTests.cs | 111 +++++++++++++++++- 16 files changed, 140 insertions(+), 31 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs index 0e71e67483..97cb22e1c9 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs @@ -112,7 +112,7 @@ public string ClientVersion public bool ExperimentalFeaturesEnabled { get; set; } = false; - public bool EnableStsRawErrorCodeTelemetry { get; set; } = false; + public bool EnableRawStsErrorCodeTelemetry { get; set; } = false; public IEnumerable ClientCapabilities { get; set; } public bool SendX5C { get; internal set; } = false; diff --git a/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs b/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs index eeb8eda9d1..6d15eb0f43 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs @@ -222,15 +222,15 @@ public T WithExperimentalFeatures(bool enableExperimentalFeatures = true) } /// - /// When enabled, the ESTS raw error code from the identity provider response is included as the - /// StsRawErrorCode tag on the MsalFailure OpenTelemetry counter. Opt-in only, because + /// When enabled, the raw ESTS error code from the identity provider response is included as the + /// RawStsErrorCode tag on the MsalFailure OpenTelemetry counter. Opt-in only, because /// ESTS error codes can be high-cardinality and may increase metric storage costs. /// /// Set to true to include the tag; false to exclude it. Default is true. /// The builder to chain the .With methods - public T WithStsRawErrorCodeTelemetry(bool enable = true) + public T WithRawStsErrorCodeTelemetry(bool enable = true) { - Config.EnableStsRawErrorCodeTelemetry = enable; + Config.EnableRawStsErrorCodeTelemetry = enable; return (T)this; } diff --git a/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs b/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs index ba23f61e45..de08de8bb7 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs @@ -96,11 +96,11 @@ public interface IAppConfig bool ExperimentalFeaturesEnabled { get; } /// - /// When enabled, the ESTS raw error code returned in the error_codes array of the identity - /// provider response is included as the StsRawErrorCode tag on the MsalFailure OpenTelemetry + /// When enabled, the raw ESTS error code returned in the error_codes array of the identity + /// provider response is included as the RawStsErrorCode tag on the MsalFailure OpenTelemetry /// counter. Disabled by default to avoid unintended metric-cardinality growth. /// - bool EnableStsRawErrorCodeTelemetry { get; } + bool EnableRawStsErrorCodeTelemetry { get; } /// /// Microsoft Identity specific OIDC extension that allows resource challenges to be resolved without interaction. diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index 50f2161e6e..911018ea2e 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -108,7 +108,7 @@ public async Task RunAsync(CancellationToken cancellationT LogFailureTelemetryToOtel( ex.ErrorCode, apiEvent, apiEvent.CacheInfo, - ServiceBundle.Config.EnableStsRawErrorCodeTelemetry ? (ex as MsalServiceException)?.ErrorCodes?[0] : null); + ServiceBundle.Config.EnableRawStsErrorCodeTelemetry ? (ex as MsalServiceException)?.ErrorCodes?[0] : null); throw; } catch (Exception ex) @@ -135,7 +135,7 @@ private void LogSuccessTelemetryToOtel(AuthenticationResult authenticationResult AuthenticationRequestParameters.RequestContext.Logger); } - private void LogFailureTelemetryToOtel(string errorCodeToLog, ApiEvent apiEvent, CacheRefreshReason cacheRefreshReason, string stsRawErrorCode = null) + private void LogFailureTelemetryToOtel(string errorCodeToLog, ApiEvent apiEvent, CacheRefreshReason cacheRefreshReason, string rawStsErrorCode = null) { // Log metrics ServiceBundle.PlatformProxy.OtelInstrumentation.LogFailureMetrics( @@ -146,7 +146,7 @@ private void LogFailureTelemetryToOtel(string errorCodeToLog, ApiEvent apiEvent, apiEvent.CallerSdkVersion, cacheRefreshReason, apiEvent.TokenType, - stsRawErrorCode); + rawStsErrorCode); } private Tuple ParseScopesForTelemetry() diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs index d8f3e3aeb8..64d4fdb01f 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs @@ -125,7 +125,7 @@ internal static void ProcessFetchInBackground( callerSdkVersion, CacheRefreshReason.ProactivelyRefreshed, apiEvent.TokenType, - serviceBundle.Config.EnableStsRawErrorCodeTelemetry ? ex.ErrorCodes?[0] : null); + serviceBundle.Config.EnableRawStsErrorCodeTelemetry ? ex.ErrorCodes?[0] : null); } catch (OperationCanceledException ex) { diff --git a/src/client/Microsoft.Identity.Client/Platforms/Features/OpenTelemetry/OtelInstrumentation.cs b/src/client/Microsoft.Identity.Client/Platforms/Features/OpenTelemetry/OtelInstrumentation.cs index ffb5d35b5a..ac4a68e2ee 100644 --- a/src/client/Microsoft.Identity.Client/Platforms/Features/OpenTelemetry/OtelInstrumentation.cs +++ b/src/client/Microsoft.Identity.Client/Platforms/Features/OpenTelemetry/OtelInstrumentation.cs @@ -206,7 +206,7 @@ public void LogFailureMetrics(string platform, string callerSdkVersion, CacheRefreshReason cacheRefreshReason, int tokenType, - string stsRawErrorCode = null) + string rawStsErrorCode = null) { if (!s_failureCounter.Value.Enabled) return; @@ -222,8 +222,8 @@ public void LogFailureMetrics(string platform, { TelemetryConstants.TokenType, tokenType } }; - if (!string.IsNullOrEmpty(stsRawErrorCode)) - tags.Add(TelemetryConstants.StsRawErrorCode, stsRawErrorCode); + if (!string.IsNullOrEmpty(rawStsErrorCode)) + tags.Add(TelemetryConstants.RawStsErrorCode, rawStsErrorCode); s_failureCounter.Value.Add(1, in tags); } diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt index 0f291c500c..191cb50b13 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt @@ -1,2 +1,2 @@ -Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithStsRawErrorCodeTelemetry(bool enable = true) -> T -Microsoft.Identity.Client.IAppConfig.EnableStsRawErrorCodeTelemetry.get -> bool +Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithRawStsErrorCodeTelemetry(bool enable = true) -> T +Microsoft.Identity.Client.IAppConfig.EnableRawStsErrorCodeTelemetry.get -> bool diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt index 0f291c500c..191cb50b13 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt @@ -1,2 +1,2 @@ -Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithStsRawErrorCodeTelemetry(bool enable = true) -> T -Microsoft.Identity.Client.IAppConfig.EnableStsRawErrorCodeTelemetry.get -> bool +Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithRawStsErrorCodeTelemetry(bool enable = true) -> T +Microsoft.Identity.Client.IAppConfig.EnableRawStsErrorCodeTelemetry.get -> bool diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt index 0f291c500c..191cb50b13 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt @@ -1,2 +1,2 @@ -Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithStsRawErrorCodeTelemetry(bool enable = true) -> T -Microsoft.Identity.Client.IAppConfig.EnableStsRawErrorCodeTelemetry.get -> bool +Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithRawStsErrorCodeTelemetry(bool enable = true) -> T +Microsoft.Identity.Client.IAppConfig.EnableRawStsErrorCodeTelemetry.get -> bool diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt index 0f291c500c..191cb50b13 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt @@ -1,2 +1,2 @@ -Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithStsRawErrorCodeTelemetry(bool enable = true) -> T -Microsoft.Identity.Client.IAppConfig.EnableStsRawErrorCodeTelemetry.get -> bool +Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithRawStsErrorCodeTelemetry(bool enable = true) -> T +Microsoft.Identity.Client.IAppConfig.EnableRawStsErrorCodeTelemetry.get -> bool diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt index 0f291c500c..191cb50b13 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt @@ -1,2 +1,2 @@ -Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithStsRawErrorCodeTelemetry(bool enable = true) -> T -Microsoft.Identity.Client.IAppConfig.EnableStsRawErrorCodeTelemetry.get -> bool +Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithRawStsErrorCodeTelemetry(bool enable = true) -> T +Microsoft.Identity.Client.IAppConfig.EnableRawStsErrorCodeTelemetry.get -> bool diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt index 0f291c500c..191cb50b13 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,2 +1,2 @@ -Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithStsRawErrorCodeTelemetry(bool enable = true) -> T -Microsoft.Identity.Client.IAppConfig.EnableStsRawErrorCodeTelemetry.get -> bool +Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithRawStsErrorCodeTelemetry(bool enable = true) -> T +Microsoft.Identity.Client.IAppConfig.EnableRawStsErrorCodeTelemetry.get -> bool diff --git a/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/IOtelInstrumentation.cs b/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/IOtelInstrumentation.cs index 7cb948b60d..b4885d57f5 100644 --- a/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/IOtelInstrumentation.cs +++ b/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/IOtelInstrumentation.cs @@ -38,6 +38,6 @@ internal void LogFailureMetrics(string platform, string callerSdkVersion, CacheRefreshReason cacheRefreshReason, int tokenType, - string stsRawErrorCode = null); + string rawStsErrorCode = null); } } diff --git a/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/NullOtelInstrumentation.cs b/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/NullOtelInstrumentation.cs index c70e524eb3..eb23813f3c 100644 --- a/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/NullOtelInstrumentation.cs +++ b/src/client/Microsoft.Identity.Client/TelemetryCore/OpenTelemetry/NullOtelInstrumentation.cs @@ -34,7 +34,7 @@ public void LogFailureMetrics(string platform, string callerSdkVersion, CacheRefreshReason cacheRefreshReason, int tokenType, - string stsRawErrorCode = null) + string rawStsErrorCode = null) { // No op } diff --git a/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryConstants.cs b/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryConstants.cs index e6b987cb7d..444f53b09d 100644 --- a/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryConstants.cs +++ b/src/client/Microsoft.Identity.Client/TelemetryCore/TelemetryConstants.cs @@ -26,7 +26,7 @@ internal static class TelemetryConstants public const string CacheInfoTelemetry = "CacheInfoTelemetry"; public const string CacheRefreshReason = "CacheRefreshReason"; public const string ErrorCode = "ErrorCode"; - public const string StsRawErrorCode = "StsRawErrorCode"; + public const string RawStsErrorCode = "RawStsErrorCode"; public const string ErrorMessage = "ErrorMessage"; public const string Duration = "Duration"; public const string DurationInUs = "DurationInUs"; diff --git a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs index 131869ac44..d144e63426 100644 --- a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs @@ -415,7 +415,116 @@ private void CreateApplication() .BuildConcrete(); } - private void VerifyMetrics(int expectedMetricCount, List exportedMetrics, + [TestMethod] + public async Task MsalFailure_WithRawStsErrorCodeTelemetry_TagIncludedAsync() + { + using (_harness = CreateTestHarness()) + { + CreateApplicationWithRawStsErrorCodeTelemetry(); + + _harness.HttpManager.AddInstanceDiscoveryMockHandler(); + _harness.HttpManager.AddTokenResponse(TokenResponseType.InvalidClient); + + MsalServiceException ex = await AssertException.TaskThrowsAsync( + () => _cca.AcquireTokenForClient(TestConstants.s_scopeForAnotherResource) + .WithExtraQueryParameters(extraQueryParams) + .WithTenantId(TestConstants.Utid) + .ExecuteAsync(CancellationToken.None)).ConfigureAwait(false); + + Assert.IsNotNull(ex.ErrorCodes, "ErrorCodes should be populated from IDP response."); + + s_meterProvider.ForceFlush(); + + var failureMetric = _exportedMetrics.First(m => m.Name == "MsalFailure"); + foreach (var metricPoint in failureMetric.GetMetricPoints()) + { + var tags = GetTagDictionary(metricPoint.Tags); + Assert.IsTrue(tags.ContainsKey(TelemetryConstants.RawStsErrorCode), + "RawStsErrorCode tag should be present when opted in."); + Assert.AreEqual(ex.ErrorCodes[0], tags[TelemetryConstants.RawStsErrorCode]); + } + } + } + + [TestMethod] + public async Task MsalFailure_WithoutRawStsErrorCodeTelemetry_TagNotIncludedAsync() + { + using (_harness = CreateTestHarness()) + { + CreateApplication(); // no WithRawStsErrorCodeTelemetry + + _harness.HttpManager.AddInstanceDiscoveryMockHandler(); + _harness.HttpManager.AddTokenResponse(TokenResponseType.InvalidClient); + + MsalServiceException ex = await AssertException.TaskThrowsAsync( + () => _cca.AcquireTokenForClient(TestConstants.s_scopeForAnotherResource) + .WithExtraQueryParameters(extraQueryParams) + .WithTenantId(TestConstants.Utid) + .ExecuteAsync(CancellationToken.None)).ConfigureAwait(false); + + Assert.IsNotNull(ex); + + s_meterProvider.ForceFlush(); + + var failureMetric = _exportedMetrics.First(m => m.Name == "MsalFailure"); + foreach (var metricPoint in failureMetric.GetMetricPoints()) + { + var tags = GetTagDictionary(metricPoint.Tags); + Assert.IsFalse(tags.ContainsKey(TelemetryConstants.RawStsErrorCode), + "RawStsErrorCode tag should not be present when not opted in."); + } + } + } + + [TestMethod] + public async Task MsalFailure_WithRawStsErrorCodeTelemetry_ClientException_TagNotIncludedAsync() + { + using (_harness = CreateTestHarness()) + { + CreateApplicationWithRawStsErrorCodeTelemetry(); + + // Null scope triggers MsalClientException before any HTTP call — no ErrorCodes + MsalClientException ex = await AssertException.TaskThrowsAsync( + () => _cca.AcquireTokenForClient(null) + .WithExtraQueryParameters(extraQueryParams) + .WithTenantId(TestConstants.Utid) + .ExecuteAsync(CancellationToken.None)).ConfigureAwait(false); + + Assert.IsNotNull(ex); + + s_meterProvider.ForceFlush(); + + var failureMetric = _exportedMetrics.First(m => m.Name == "MsalFailure"); + foreach (var metricPoint in failureMetric.GetMetricPoints()) + { + var tags = GetTagDictionary(metricPoint.Tags); + Assert.IsFalse(tags.ContainsKey(TelemetryConstants.RawStsErrorCode), + "RawStsErrorCode tag should not be present for non-service exceptions."); + } + } + } + + private void CreateApplicationWithRawStsErrorCodeTelemetry() + { + _cca = ConfidentialClientApplicationBuilder + .Create(TestConstants.ClientId) + .WithExperimentalFeatures() + .WithRawStsErrorCodeTelemetry() + .WithAuthority(TestConstants.AuthorityUtidTenant) + .WithClientSecret(TestConstants.ClientSecret) + .WithHttpManager(_harness.HttpManager) + .BuildConcrete(); + } + + private static IDictionary GetTagDictionary(ReadOnlyTagCollection tags) + { + var dict = new Dictionary(); + foreach (var tag in tags) + dict[tag.Key] = tag.Value; + return dict; + } + + private void VerifyMetrics(int expectedMetricCount, List exportedMetrics, long expectedSuccessfulRequests, long expectedFailedRequests) { Assert.HasCount(expectedMetricCount, exportedMetrics, "Count of metrics recorded is not as expected."); From d5f21170c02b8dc36a7e1c1b404590e13dc2ef54 Mon Sep 17 00:00:00 2001 From: Sergei Smelov Date: Mon, 20 Apr 2026 09:16:30 +0200 Subject: [PATCH 3/6] Make the raw STS error code the default MsalFailure tag --- .../AppConfig/ApplicationConfiguration.cs | 2 - .../BaseAbstractApplicationBuilder.cs | 13 ----- .../AppConfig/IAppConfig.cs | 9 +--- .../Internal/Requests/RequestBase.cs | 2 +- .../Internal/Requests/SilentRequestHelper.cs | 2 +- .../PublicApi/net462/PublicAPI.Unshipped.txt | 3 +- .../PublicApi/net472/PublicAPI.Unshipped.txt | 3 +- .../net8.0-android/PublicAPI.Unshipped.txt | 3 +- .../net8.0-ios/PublicAPI.Unshipped.txt | 3 +- .../PublicApi/net8.0/PublicAPI.Unshipped.txt | 3 +- .../netstandard2.0/PublicAPI.Unshipped.txt | 3 +- .../OTelInstrumentationTests.cs | 52 ++----------------- 12 files changed, 14 insertions(+), 84 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs index 97cb22e1c9..4364ad143f 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs @@ -112,8 +112,6 @@ public string ClientVersion public bool ExperimentalFeaturesEnabled { get; set; } = false; - public bool EnableRawStsErrorCodeTelemetry { get; set; } = false; - public IEnumerable ClientCapabilities { get; set; } public bool SendX5C { get; internal set; } = false; public bool LegacyCacheCompatibilityEnabled { get; internal set; } = true; diff --git a/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs b/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs index 6d15eb0f43..d75c3cd3fe 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs @@ -221,19 +221,6 @@ public T WithExperimentalFeatures(bool enableExperimentalFeatures = true) return (T)this; } - /// - /// When enabled, the raw ESTS error code from the identity provider response is included as the - /// RawStsErrorCode tag on the MsalFailure OpenTelemetry counter. Opt-in only, because - /// ESTS error codes can be high-cardinality and may increase metric storage costs. - /// - /// Set to true to include the tag; false to exclude it. Default is true. - /// The builder to chain the .With methods - public T WithRawStsErrorCodeTelemetry(bool enable = true) - { - Config.EnableRawStsErrorCodeTelemetry = enable; - return (T)this; - } - /// /// Sets the name of the calling SDK API for telemetry purposes. /// diff --git a/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs b/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs index de08de8bb7..92b4bf0d5c 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs @@ -96,14 +96,7 @@ public interface IAppConfig bool ExperimentalFeaturesEnabled { get; } /// - /// When enabled, the raw ESTS error code returned in the error_codes array of the identity - /// provider response is included as the RawStsErrorCode tag on the MsalFailure OpenTelemetry - /// counter. Disabled by default to avoid unintended metric-cardinality growth. - /// - bool EnableRawStsErrorCodeTelemetry { get; } - - /// - /// Microsoft Identity specific OIDC extension that allows resource challenges to be resolved without interaction. + /// Microsoft Identity specific OIDC extension that allows resource challenges to be resolved without interaction. /// Allows configuration of one or more client capabilities, e.g. "llt" /// /// diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index 911018ea2e..f85eb1aa95 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -108,7 +108,7 @@ public async Task RunAsync(CancellationToken cancellationT LogFailureTelemetryToOtel( ex.ErrorCode, apiEvent, apiEvent.CacheInfo, - ServiceBundle.Config.EnableRawStsErrorCodeTelemetry ? (ex as MsalServiceException)?.ErrorCodes?[0] : null); + (ex as MsalServiceException)?.ErrorCodes?[0]); throw; } catch (Exception ex) diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs index 64d4fdb01f..b869e68161 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs @@ -125,7 +125,7 @@ internal static void ProcessFetchInBackground( callerSdkVersion, CacheRefreshReason.ProactivelyRefreshed, apiEvent.TokenType, - serviceBundle.Config.EnableRawStsErrorCodeTelemetry ? ex.ErrorCodes?[0] : null); + ex.ErrorCodes?[0]); } catch (OperationCanceledException ex) { diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt index 191cb50b13..8b13789179 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt @@ -1,2 +1 @@ -Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithRawStsErrorCodeTelemetry(bool enable = true) -> T -Microsoft.Identity.Client.IAppConfig.EnableRawStsErrorCodeTelemetry.get -> bool + diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt index 191cb50b13..8b13789179 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt @@ -1,2 +1 @@ -Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithRawStsErrorCodeTelemetry(bool enable = true) -> T -Microsoft.Identity.Client.IAppConfig.EnableRawStsErrorCodeTelemetry.get -> bool + diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt index 191cb50b13..8b13789179 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt @@ -1,2 +1 @@ -Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithRawStsErrorCodeTelemetry(bool enable = true) -> T -Microsoft.Identity.Client.IAppConfig.EnableRawStsErrorCodeTelemetry.get -> bool + diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt index 191cb50b13..8b13789179 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt @@ -1,2 +1 @@ -Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithRawStsErrorCodeTelemetry(bool enable = true) -> T -Microsoft.Identity.Client.IAppConfig.EnableRawStsErrorCodeTelemetry.get -> bool + diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt index 191cb50b13..8b13789179 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt @@ -1,2 +1 @@ -Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithRawStsErrorCodeTelemetry(bool enable = true) -> T -Microsoft.Identity.Client.IAppConfig.EnableRawStsErrorCodeTelemetry.get -> bool + diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt index 191cb50b13..8b13789179 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,2 +1 @@ -Microsoft.Identity.Client.BaseAbstractApplicationBuilder.WithRawStsErrorCodeTelemetry(bool enable = true) -> T -Microsoft.Identity.Client.IAppConfig.EnableRawStsErrorCodeTelemetry.get -> bool + diff --git a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs index d144e63426..85654273a1 100644 --- a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs @@ -416,11 +416,11 @@ private void CreateApplication() } [TestMethod] - public async Task MsalFailure_WithRawStsErrorCodeTelemetry_TagIncludedAsync() + public async Task MsalFailure_ServiceException_RawStsErrorCodeTag_IncludedAsync() { using (_harness = CreateTestHarness()) { - CreateApplicationWithRawStsErrorCodeTelemetry(); + CreateApplication(); _harness.HttpManager.AddInstanceDiscoveryMockHandler(); _harness.HttpManager.AddTokenResponse(TokenResponseType.InvalidClient); @@ -440,48 +440,18 @@ public async Task MsalFailure_WithRawStsErrorCodeTelemetry_TagIncludedAsync() { var tags = GetTagDictionary(metricPoint.Tags); Assert.IsTrue(tags.ContainsKey(TelemetryConstants.RawStsErrorCode), - "RawStsErrorCode tag should be present when opted in."); + "RawStsErrorCode tag should always be present for service exceptions."); Assert.AreEqual(ex.ErrorCodes[0], tags[TelemetryConstants.RawStsErrorCode]); } } } [TestMethod] - public async Task MsalFailure_WithoutRawStsErrorCodeTelemetry_TagNotIncludedAsync() + public async Task MsalFailure_ClientException_RawStsErrorCodeTag_NotIncludedAsync() { using (_harness = CreateTestHarness()) { - CreateApplication(); // no WithRawStsErrorCodeTelemetry - - _harness.HttpManager.AddInstanceDiscoveryMockHandler(); - _harness.HttpManager.AddTokenResponse(TokenResponseType.InvalidClient); - - MsalServiceException ex = await AssertException.TaskThrowsAsync( - () => _cca.AcquireTokenForClient(TestConstants.s_scopeForAnotherResource) - .WithExtraQueryParameters(extraQueryParams) - .WithTenantId(TestConstants.Utid) - .ExecuteAsync(CancellationToken.None)).ConfigureAwait(false); - - Assert.IsNotNull(ex); - - s_meterProvider.ForceFlush(); - - var failureMetric = _exportedMetrics.First(m => m.Name == "MsalFailure"); - foreach (var metricPoint in failureMetric.GetMetricPoints()) - { - var tags = GetTagDictionary(metricPoint.Tags); - Assert.IsFalse(tags.ContainsKey(TelemetryConstants.RawStsErrorCode), - "RawStsErrorCode tag should not be present when not opted in."); - } - } - } - - [TestMethod] - public async Task MsalFailure_WithRawStsErrorCodeTelemetry_ClientException_TagNotIncludedAsync() - { - using (_harness = CreateTestHarness()) - { - CreateApplicationWithRawStsErrorCodeTelemetry(); + CreateApplication(); // Null scope triggers MsalClientException before any HTTP call — no ErrorCodes MsalClientException ex = await AssertException.TaskThrowsAsync( @@ -504,18 +474,6 @@ public async Task MsalFailure_WithRawStsErrorCodeTelemetry_ClientException_TagNo } } - private void CreateApplicationWithRawStsErrorCodeTelemetry() - { - _cca = ConfidentialClientApplicationBuilder - .Create(TestConstants.ClientId) - .WithExperimentalFeatures() - .WithRawStsErrorCodeTelemetry() - .WithAuthority(TestConstants.AuthorityUtidTenant) - .WithClientSecret(TestConstants.ClientSecret) - .WithHttpManager(_harness.HttpManager) - .BuildConcrete(); - } - private static IDictionary GetTagDictionary(ReadOnlyTagCollection tags) { var dict = new Dictionary(); From 93782daac91d9f125c7b1475baad6c4340b94748 Mon Sep 17 00:00:00 2001 From: Sergei Smelov Date: Thu, 23 Apr 2026 23:02:05 +0200 Subject: [PATCH 4/6] Update --- .../AppConfig/BaseAbstractApplicationBuilder.cs | 2 +- .../Microsoft.Identity.Client/AppConfig/IAppConfig.cs | 6 +++--- .../PublicApi/net462/PublicAPI.Unshipped.txt | 1 - .../PublicApi/net472/PublicAPI.Unshipped.txt | 1 - .../PublicApi/net8.0-android/PublicAPI.Unshipped.txt | 1 - .../PublicApi/net8.0-ios/PublicAPI.Unshipped.txt | 1 - .../PublicApi/net8.0/PublicAPI.Unshipped.txt | 1 - .../PublicApi/netstandard2.0/PublicAPI.Unshipped.txt | 1 - 8 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs b/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs index d75c3cd3fe..ea20f7ee20 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs @@ -208,7 +208,7 @@ protected T WithOptions(BaseApplicationOptions applicationOptions) } /// - /// Allows usage of experimental features and APIs. If this flag is not set, experimental features + /// Allows usage of experimental features and APIs. If this flag is not set, experimental features /// will throw an exception. For details see https://aka.ms/msal-net-experimental-features /// /// diff --git a/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs b/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs index 92b4bf0d5c..50c31b3c2f 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/IAppConfig.cs @@ -89,14 +89,14 @@ public interface IAppConfig ITelemetryConfig TelemetryConfig { get; } /// - /// Allows usage of features that are experimental and would otherwise throw a specific exception. - /// Use of experimental features in production is not recommended and are subject to be removed between builds. + /// Allows usage of features that are experimental and would otherwise throw a specific exception. + /// Use of experimental features in production is not recommended and are subject to be removed between builds. /// For details see https://aka.ms/msal-net-experimental-features. /// bool ExperimentalFeaturesEnabled { get; } /// - /// Microsoft Identity specific OIDC extension that allows resource challenges to be resolved without interaction. + /// Microsoft Identity specific OIDC extension that allows resource challenges to be resolved without interaction. /// Allows configuration of one or more client capabilities, e.g. "llt" /// /// diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt index 8b13789179..e69de29bb2 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt @@ -1 +0,0 @@ - diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt index 8b13789179..e69de29bb2 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt @@ -1 +0,0 @@ - diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt index 8b13789179..e69de29bb2 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt @@ -1 +0,0 @@ - diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt index 8b13789179..e69de29bb2 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt @@ -1 +0,0 @@ - diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt index 8b13789179..e69de29bb2 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt @@ -1 +0,0 @@ - diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt index 8b13789179..e69de29bb2 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1 +0,0 @@ - From f767a734495f894b7239b3dd2de5117c69b799ce Mon Sep 17 00:00:00 2001 From: Sergei Smelov Date: Fri, 24 Apr 2026 13:51:05 +0200 Subject: [PATCH 5/6] Update --- .../Internal/Requests/RequestBase.cs | 2 +- .../Internal/Requests/SilentRequestHelper.cs | 3 ++- .../TelemetryTests/OTelInstrumentationTests.cs | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index f85eb1aa95..5f27a8063c 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -108,7 +108,7 @@ public async Task RunAsync(CancellationToken cancellationT LogFailureTelemetryToOtel( ex.ErrorCode, apiEvent, apiEvent.CacheInfo, - (ex as MsalServiceException)?.ErrorCodes?[0]); + (ex as MsalServiceException)?.ErrorCodes?.FirstOrDefault()); throw; } catch (Exception ex) diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs index b869e68161..a5f43bf85a 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/SilentRequestHelper.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Identity.Client.Cache.Items; @@ -125,7 +126,7 @@ internal static void ProcessFetchInBackground( callerSdkVersion, CacheRefreshReason.ProactivelyRefreshed, apiEvent.TokenType, - ex.ErrorCodes?[0]); + ex.ErrorCodes?.FirstOrDefault()); } catch (OperationCanceledException ex) { diff --git a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs index 85654273a1..933815bbd1 100644 --- a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs @@ -440,8 +440,8 @@ public async Task MsalFailure_ServiceException_RawStsErrorCodeTag_IncludedAsync( { var tags = GetTagDictionary(metricPoint.Tags); Assert.IsTrue(tags.ContainsKey(TelemetryConstants.RawStsErrorCode), - "RawStsErrorCode tag should always be present for service exceptions."); - Assert.AreEqual(ex.ErrorCodes[0], tags[TelemetryConstants.RawStsErrorCode]); + "RawStsErrorCode tag should be present when the IDP response contains error_codes."); + Assert.AreEqual(ex.ErrorCodes.FirstOrDefault(), tags[TelemetryConstants.RawStsErrorCode]); } } } From a5424a959c7beaa33b661623031847dbb5b7cb7d Mon Sep 17 00:00:00 2001 From: Sergei Smelov Date: Wed, 29 Apr 2026 16:33:11 +0200 Subject: [PATCH 6/6] Update tests --- .../TelemetryTests/OTelInstrumentationTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs index 933815bbd1..f074df9f80 100644 --- a/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/TelemetryTests/OTelInstrumentationTests.cs @@ -534,7 +534,10 @@ private void VerifyMetrics(int expectedMetricCount, List exportedMetrics foreach (var metricPoint in exportedItem.GetMetricPoints()) { totalFailedRequests += metricPoint.GetSumLong(); - AssertTags(metricPoint.Tags, expectedTags, true); + var pointExpectedTags = new List(expectedTags); + if (GetTagDictionary(metricPoint.Tags).ContainsKey(TelemetryConstants.RawStsErrorCode)) + pointExpectedTags.Add(TelemetryConstants.RawStsErrorCode); + AssertTags(metricPoint.Tags, pointExpectedTags, true); } Assert.AreEqual(expectedFailedRequests, totalFailedRequests);