Skip to content

Commit

Permalink
Add caller sdk id and version to telemetry server side and client side (
Browse files Browse the repository at this point in the history
#4864)

* Add caller sdk id and version to telemetry server side and client side

* Address comments

* Address comments

* Add constraints and use WithClientName and WithClientVersion APIs

* Update tests to expect null for client name and client version

* Update tests for telemetry

* Add tests

* Update src/client/Microsoft.Identity.Client/Platforms/Features/OpenTelemetry/OtelInstrumentation.cs

Co-authored-by: Tim Hannifin <87669370+TimHannMSFT@users.noreply.github.com>

* Update src/client/Microsoft.Identity.Client/Platforms/Features/OpenTelemetry/OtelInstrumentation.cs

Co-authored-by: Tim Hannifin <87669370+TimHannMSFT@users.noreply.github.com>

* Update src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs

Co-authored-by: Tim Hannifin <87669370+TimHannMSFT@users.noreply.github.com>

* Update src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs

Co-authored-by: Tim Hannifin <87669370+TimHannMSFT@users.noreply.github.com>

* Add caller sdk id and version tags to failed metrics as well

* Update src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs

Co-authored-by: Gladwin Johnson <90415114+gladjohn@users.noreply.github.com>

* Update src/client/Microsoft.Identity.Client/AppConfig/BaseAbstractApplicationBuilder.cs

Co-authored-by: Gladwin Johnson <90415114+gladjohn@users.noreply.github.com>

* Address comments

---------

Co-authored-by: Tim Hannifin <87669370+TimHannMSFT@users.noreply.github.com>
Co-authored-by: Gladwin Johnson <90415114+gladjohn@users.noreply.github.com>
  • Loading branch information
3 people committed Sep 16, 2024
1 parent b5177a4 commit c1ecec7
Show file tree
Hide file tree
Showing 28 changed files with 337 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,6 @@ protected T WithScopes(IEnumerable<string> scopes)
return this as T;
}

/// <summary>
/// Sets Extra Query Parameters for the query string in the HTTP authentication request.
/// </summary>
/// <param name="extraQueryParameters">This parameter will be appended as is to the query string in the HTTP authentication request to the authority
/// as a string of segments of the form <c>key=value</c> separated by an ampersand character.
/// The parameter can be null.</param>
/// <returns>The builder to chain the .With methods.</returns>
public T WithExtraQueryParameters(Dictionary<string, string> extraQueryParameters)
{
CommonParameters.ExtraQueryParameters = extraQueryParameters ??
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
return this as T;
}

/// <summary>
/// Sets claims in the query. Use when the AAD admin has enabled conditional access. Acquiring the token normally will result in a
/// <see cref="MsalUiRequiredException"/> with the <see cref="MsalServiceException.Claims"/> property set. Retry the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,20 @@ public T WithCorrelationId(Guid correlationId)
return (T)this;
}

/// <summary>
/// Sets Extra Query Parameters for the query string in the HTTP authentication request.
/// </summary>
/// <param name="extraQueryParameters">This parameter will be appended as is to the query string in the HTTP authentication request to the authority
/// as a string of segments of the form <c>key=value</c> separated by an ampersand character.
/// The parameter can be null.</param>
/// <returns>The builder to chain the .With methods.</returns>
public T WithExtraQueryParameters(Dictionary<string, string> extraQueryParameters)
{
CommonParameters.ExtraQueryParameters = extraQueryParameters ??
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
return this as T;
}

/// <summary>
/// Validates the parameters of the AcquireToken operation.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,28 +256,6 @@ public T WithTenantId(string tenantId)
return this as T;
}

/// <summary>
/// Sets the name of the calling application for telemetry purposes.
/// </summary>
/// <param name="clientName">The name of the application for telemetry purposes.</param>
/// <returns></returns>
public T WithClientName(string clientName)
{
Config.ClientName = GetValueIfNotEmpty(Config.ClientName, clientName);
return this as T;
}

/// <summary>
/// Sets the version of the calling application for telemetry purposes.
/// </summary>
/// <param name="clientVersion">The version of the calling application for telemetry purposes.</param>
/// <returns></returns>
public T WithClientVersion(string clientVersion)
{
Config.ClientVersion = GetValueIfNotEmpty(Config.ClientVersion, clientVersion);
return this as T;
}

/// <summary>
/// Sets application options, which can, for instance have been read from configuration files.
/// See https://aka.ms/msal-net-application-configuration.
Expand Down Expand Up @@ -643,10 +621,5 @@ public T WithB2CAuthority(string authorityUri)
}

