diff --git a/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/api/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.netstandard2.0.cs b/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/api/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.netstandard2.0.cs index cf97b3ef334f..be60cb0c5ef7 100644 --- a/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/api/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.netstandard2.0.cs +++ b/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/api/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.netstandard2.0.cs @@ -8,7 +8,7 @@ internal AuthenticationEventMetadataAttribute() { } } public partial class AuthenticationEventResponseHandler : Microsoft.Azure.WebJobs.Host.Bindings.IValueBinder, Microsoft.Azure.WebJobs.Host.Bindings.IValueProvider { - public AuthenticationEventResponseHandler() { } + internal AuthenticationEventResponseHandler() { } public Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework.AuthenticationEventRequestBase Request { get { throw null; } } public Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework.AuthenticationEventResponse Response { get { throw null; } } public System.Type Type { get { throw null; } } @@ -34,10 +34,15 @@ public enum EventDefinition [Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.AuthenticationEventMetadataAttribute(typeof(Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.TokenIssuanceStart.TokenIssuanceStartRequest), "microsoft.graph.authenticationEvent.TokenIssuanceStart", "TokenIssuanceStart", "CloudEventActionableTemplate.json")] TokenIssuanceStart = 0, } - public static partial class EventTriggerMetrics + public sealed partial class EventTriggerMetrics { - public static string MetricsHeader; - public static string ProductName; + internal EventTriggerMetrics() { } + public const string MetricsHeader = "User-Agent"; + public const string ProductName = "AuthenticationEvents"; + public static string Framework { get { throw null; } } + public static Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.EventTriggerMetrics Instance { get { throw null; } } + public static string Platform { get { throw null; } } + public static string ProductVersion { get { throw null; } } } public enum EventType { diff --git a/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/AuthenticationEventBinding.cs b/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/AuthenticationEventBinding.cs index 6a84c115ce3e..ed04e1746a71 100644 --- a/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/AuthenticationEventBinding.cs +++ b/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/AuthenticationEventBinding.cs @@ -85,7 +85,8 @@ private static IReadOnlyDictionary GetBindingDataContract(Paramete public async Task BindAsync(object value, ValueBindingContext context) { var request = (HttpRequestMessage)value; - AuthenticationEventResponseHandler eventResponseHandler = (AuthenticationEventResponseHandler)request.Properties[AuthenticationEventResponseHandler.EventResponseProperty]; + AuthenticationEventResponseHandler eventResponseHandler = + (AuthenticationEventResponseHandler)request.Properties[AuthenticationEventResponseHandler.EventResponseProperty]; try { if (request == null) @@ -98,7 +99,15 @@ public async Task BindAsync(object value, ValueBindingContext cont AuthenticationEventMetadata eventMetadata = GetEventAndValidateSchema(payload); eventResponseHandler.Request = GetRequestForEvent(request, payload, eventMetadata, Claims); - return new TriggerData(new AuthenticationEventValueBinder(eventResponseHandler.Request, _authEventTriggerAttr), GetBindingData(context, value, eventResponseHandler)) + + return new TriggerData( + new AuthenticationEventValueBinder( + eventResponseHandler.Request, + _authEventTriggerAttr), + GetBindingData( + context, + value, + eventResponseHandler)) { ReturnValueProvider = eventResponseHandler }; @@ -119,7 +128,12 @@ public async Task BindAsync(object value, ValueBindingContext cont /// A TriggerData Object with the failed event request based on the event. With the related request status set. /// /// - private TriggerData GetFaultyRequest(ValueBindingContext context, object value, HttpRequestMessage request, AuthenticationEventResponseHandler eventResponseHandler, Exception ex) + private TriggerData GetFaultyRequest( + ValueBindingContext context, + object value, + HttpRequestMessage request, + AuthenticationEventResponseHandler eventResponseHandler, + Exception ex) { eventResponseHandler.Request = _parameterInfo.ParameterType == typeof(string) ? new EmptyRequest(request) : AuthenticationEventMetadata.CreateEventRequest(request, _parameterInfo.ParameterType, null); eventResponseHandler.Request.StatusMessage = ex.Message; diff --git a/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/AuthenticationEventResponseHandler.cs b/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/AuthenticationEventResponseHandler.cs index 7f3c3e63afeb..ce53035e3e56 100644 --- a/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/AuthenticationEventResponseHandler.cs +++ b/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/AuthenticationEventResponseHandler.cs @@ -31,18 +31,20 @@ public class AuthenticationEventResponseHandler : IValueBinder public AuthenticationEventResponse Response { get => _response; - internal set + private set { if (value != null) { _response = value; // Set metrics on the headers for the response - EventTriggerMetrics.SetMetricHeaders(_response); + EventTriggerMetrics.Instance.SetMetricHeaders(_response); } } } + internal AuthenticationEventResponseHandler() { } + /// Gets the type. /// The type. public Type Type => typeof(AuthenticationEventResponse).MakeByRefType(); diff --git a/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/Common/EventTriggerMetrics.cs b/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/Common/EventTriggerMetrics.cs index f004be886d26..792ee8bf04ff 100644 --- a/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/Common/EventTriggerMetrics.cs +++ b/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/Common/EventTriggerMetrics.cs @@ -1,62 +1,85 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Runtime.InteropServices; -using System.Runtime.Versioning; namespace Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents { /// /// Static class to set the metric headers for each event trigger. /// - public static class EventTriggerMetrics + public sealed class EventTriggerMetrics { /// - /// The client library's product name + /// Default constructor for eventmetrics + /// + private EventTriggerMetrics() + { + var assembly = AssemblyName.GetAssemblyName("Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.dll"); + + ProductVersion = assembly.Version.ToString(); + Framework = RuntimeInformation.FrameworkDescription; + Platform = RuntimeInformation.OSDescription ?? "unknown"; + } + + /// + /// Lazy immplementation to make sure that only one instance is created and returned, while delaying the creation till needed. /// - public static string ProductName = "AuthenticationEvents"; + private static readonly Lazy lazyEventTrigger = new Lazy(() => new EventTriggerMetrics()); /// - /// Current executing assembly + /// The singleton instance for event trigger metrics + /// + public static EventTriggerMetrics Instance + { + get + { + return lazyEventTrigger.Value; + } + } + + /// + /// The client library's product name /// - private static readonly Assembly assembly = typeof(EventTriggerMetrics).Assembly; + public const string ProductName = "AuthenticationEvents"; /// /// Get the platform of the event trigger based on the OS. /// /// OS Name - private static string Platform => RuntimeInformation.OSDescription ?? "unknown"; + public static string Platform { get; private set; } /// /// Product version of the event trigger. Example: 1.0.0-beta, 1.0.0, 2.0.0 /// - private static string ProductVersion => assembly.GetCustomAttribute()?.Version; + public static string ProductVersion { get; private set; } /// - /// Runtime of the event trigger. Example: .NET, JS, TS, PY + /// Framework of the event trigger. Example: .NET, JS, TS, PY /// - private static string Framework => assembly.GetCustomAttribute()?.FrameworkName; + public static string Framework { get; private set; } /// /// Header key to add the metrics to. /// User-Agent is the standard header to add metrics to recommended by Azure sdk guidelines. /// - public static string MetricsHeader = "User-Agent"; + public const string MetricsHeader = "User-Agent"; /// /// Set the metrics on the response message. /// /// The reponse message - internal static void SetMetricHeaders(HttpResponseMessage message) + internal void SetMetricHeaders(HttpResponseMessage message) { if (message != null) { var headers = message.Headers; - headers.AddOrReplaceToHeader(GetHeaderValue(Platform, ProductVersion, Framework)); + AddOrReplaceToHeader(headers, GetHeaderValueFormatted(Platform, ProductVersion, Framework)); } } @@ -65,7 +88,7 @@ internal static void SetMetricHeaders(HttpResponseMessage message) /// /// to add the key and value to /// Header value to add - private static void AddOrReplaceToHeader(this HttpHeaders headers, string value) + private static void AddOrReplaceToHeader(HttpHeaders headers, string value) { if (headers.Contains(MetricsHeader)) { @@ -76,9 +99,9 @@ private static void AddOrReplaceToHeader(this HttpHeaders headers, string value) headers.Add(MetricsHeader, value); } - private static string GetHeaderValue(string platform, string version, string runtime) + private static string GetHeaderValueFormatted(string platform, string version, string runtime) { - return $"azsdk-net-{ProductName}/{version} ({runtime}; {platform})"; + return $"azsdk-net-{ProductName}/{version} ({runtime}; {platform.Trim()})"; } } } diff --git a/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/Helpers.cs b/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/Helpers.cs index 35b2802d0267..3fa708dce523 100644 --- a/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/Helpers.cs +++ b/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/Helpers.cs @@ -59,7 +59,7 @@ internal static HttpResponseMessage HttpErrorResponse(Exception ex) }; // Set the metrics on header - EventTriggerMetrics.SetMetricHeaders(response); + EventTriggerMetrics.Instance.SetMetricHeaders(response); return response; } diff --git a/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/tests/Common/EventTriggerMetricsTests.cs b/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/tests/Common/EventTriggerMetricsTests.cs index 8664318fb3c8..551c68ecf629 100644 --- a/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/tests/Common/EventTriggerMetricsTests.cs +++ b/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/tests/Common/EventTriggerMetricsTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.Diagnostics.Eventing.Reader; using System.Linq; using System.Net.Http; @@ -16,7 +17,7 @@ public class EventTriggerMetricsTests public void TestSetMetricHeadersNull() { HttpResponseMessage message = null; - Assert.DoesNotThrow(() => EventTriggerMetrics.SetMetricHeaders(message)); + Assert.DoesNotThrow(() => EventTriggerMetrics.Instance.SetMetricHeaders(message)); Assert.IsNull( anObject: message, message: "Verify AuthenticationEventRequestBase is not set to anything when null."); @@ -27,13 +28,32 @@ public void TestSetMetricHeadersNull() public void TestSetMetricHeaders() { HttpResponseMessage message = new() { }; - EventTriggerMetrics.SetMetricHeaders(message); + EventTriggerMetrics.Instance.SetMetricHeaders(message); + + Assert.IsNotEmpty(EventTriggerMetrics.Framework, "Framework should note be empty"); + Assert.IsNotEmpty(EventTriggerMetrics.ProductVersion, "ProductVersion should not be empty"); + Assert.IsNotEmpty(EventTriggerMetrics.Platform, "Platform should not be empty"); var headers = message.Headers; Assert.IsTrue(headers.Contains(EventTriggerMetrics.MetricsHeader)); string headerValue = headers.GetValues(EventTriggerMetrics.MetricsHeader).First(); - Assert.AreEqual(GetTestHeaderValue(), headerValue, "Verify default header values match"); + Assert.IsNotEmpty(headerValue, "Header value should not be empty or null"); + } + + [Test] + [Description("Verify if sets the headers is in the correct format")] + public void TestSetMetricFormat() + { + HttpResponseMessage message = new() { }; + EventTriggerMetrics.Instance.SetMetricHeaders(message); + + var headers = message.Headers; + Assert.IsTrue(headers.Contains(EventTriggerMetrics.MetricsHeader)); + + string headerValue = headers.GetValues(EventTriggerMetrics.MetricsHeader).First(); + + Assert.AreEqual(GetTestHeaderValue(), headerValue, "Verify format of header values matches"); } [Test] @@ -43,7 +63,7 @@ public void TestAppendMetricHeaders() HttpResponseMessage message = new() { }; message.Headers.Add(EventTriggerMetrics.MetricsHeader, "test"); - EventTriggerMetrics.SetMetricHeaders(message); + EventTriggerMetrics.Instance.SetMetricHeaders(message); var headers = message.Headers; Assert.IsTrue(headers.Contains(EventTriggerMetrics.MetricsHeader)); @@ -53,11 +73,15 @@ public void TestAppendMetricHeaders() } private static string GetTestHeaderValue( - string framework = ".NETStandard,Version=v2.0", - string version = "1.0.0.0", - string platform = "Microsoft Windows 10.0.22621") + string framework = null, + string version = null, + string platform = null) { - return $"azsdk-net-{EventTriggerMetrics.ProductName}/{version} ({framework}; {platform})"; + framework ??= EventTriggerMetrics.Framework; + version ??= EventTriggerMetrics.ProductVersion; + platform ??= EventTriggerMetrics.Platform; + + return $"azsdk-net-{EventTriggerMetrics.ProductName}/{version} ({framework}; {platform.Trim()})"; } } } diff --git a/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/tests/ConfigProviderTests.cs b/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/tests/ConfigProviderTests.cs index b58d209f80f0..f4b326068ffe 100644 --- a/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/tests/ConfigProviderTests.cs +++ b/sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/tests/ConfigProviderTests.cs @@ -1,6 +1,6 @@ -using System; using System.Net; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using static Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Tests.TestHelper; @@ -34,7 +34,7 @@ public async Task PostConfigProviderTests(string url, HttpStatusCode httpStatusC { AuthenticationEventResponseHandler eventsResponseHandler = GetAuthenticationEventResponseHandler(mockedRequest); - eventsResponseHandler.Response = GetContentForHttpStatus(httpStatusCode); + eventsResponseHandler.SetValueAsync(GetContentForHttpStatus(httpStatusCode), CancellationToken.None); } });