Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,19 @@ public async Task<AuthenticationResult> RunAsync(CancellationToken cancellationT

private void LogSuccessTelemetryToOtel(AuthenticationResult authenticationResult, ApiEvent apiEvent, long durationInUs)
{
CacheLevel cacheLevel = GetCacheLevel(authenticationResult);

// Log metrics
ServiceBundle.PlatformProxy.OtelInstrumentation.LogSuccessMetrics(
ServiceBundle.PlatformProxy.GetProductName(),
apiEvent.ApiId,
apiEvent.CallerSdkApiId,
apiEvent.CallerSdkVersion,
GetCacheLevel(authenticationResult),
cacheLevel,
durationInUs,
authenticationResult.AuthenticationResultMetadata,
AuthenticationRequestParameters.RequestContext.Logger);
AuthenticationRequestParameters.RequestContext.Logger,
authenticationResult.ExpiresOn);
}

private void LogFailureTelemetryToOtel(string errorCodeToLog, ApiEvent apiEvent, CacheRefreshReason cacheRefreshReason)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal class OtelInstrumentation : IOtelInstrumentation
private const string DurationInL2CacheHistogramName = "MsalDurationInL2Cache.1A";
private const string DurationInHttpHistogramName = "MsalDurationInHttp.1A";
private const string DurationInExtensionInMsHistogram = "MsalDurationInExtensionInMs.1B";
private const string RemainingTokenLifetimeHistogramName = "MsalRemainingTokenLifetime.1A";

/// <summary>
/// Meter to hold the MSAL metrics.
Expand Down Expand Up @@ -88,6 +89,14 @@ internal class OtelInstrumentation : IOtelInstrumentation
unit: "us",
description: "Performance of token acquisition calls extension latency."));

/// <summary>
/// Histogram to record the remaining lifetime of acquired tokens in seconds.
Comment thread
neha-bhargava marked this conversation as resolved.
/// </summary>
internal static readonly Lazy<Histogram<long>> s_remainingTokenLifetime = new(() => Meter.CreateHistogram<long>(
RemainingTokenLifetimeHistogramName,
unit: "s",
description: "Remaining lifetime of acquired tokens at the time of acquisition."));
Comment thread
neha-bhargava marked this conversation as resolved.
Comment on lines +92 to +98
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says MsalRemainingTokenLifetime.1A should record remaining token lifetime in minutes, but the instrument is defined/recorded in seconds (unit "s" and TotalSeconds). Either update the implementation/unit to minutes, or update the PR description and any downstream expectations to explicitly be seconds.

Copilot uses AI. Check for mistakes.

public OtelInstrumentation()
{
// Needed to fail fast if the runtime, like in-process Azure Functions, doesn't support OpenTelemetry
Expand All @@ -103,7 +112,8 @@ public void LogSuccessMetrics(
CacheLevel cacheLevel,
long totalDurationInUs,
AuthenticationResultMetadata authResultMetadata,
ILoggerAdapter logger)
ILoggerAdapter logger,
DateTimeOffset expiresOn)
{
IncrementSuccessCounter(
platform,
Expand Down Expand Up @@ -171,6 +181,19 @@ public void LogSuccessMetrics(
new(TelemetryConstants.CacheLevel, authResultMetadata.CacheLevel),
new(TelemetryConstants.TokenType, authResultMetadata.TelemetryTokenType));
}

if (s_remainingTokenLifetime.Value.Enabled)
{
long remainingSeconds = Math.Max(0, (long)(expiresOn - DateTimeOffset.UtcNow).TotalSeconds);

s_remainingTokenLifetime.Value.Record(remainingSeconds,
new(TelemetryConstants.MsalVersionPlatform, $"{MsalIdHelper.GetMsalVersion()},{platform}"),
new(TelemetryConstants.ApiId, apiId),
new(TelemetryConstants.TokenSource, authResultMetadata.TokenSource),
new(TelemetryConstants.CacheLevel, cacheLevel),
new(TelemetryConstants.CacheRefreshReason, authResultMetadata.CacheRefreshReason),
new(TelemetryConstants.TokenType, authResultMetadata.TelemetryTokenType));
Comment on lines +189 to +195
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description calls out TokenType tag as a human-readable string and mentions adding a TelemetryTokenTypeConstants.ToDisplayString() helper, but this metric records authResultMetadata.TelemetryTokenType (an int). If TokenType is intended to be a display string for this histogram, convert the int to the display string (e.g., via the helper) before tagging; otherwise, update the PR description to match the emitted tag value type.

Copilot uses AI. Check for mistakes.
}
}

public void IncrementSuccessCounter(string platform,
Expand Down Expand Up @@ -218,5 +241,6 @@ public void LogFailureMetrics(string platform,
new(TelemetryConstants.TokenType, tokenType));
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ internal void LogSuccessMetrics(
CacheLevel cacheLevel,
long totalDurationInUs,
AuthenticationResultMetadata authResultMetadata,
ILoggerAdapter logger);
ILoggerAdapter logger,
DateTimeOffset expiresOn);