#endregion

private static string GetValueIfNotEmpty(string original, string value)
{
return string.IsNullOrWhiteSpace(value) ? original : value;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,23 @@ public ApplicationConfiguration(MsalClientType applicationType)
break;
}
}

public const string DefaultClientName = "UnknownClient";
public const string DefaultClientVersion = "0.0.0.0";

// For telemetry, the ClientName of the application.
private string _clientName = DefaultClientName;
private string _clientName;
public string ClientName
{
get => _clientName;
internal set { _clientName = string.IsNullOrWhiteSpace(value) ? DefaultClientName : value; }
internal set { _clientName = string.IsNullOrWhiteSpace(value) ? string.Empty : value; }

}

// For telemetry, the ClientVersion of the application.
private string _clientVersion = DefaultClientVersion;
private string _clientVersion;
public string ClientVersion
{
get => _clientVersion;
internal set { _clientVersion = string.IsNullOrWhiteSpace(value) ? DefaultClientVersion : value; }
internal set { _clientVersion = string.IsNullOrWhiteSpace(value) ? string.Empty : value; }

}

public Func<object> ParentActivityOrWindowFunc { get; internal set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,28 @@ public T WithExperimentalFeatures(bool enableExperimentalFeatures = true)
return (T)this;
}

/// <summary>
/// Sets the name of the calling SDK API for telemetry purposes.
/// </summary>
/// <param name="clientName">The name of the SDK API for telemetry purposes.</param>
/// <returns></returns>
public T WithClientName(string clientName)
{
Config.ClientName = GetValueIfNotEmpty(Config.ClientName, clientName);
return this as T;
}

/// <summary>
/// Sets the version of the calling SDK for telemetry purposes.
/// </summary>
/// <param name="clientVersion">The version of the calling SDK for telemetry purposes.</param>
/// <returns></returns>
public T WithClientVersion(string clientVersion)
{
Config.ClientVersion = GetValueIfNotEmpty(Config.ClientVersion, clientVersion);
return this as T;
}

internal virtual ApplicationConfiguration BuildConfiguration()
{
ResolveAuthority();
Expand Down Expand Up @@ -295,5 +317,10 @@ internal void ValidateUseOfExperimentalFeature([System.Runtime.CompilerServices.
MsalErrorMessage.ExperimentalFeature(memberName));
}
}

internal static string GetValueIfNotEmpty(string original, string value)
{
return string.IsNullOrWhiteSpace(value) ? original : value;
}
}
}
3 changes: 3 additions & 0 deletions src/client/Microsoft.Identity.Client/Internal/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ internal static class Constants
public const string ManagedIdentityDefaultTenant = "managed_identity";
public const string CiamAuthorityHostSuffix = ".ciamlogin.com";

public const int CallerSdkIdMaxLength = 10;
public const int CallerSdkVersionMaxLength = 20;

public static string FormatEnterpriseRegistrationOnPremiseUri(string domain)
{
return $"https://enterpriseregistration.{domain}/enrollmentserver/contract";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal class LoggerHelper

public static string GetClientInfo(string clientName, string clientVersion)
{
if (!string.IsNullOrEmpty(clientName) && !ApplicationConfiguration.DefaultClientName.Equals(clientName))
if (!string.IsNullOrEmpty(clientName))
{
// space is intentional for formatting of the message
if (string.IsNullOrEmpty(clientVersion))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ protected override async Task<AuthenticationResult> ExecuteAsync(CancellationTok
// Use a linked token source, in case the original cancellation token source is disposed before this background task completes.
using var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
return GetAccessTokenAsync(tokenSource.Token, logger);
}, logger, ServiceBundle, AuthenticationRequestParameters.RequestContext.ApiEvent.ApiId);
}, logger, ServiceBundle, AuthenticationRequestParameters.RequestContext.ApiEvent.ApiId,
AuthenticationRequestParameters.RequestContext.ApiEvent.CallerSdkApiId,
AuthenticationRequestParameters.RequestContext.ApiEvent.CallerSdkVersion);
}
}
catch (MsalServiceException e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ protected override async Task<AuthenticationResult> ExecuteAsync(CancellationTok
// Use a linked token source, in case the original cancellation token source is disposed before this background task completes.
using var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
return GetAccessTokenAsync(tokenSource.Token, logger);
}, logger, ServiceBundle, AuthenticationRequestParameters.RequestContext.ApiEvent.ApiId);
}, logger, ServiceBundle, AuthenticationRequestParameters.RequestContext.ApiEvent.ApiId,
AuthenticationRequestParameters.RequestContext.ApiEvent.CallerSdkApiId,
AuthenticationRequestParameters.RequestContext.ApiEvent.CallerSdkVersion);
}
}
catch (MsalServiceException e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,9 @@ protected override async Task<AuthenticationResult> ExecuteAsync(CancellationTok
// Use a linked token source, in case the original cancellation token source is disposed before this background task completes.
using var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
return RefreshRtOrFetchNewAccessTokenAsync(tokenSource.Token);
}, logger, ServiceBundle, AuthenticationRequestParameters.RequestContext.ApiEvent.ApiId);
}, logger, ServiceBundle, AuthenticationRequestParameters.RequestContext.ApiEvent.ApiId,
AuthenticationRequestParameters.RequestContext.ApiEvent.CallerSdkApiId,
AuthenticationRequestParameters.RequestContext.ApiEvent.CallerSdkVersion);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using Microsoft.Identity.Client.TelemetryCore.TelemetryClient;
using Microsoft.Identity.Client.TelemetryCore.OpenTelemetry;
using Microsoft.Identity.Client.Internal.Broker;
using System.Runtime.ConstrainedExecution;

