-
Notifications
You must be signed in to change notification settings - Fork 408
Expose canonical tag names per-metric #6076
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
neha-bhargava
merged 5 commits into
main
from
ssmelov/expose_canonical_tag_names_per_metric
Jun 23, 2026
+338
−36
Merged
Changes from 2 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
7d4cc62
Expose canonical tag names per-metric
ssmelov 77d7ef9
Merge main into branch: resolve PublicAPI.Unshipped.txt conflicts
Copilot 2ea631b
Move MsalMetricsCatalog to Extensibiliry namespace
ssmelov ec3744a
Adjust the comments
ssmelov 194ca2a
Merge branch 'main' into ssmelov/expose_canonical_tag_names_per_metric
neha-bhargava File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
171 changes: 171 additions & 0 deletions
171
src/client/Microsoft.Identity.Client/MsalMetricsCatalog.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Collections.ObjectModel; | ||
| using Microsoft.Identity.Client.TelemetryCore; | ||
|
|
||
| namespace Microsoft.Identity.Client | ||
| { | ||
| /// <summary> | ||
| /// Describes the OpenTelemetry metrics that MSAL emits and, for each metric, the canonical | ||
| /// (MSAL-owned) tag names it records. Consumers such as downstream metric pipelines can use | ||
| /// <see cref="CanonicalTagsByMetric"/> to discover which tags belong to a given MSAL metric and, | ||
| /// for example, keep only those tags. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// The tag names listed for a metric are the canonical base tags MSAL always emits for it. They do not | ||
| /// include any extra tags supplied through | ||
| /// <see cref="Extensibility.AbstractConfidentialClientAcquireTokenParameterBuilderExtension.WithOtelTagsEnricher{T}"/>, | ||
| /// which are caller-defined and therefore not part of this catalog. Some canonical tags are emitted only | ||
| /// under certain conditions (for example the raw STS error-code tag is present on the failure counter only | ||
| /// when the STS returns a sub-error); they are listed here because they are part of the metric's canonical | ||
| /// schema. | ||
| /// </para> | ||
| /// <para> | ||
| /// Both the metric names (the dictionary keys) and the tag names (the dictionary values) are plain strings | ||
| /// that match what MSAL records, so consumers can compare them directly against the metric and tag names | ||
| /// observed on the OpenTelemetry pipeline, then drive per-metric tag filtering from this mapping (for | ||
| /// example via OpenTelemetry Views and <c>MetricStreamConfiguration.TagKeys</c>). Keeping the mapping here | ||
| /// means it stays in sync with MSAL automatically: updating the MSAL package reference updates the canonical | ||
| /// tag set, with nothing to maintain on the consumer side. | ||
| /// </para> | ||
| /// </remarks> | ||
| public static class MsalMetricsCatalog | ||
| { | ||
| // Metric names are defined once here (internal) so the published catalog and the instruments created in | ||
| // OtelInstrumentation share a single source of truth and cannot drift. They are intentionally not part of | ||
| // the public surface: consumers read metric names from the keys of CanonicalTagsByMetric (and from the | ||
| // OpenTelemetry pipeline), so there is no need to expose them as separate public constants. | ||
| internal const string SuccessCounterName = "MsalSuccess"; | ||
| internal const string FailureCounterName = "MsalFailure"; | ||
| internal const string TotalDurationHistogramName = "MsalTotalDuration.1A"; | ||
| internal const string TotalDurationV2HistogramName = "MsalTotalDurationV2.1A"; | ||
| internal const string DurationInL1CacheHistogramName = "MsalDurationInL1CacheInUs.1B"; | ||
| internal const string DurationInL2CacheHistogramName = "MsalDurationInL2Cache.1A"; | ||
| internal const string DurationInHttpHistogramName = "MsalDurationInHttp.1A"; | ||
| internal const string DurationInHttpV2HistogramName = "MsalDurationInHttpV2.1A"; | ||
| internal const string DurationInExtensionHistogramName = "MsalDurationInExtensionInMs.1B"; | ||
| internal const string RemainingTokenLifetimeHistogramName = "MsalRemainingTokenLifetime.1A"; | ||
|
|
||
| /// <summary> | ||
| /// Maps each MSAL metric name to the read-only list of canonical tag names that metric records. | ||
| /// Keys are compared with <see cref="System.StringComparer.Ordinal"/>. | ||
| /// </summary> | ||
| public static IReadOnlyDictionary<string, IReadOnlyList<string>> CanonicalTagsByMetric { get; } = | ||
| BuildCanonicalTagsByMetric(); | ||
|
|
||
| private static IReadOnlyDictionary<string, IReadOnlyList<string>> BuildCanonicalTagsByMetric() | ||
| { | ||
| // Tag names reference the internal TelemetryConstants used when recording, so the mapping cannot | ||
| // drift from the tags MSAL actually emits. | ||
| var map = new Dictionary<string, string[]>(StringComparer.Ordinal) | ||
| { | ||
| [SuccessCounterName] = new[] | ||
| { | ||
| TelemetryConstants.MsalVersion, | ||
| TelemetryConstants.Platform, | ||
| TelemetryConstants.ApiId, | ||
| TelemetryConstants.CallerSdkId, | ||
| TelemetryConstants.TokenSource, | ||
| TelemetryConstants.CacheRefreshReason, | ||
| TelemetryConstants.CacheLevel, | ||
| TelemetryConstants.TokenType, | ||
| }, | ||
| [FailureCounterName] = new[] | ||
| { | ||
| TelemetryConstants.MsalVersion, | ||
| TelemetryConstants.Platform, | ||
| TelemetryConstants.ErrorCode, | ||
| TelemetryConstants.ApiId, | ||
| TelemetryConstants.CallerSdkId, | ||
| TelemetryConstants.CacheRefreshReason, | ||
| TelemetryConstants.TokenType, | ||
| TelemetryConstants.RawStsErrorCode, | ||
| }, | ||
| [TotalDurationHistogramName] = new[] | ||
| { | ||
| TelemetryConstants.MsalVersion, | ||
| TelemetryConstants.Platform, | ||
| TelemetryConstants.ApiId, | ||
| TelemetryConstants.TokenSource, | ||
| TelemetryConstants.CacheLevel, | ||
| TelemetryConstants.CacheRefreshReason, | ||
| TelemetryConstants.TokenType, | ||
| }, | ||
| [TotalDurationV2HistogramName] = new[] | ||
| { | ||
| TelemetryConstants.MsalVersionPlatform, | ||
| TelemetryConstants.ApiId, | ||
| TelemetryConstants.TokenSource, | ||
| TelemetryConstants.CacheLevel, | ||
| TelemetryConstants.CacheRefreshReason, | ||
| TelemetryConstants.TokenType, | ||
| TelemetryConstants.ErrorCode, | ||
| TelemetryConstants.Succeeded, | ||
| }, | ||
| [DurationInL1CacheHistogramName] = new[] | ||
| { | ||
| TelemetryConstants.MsalVersion, | ||
| TelemetryConstants.Platform, | ||
| TelemetryConstants.ApiId, | ||
| TelemetryConstants.TokenSource, | ||
| TelemetryConstants.CacheLevel, | ||
| TelemetryConstants.CacheRefreshReason, | ||
| }, | ||
| [DurationInL2CacheHistogramName] = new[] | ||
| { | ||
| TelemetryConstants.MsalVersion, | ||
| TelemetryConstants.Platform, | ||
| TelemetryConstants.ApiId, | ||
| TelemetryConstants.CacheRefreshReason, | ||
| }, | ||
| [DurationInHttpHistogramName] = new[] | ||
| { | ||
| TelemetryConstants.MsalVersion, | ||
| TelemetryConstants.Platform, | ||
| TelemetryConstants.ApiId, | ||
| TelemetryConstants.TokenType, | ||
| }, | ||
| [DurationInHttpV2HistogramName] = new[] | ||
| { | ||
| TelemetryConstants.MsalVersionPlatform, | ||
| TelemetryConstants.ApiId, | ||
| TelemetryConstants.TokenType, | ||
| TelemetryConstants.HttpStatusCode, | ||
| }, | ||
| [DurationInExtensionHistogramName] = new[] | ||
| { | ||
| TelemetryConstants.MsalVersion, | ||
| TelemetryConstants.Platform, | ||
| TelemetryConstants.ApiId, | ||
| TelemetryConstants.TokenSource, | ||
| TelemetryConstants.CacheLevel, | ||
| TelemetryConstants.TokenType, | ||
| }, | ||
| [RemainingTokenLifetimeHistogramName] = new[] | ||
| { | ||
| TelemetryConstants.MsalVersionPlatform, | ||
| TelemetryConstants.ApiId, | ||
| TelemetryConstants.TokenSource, | ||
| TelemetryConstants.CacheLevel, | ||
| TelemetryConstants.CacheRefreshReason, | ||
| TelemetryConstants.TokenType, | ||
| }, | ||
| }; | ||
|
|
||
| // Expose each tag list as a ReadOnlyCollection rather than the backing array: an IReadOnlyList that is | ||
| // actually a string[] can be cast back and mutated by a consumer, which would corrupt this shared | ||
| // static catalog process-wide. The dictionary itself is already read-only. | ||
| var readOnlyMap = new Dictionary<string, IReadOnlyList<string>>(map.Count, StringComparer.Ordinal); | ||
| foreach (KeyValuePair<string, string[]> entry in map) | ||
| { | ||
| readOnlyMap[entry.Key] = Array.AsReadOnly(entry.Value); | ||
| } | ||
|
|
||
| return new ReadOnlyDictionary<string, IReadOnlyList<string>>(readOnlyMap); | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.