internal void IncrementSuccessCounter(string platform,
ApiEvent.ApiIds apiId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public void LogSuccessMetrics(
CacheLevel cacheLevel,
long totalDurationInUs,
AuthenticationResultMetadata authResultMetadata,
ILoggerAdapter logger)
ILoggerAdapter logger,
DateTimeOffset expiresOn)
{
// No op
}
Expand Down Expand Up @@ -50,5 +51,6 @@ void IOtelInstrumentation.IncrementSuccessCounter(string platform,
{
// No op
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ internal static class TelemetryConstants
public const string IsProactiveRefresh = "IsProactiveRefresh";
public const string CallerSdkId = "CallerSdkId";
public const string CallerSdkVersion = "CallerSdkVersion";
public const string MsalVersionPlatform = "MsalVersionPlatform";

#endregion
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public async Task ProactiveRefreshTriggers_WithTelemetry_Test()

private long ValidateSuccessMetrics(MeterProvider meterProvider, List<Metric> exportedMetrics)
{
Assert.HasCount(5, exportedMetrics);
Assert.HasCount(6, exportedMetrics);

foreach (var metric in exportedMetrics)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public async Task AcquireTokenOTelTestAsync()
await AcquireTokenMsalClientExceptionAsync().ConfigureAwait(false);

s_meterProvider.ForceFlush();
VerifyMetrics(6, _exportedMetrics, 2, 2);
VerifyMetrics(7, _exportedMetrics, 2, 2);
}
}

Expand All @@ -91,7 +91,7 @@ public async Task AcquireTokenOTelTestWithExtensionAsync()
await AcquireTokenMsalClientExceptionAsync().ConfigureAwait(false);

s_meterProvider.ForceFlush();
VerifyMetrics(6, _exportedMetrics, 2, 2);
VerifyMetrics(7, _exportedMetrics, 2, 2);
}
}

Expand Down Expand Up @@ -147,7 +147,7 @@ public async Task ProactiveTokenRefresh_ValidResponse_ClientCredential_Async()
Assert.AreEqual(CacheRefreshReason.NotApplicable, result.AuthenticationResultMetadata.CacheRefreshReason);

s_meterProvider.ForceFlush();
VerifyMetrics(5, _exportedMetrics, 4, 0);
VerifyMetrics(6, _exportedMetrics, 4, 0);
}
}

Expand Down Expand Up @@ -222,7 +222,7 @@ public async Task ProactiveTokenRefresh_ValidResponse_MSI_Async()
Assert.AreEqual(CacheRefreshReason.NotApplicable, result.AuthenticationResultMetadata.CacheRefreshReason);

s_meterProvider.ForceFlush();
VerifyMetrics(5, _exportedMetrics, 4, 0);
VerifyMetrics(6, _exportedMetrics, 4, 0);
}
}

Expand Down Expand Up @@ -279,7 +279,7 @@ public async Task ProactiveTokenRefresh_ValidResponse_OBO_Async()
Assert.AreEqual(CacheRefreshReason.NotApplicable, result.AuthenticationResultMetadata.CacheRefreshReason);

s_meterProvider.ForceFlush();
VerifyMetrics(5, _exportedMetrics, 4, 0);
VerifyMetrics(6, _exportedMetrics, 4, 0);
}
}

Expand Down Expand Up @@ -329,7 +329,7 @@ public async Task ProactiveTokenRefresh_AadUnavailableResponse_Async()
Thread.Sleep(1000);

s_meterProvider.ForceFlush();
VerifyMetrics(4, _exportedMetrics, 3, 1);
VerifyMetrics(5, _exportedMetrics, 3, 1);
}
}

Expand Down Expand Up @@ -577,6 +577,26 @@ private void VerifyMetrics(int expectedMetricCount, List<Metric> exportedMetrics
AssertTags(metricPoint.Tags, expectedTags);
}

break;

case "MsalRemainingTokenLifetime.1A":
Trace.WriteLine("Verify the metrics captured for MsalRemainingTokenLifetime.1A histogram.");
Assert.AreEqual(MetricType.Histogram, exportedItem.MetricType);

expectedTags.Add(TelemetryConstants.MsalVersionPlatform);
expectedTags.Add(TelemetryConstants.ApiId);
expectedTags.Add(TelemetryConstants.TokenSource);
expectedTags.Add(TelemetryConstants.CacheLevel);
expectedTags.Add(TelemetryConstants.CacheRefreshReason);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test validates tags but never asserts the recorded histogram value

expectedTags.Add(TelemetryConstants.TokenType);

foreach (var metricPoint in exportedItem.GetMetricPoints())
{
AssertTags(metricPoint.Tags, expectedTags);
Assert.IsGreaterThan((long)0, metricPoint.GetHistogramCount(), "Histogram should have at least one recorded value.");
Assert.IsGreaterThanOrEqualTo(0.0, metricPoint.GetHistogramSum(), "Remaining token lifetime should be non-negative.");
}

break;
default:
Assert.Fail("Unexpected metrics logged.");
Expand Down
Loading