namespace Microsoft.Identity.Client.Internal.Requests
{
Expand Down Expand Up @@ -91,7 +92,7 @@ public async Task<AuthenticationResult> RunAsync(CancellationToken cancellationT

UpdateTelemetry(measureDurationResult.Milliseconds + measureTelemetryDurationResult.Milliseconds, apiEvent, authenticationResult);
LogMetricsFromAuthResult(authenticationResult, AuthenticationRequestParameters.RequestContext.Logger);
LogSuccessTelemetryToOtel(authenticationResult, apiEvent.ApiId, measureDurationResult.Microseconds);
LogSuccessTelemetryToOtel(authenticationResult, apiEvent, measureDurationResult.Microseconds);

return authenticationResult;
}
Expand All @@ -104,38 +105,42 @@ public async Task<AuthenticationResult> RunAsync(CancellationToken cancellationT
}
AuthenticationRequestParameters.RequestContext.Logger.ErrorPii(ex);

LogFailureTelemetryToOtel(ex.ErrorCode, apiEvent.ApiId, apiEvent.CacheInfo);
LogFailureTelemetryToOtel(ex.ErrorCode, apiEvent, apiEvent.CacheInfo);
throw;
}
catch (Exception ex)
{
apiEvent.ApiErrorCode = ex.GetType().Name;
AuthenticationRequestParameters.RequestContext.Logger.ErrorPii(ex);

LogFailureTelemetryToOtel(ex.GetType().Name, apiEvent.ApiId, apiEvent.CacheInfo);
LogFailureTelemetryToOtel(ex.GetType().Name, apiEvent, apiEvent.CacheInfo);
throw;
}
}

private void LogSuccessTelemetryToOtel(AuthenticationResult authenticationResult, ApiEvent.ApiIds apiId, long durationInUs)
private void LogSuccessTelemetryToOtel(AuthenticationResult authenticationResult, ApiEvent apiEvent, long durationInUs)
{
// Log metrics
ServiceBundle.PlatformProxy.OtelInstrumentation.LogSuccessMetrics(
ServiceBundle.PlatformProxy.GetProductName(),
apiId,
apiEvent.ApiId,
apiEvent.CallerSdkApiId,
apiEvent.CallerSdkVersion,
GetCacheLevel(authenticationResult),
durationInUs,
authenticationResult.AuthenticationResultMetadata,
AuthenticationRequestParameters.RequestContext.Logger);
}

private void LogFailureTelemetryToOtel(string errorCodeToLog, ApiEvent.ApiIds apiId, CacheRefreshReason cacheRefreshReason)
private void LogFailureTelemetryToOtel(string errorCodeToLog, ApiEvent apiEvent, CacheRefreshReason cacheRefreshReason)
{
// Log metrics
ServiceBundle.PlatformProxy.OtelInstrumentation.LogFailureMetrics(
ServiceBundle.PlatformProxy.GetProductName(),
errorCodeToLog,
apiId,
apiEvent.ApiId,
apiEvent.CallerSdkApiId,
apiEvent.CallerSdkVersion,
cacheRefreshReason);
}

Expand Down Expand Up @@ -242,12 +247,42 @@ private ApiEvent InitializeApiEvent(string accountId)
apiEvent.TokenType = AuthenticationRequestParameters.AuthenticationScheme.TelemetryTokenType;
apiEvent.AssertionType = GetAssertionType();

