Skip to content

Commit eafba37

Browse files
authored
Merge origin/main into nebharg/fic-otel-enricher
2 parents bd34a52 + 1159aa4 commit eafba37

11 files changed

Lines changed: 230 additions & 1 deletion

File tree

src/client/Microsoft.Identity.Client/AuthenticationResultMetadata.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ namespace Microsoft.Identity.Client
99
/// <summary>
1010
/// Contains metadata of the authentication result. <see cref="Metrics"/> for additional MSAL-wide metrics.
1111
/// </summary>
12+
#if NETFRAMEWORK || NETSTANDARD
13+
// Marked [Serializable] only on the frameworks whose Exception.Data (ListDictionaryInternal) rejects
14+
// non-serializable values, so downstream providers can read this object off the raw exception's Data bag
15+
// (Bug 3696194). .NET (Core) removed that check, so the attribute is unnecessary there. The whole member
16+
// graph is serializable (enums, strings, numeric types, and the RegionDetails DTO), so this is honest.
17+
[Serializable]
18+
#endif
1219
public class AuthenticationResultMetadata
1320
{
1421

src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,49 @@ public async Task<AuthenticationResult> RunAsync(CancellationToken cancellationT
138138
apiEvent.ApiErrorCode = ex.GetType().Name;
139139
AuthenticationRequestParameters.RequestContext.Logger.ErrorPii(ex);
140140

141-
LogFailureTelemetryToOtel(ex.GetType().Name, apiEvent, apiEvent.CacheInfo, httpStatusCode: 0, totalDurationInMs: requestStopwatch.ElapsedMilliseconds + measureTelemetryDurationResult.Milliseconds);
141+
// Compute the total duration once so the value on the synthesized metadata matches the
142+
// value logged to OpenTelemetry (the stopwatch keeps running, so re-reading it drifts).
143+
long totalDurationInMs = requestStopwatch.ElapsedMilliseconds + measureTelemetryDurationResult.Milliseconds;
144+
145+
AuthenticationResultMetadata failureMetadata = CreateFailureMetadata(apiEvent, totalDurationInMs);
146+
147+
// Expose the failure metadata on the ORIGINAL exception via its Data bag so downstream
148+
// header-creation providers - which catch the raw non-MSAL exception, not the enricher
149+
// wrapper - can surface token-acquisition diagnostics (Bug 3696194). The value is the same
150+
// strongly-typed object MSAL builds for the success path, so consumers reuse their mapper.
151+
// Guarded because a derived exception may expose a null or read-only Data bag, and telemetry
152+
// plumbing must never throw here and mask the caller's original exception. On .NET Framework /
153+
// netstandard the Data bag also rejects non-serializable values, so AuthenticationResultMetadata
154+
// is marked [Serializable] on those targets (see AuthenticationResultMetadata.cs).
155+
if (ex.Data is { IsReadOnly: false })
156+
{
157+
ex.Data[MsalException.AuthenticationResultMetadataKey] = failureMetadata;
158+
}
159+
160+
// The original exception is re-thrown below; MSAL never surfaces this wrapper. It exists only
161+
// so the OpenTelemetry tag enricher observes a populated ExecutionResult.Exception (carrying
162+
// failure metadata) for non-MSAL failures, mirroring the MsalException path above. The
163+
// originating exception's type is captured as the ErrorCode and it is preserved as the
164+
// InnerException so consumers retain full fidelity. Fall back to the type name when
165+
// FullName is null (some generic/array types) or Message is empty/whitespace, because the
166+
// MsalException ctor rejects a null/empty errorCode or errorMessage - without the fallback
167+
// that ArgumentNullException would replace the original exception we re-throw below.
168+
string enricherErrorCode = ex.GetType().FullName ?? ex.GetType().Name;
169+
string enricherErrorMessage = string.IsNullOrWhiteSpace(ex.Message) ? ex.GetType().Name : ex.Message;
170+
171+
MsalException enricherException = new MsalException(enricherErrorCode, enricherErrorMessage, ex)
172+
{
173+
AuthenticationResultMetadata = failureMetadata,
174+
CorrelationId = AuthenticationRequestParameters.CorrelationId.ToString(),
175+
};
176+
177+
LogFailureTelemetryToOtel(
178+
ex.GetType().Name,
179+
apiEvent,
180+
apiEvent.CacheInfo,
181+
httpStatusCode: 0,
182+
totalDurationInMs: totalDurationInMs,
183+
exception: enricherException);
142184
throw;
143185
}
144186
}

src/client/Microsoft.Identity.Client/MsalException.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ public class MsalException : Exception
4848
/// </summary>
4949
public const string ManagedIdentitySource = "ManagedIdentitySource";
5050

51+
/// <summary>
52+
/// A key on <see cref="System.Exception.Data"/> under which MSAL stores the
53+
/// <see cref="AuthenticationResultMetadata"/> captured when a token acquisition fails with a
54+
/// non-<see cref="MsalException"/>. Lets downstream consumers surface token-acquisition diagnostics
55+
/// even though the originating exception is re-thrown unchanged.
56+
/// </summary>
57+
public const string AuthenticationResultMetadataKey = "MsalAuthenticationResultMetadata";
58+
5159
private string _errorCode;
5260

5361
/// <summary>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const Microsoft.Identity.Client.MsalException.AuthenticationResultMetadataKey = "MsalAuthenticationResultMetadata" -> string
12
Microsoft.Identity.Client.AssertionRequestOptions.OtelTagsEnricher.get -> System.Action<Microsoft.Identity.Client.Extensibility.ExecutionResult, System.Collections.Generic.IList<System.Collections.Generic.KeyValuePair<string, object>>>
23
Microsoft.Identity.Client.AssertionRequestOptions.OtelTagsEnricher.set -> void
34
Microsoft.Identity.Client.MsalServiceException.ErrorCodesForLogging.get -> System.Collections.Generic.IReadOnlyList<string>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const Microsoft.Identity.Client.MsalException.AuthenticationResultMetadataKey = "MsalAuthenticationResultMetadata" -> string
12
Microsoft.Identity.Client.AssertionRequestOptions.OtelTagsEnricher.get -> System.Action<Microsoft.Identity.Client.Extensibility.ExecutionResult, System.Collections.Generic.IList<System.Collections.Generic.KeyValuePair<string, object>>>
23
Microsoft.Identity.Client.AssertionRequestOptions.OtelTagsEnricher.set -> void
34
Microsoft.Identity.Client.MsalServiceException.ErrorCodesForLogging.get -> System.Collections.Generic.IReadOnlyList<string>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const Microsoft.Identity.Client.MsalException.AuthenticationResultMetadataKey = "MsalAuthenticationResultMetadata" -> string
12
Microsoft.Identity.Client.AssertionRequestOptions.OtelTagsEnricher.get -> System.Action<Microsoft.Identity.Client.Extensibility.ExecutionResult, System.Collections.Generic.IList<System.Collections.Generic.KeyValuePair<string, object>>>
23
Microsoft.Identity.Client.AssertionRequestOptions.OtelTagsEnricher.set -> void
34
Microsoft.Identity.Client.MsalServiceException.ErrorCodesForLogging.get -> System.Collections.Generic.IReadOnlyList<string>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const Microsoft.Identity.Client.MsalException.AuthenticationResultMetadataKey = "MsalAuthenticationResultMetadata" -> string
12
Microsoft.Identity.Client.AssertionRequestOptions.OtelTagsEnricher.get -> System.Action<Microsoft.Identity.Client.Extensibility.ExecutionResult, System.Collections.Generic.IList<System.Collections.Generic.KeyValuePair<string, object>>>
23
Microsoft.Identity.Client.AssertionRequestOptions.OtelTagsEnricher.set -> void
34
Microsoft.Identity.Client.MsalServiceException.ErrorCodesForLogging.get -> System.Collections.Generic.IReadOnlyList<string>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const Microsoft.Identity.Client.MsalException.AuthenticationResultMetadataKey = "MsalAuthenticationResultMetadata" -> string
12
Microsoft.Identity.Client.AssertionRequestOptions.OtelTagsEnricher.get -> System.Action<Microsoft.Identity.Client.Extensibility.ExecutionResult, System.Collections.Generic.IList<System.Collections.Generic.KeyValuePair<string, object>>>
23
Microsoft.Identity.Client.AssertionRequestOptions.OtelTagsEnricher.set -> void
34
Microsoft.Identity.Client.MsalServiceException.ErrorCodesForLogging.get -> System.Collections.Generic.IReadOnlyList<string>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const Microsoft.Identity.Client.MsalException.AuthenticationResultMetadataKey = "MsalAuthenticationResultMetadata" -> string
12
Microsoft.Identity.Client.AssertionRequestOptions.OtelTagsEnricher.get -> System.Action<Microsoft.Identity.Client.Extensibility.ExecutionResult, System.Collections.Generic.IList<System.Collections.Generic.KeyValuePair<string, object>>>
23
Microsoft.Identity.Client.AssertionRequestOptions.OtelTagsEnricher.set -> void
34
Microsoft.Identity.Client.MsalServiceException.ErrorCodesForLogging.get -> System.Collections.Generic.IReadOnlyList<string>

src/client/Microsoft.Identity.Client/RegionDetails.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
using System;
45
using Microsoft.Identity.Client.Region;
56

67
namespace Microsoft.Identity.Client
@@ -11,6 +12,11 @@ namespace Microsoft.Identity.Client
1112
/// <see cref="AuthenticationResultMetadata"/> for additional metadata
1213
/// information of the authentication result.
1314
/// </summary>
15+
#if NETFRAMEWORK || NETSTANDARD
16+
// Serializable alongside AuthenticationResultMetadata (its container) so the graph stored in
17+
// Exception.Data is fully serializable on .NET Framework / netstandard (Bug 3696194).
18+
[Serializable]
19+
#endif
1420
public class RegionDetails
1521
{
1622
/// <summary>

0 commit comments

Comments
 (0)