UpdateCallerSdkDetails(apiEvent);

// Give derived classes the ability to add or modify fields in the telemetry as needed.
EnrichTelemetryApiEvent(apiEvent);

return apiEvent;
}

private void UpdateCallerSdkDetails(ApiEvent apiEvent)
{
string callerSdkId;
string callerSdkVer;

// Check if ExtraQueryParameters contains caller-sdk-id and caller-sdk-ver
if (AuthenticationRequestParameters.ExtraQueryParameters.TryGetValue("caller-sdk-id", out callerSdkId))
{
AuthenticationRequestParameters.ExtraQueryParameters.Remove("caller-sdk-id");
}
else
{
callerSdkId = AuthenticationRequestParameters.RequestContext.ServiceBundle.Config.ClientName;
}

if (AuthenticationRequestParameters.ExtraQueryParameters.TryGetValue("caller-sdk-ver", out callerSdkVer))
{
AuthenticationRequestParameters.ExtraQueryParameters.Remove("caller-sdk-ver");
}
else
{
callerSdkVer = AuthenticationRequestParameters.RequestContext.ServiceBundle.Config.ClientVersion;
}

apiEvent.CallerSdkApiId = callerSdkId == null ? null : callerSdkId.Substring(0, Math.Min(callerSdkId.Length, Constants.CallerSdkIdMaxLength));
apiEvent.CallerSdkVersion = callerSdkVer == null ? null : callerSdkVer.Substring(0, Math.Min(callerSdkVer.Length, Constants.CallerSdkVersionMaxLength));
}

private AssertionType GetAssertionType()
{
if (ServiceBundle.Config.IsManagedIdentity ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ public async Task<AuthenticationResult> ExecuteAsync(CancellationToken cancellat
// Use a linked token source, in case the original cancellation token source is disposed before this background task completes.
using var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
return RefreshRtOrFailAsync(tokenSource.Token);
}, logger, ServiceBundle, AuthenticationRequestParameters.RequestContext.ApiEvent.ApiId);
}, logger, ServiceBundle, AuthenticationRequestParameters.RequestContext.ApiEvent.ApiId,
AuthenticationRequestParameters.RequestContext.ApiEvent.CallerSdkApiId,
AuthenticationRequestParameters.RequestContext.ApiEvent.CallerSdkVersion);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ internal static void ProcessFetchInBackground(
Func<Task<AuthenticationResult>> fetchAction,
ILoggerAdapter logger,
IServiceBundle serviceBundle,
ApiEvent.ApiIds apiId)
ApiEvent.ApiIds apiId,
string callerSdkId,
string callerSdkVersion)
{
_ = Task.Run(async () =>
{
Expand All @@ -95,6 +97,8 @@ internal static void ProcessFetchInBackground(
serviceBundle.PlatformProxy.OtelInstrumentation.IncrementSuccessCounter(
serviceBundle.PlatformProxy.GetProductName(),
apiId,
callerSdkId,
callerSdkVersion,
TokenSource.IdentityProvider,
CacheRefreshReason.ProactivelyRefreshed,
Cache.CacheLevel.None,
Expand All @@ -116,6 +120,8 @@ internal static void ProcessFetchInBackground(
serviceBundle.PlatformProxy.GetProductName(),
ex.ErrorCode,
apiId,
callerSdkId,
callerSdkVersion,
CacheRefreshReason.ProactivelyRefreshed);
}
catch (OperationCanceledException ex)
Expand All @@ -125,6 +131,8 @@ internal static void ProcessFetchInBackground(
serviceBundle.PlatformProxy.GetProductName(),
ex.GetType().Name,
apiId,
callerSdkId,
callerSdkVersion,
CacheRefreshReason.ProactivelyRefreshed);
}
catch (Exception ex)
Expand All @@ -134,6 +142,8 @@ internal static void ProcessFetchInBackground(
serviceBundle.PlatformProxy.GetProductName(),
ex.GetType().Name,
apiId,
callerSdkId,
callerSdkVersion,
CacheRefreshReason.ProactivelyRefreshed);
}
});
Expand Down
1 change: 1 addition & 0 deletions src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ private void AddClaims()
// no-op if resolvedClaims is null
_oAuth2Client.AddBodyParameter(OAuth2Parameter.Claims, resolvedClaims);
}

private void AddExtraHttpHeaders()
{
if (_requestParams.ExtraHttpHeaders != null)
Expand Down
Loading

0 comments on commit c1ecec7

Please sign in to comment.