From 08b60af1bca8cffff8ba0a72164fb7505ffe114d Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Tue, 13 Aug 2024 15:53:06 -0700 Subject: [PATCH] Log registered IApiDescriptionProvider implementations on load (#57318) * Log registered IApiDescriptionProvider implementations on load * Use internal API for new constructor --- .../ApiDescriptionGroupCollectionProvider.cs | 24 +++++++++++++++++-- ...oApiExplorerServiceCollectionExtensions.cs | 6 ++++- .../MvcApiExplorerMvcCoreBuilderExtensions.cs | 7 +++++- .../Mvc.FunctionalTests/ApiExplorerTest.cs | 13 ++++++++++ 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/Mvc/Mvc.ApiExplorer/src/ApiDescriptionGroupCollectionProvider.cs b/src/Mvc/Mvc.ApiExplorer/src/ApiDescriptionGroupCollectionProvider.cs index b0eb899a42e2..5bd7bce0cc10 100644 --- a/src/Mvc/Mvc.ApiExplorer/src/ApiDescriptionGroupCollectionProvider.cs +++ b/src/Mvc/Mvc.ApiExplorer/src/ApiDescriptionGroupCollectionProvider.cs @@ -3,16 +3,18 @@ using System.Linq; using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.ApiExplorer; /// -public class ApiDescriptionGroupCollectionProvider : IApiDescriptionGroupCollectionProvider +public partial class ApiDescriptionGroupCollectionProvider : IApiDescriptionGroupCollectionProvider { private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; private readonly IApiDescriptionProvider[] _apiDescriptionProviders; private ApiDescriptionGroupCollection? _apiDescriptionGroups; + private readonly ILogger? _logger; /// /// Creates a new instance of . @@ -28,7 +30,15 @@ public ApiDescriptionGroupCollectionProvider( IEnumerable apiDescriptionProviders) { _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; - _apiDescriptionProviders = apiDescriptionProviders.OrderBy(item => item.Order).ToArray(); + _apiDescriptionProviders = [.. apiDescriptionProviders.OrderBy(item => item.Order)]; + } + + internal ApiDescriptionGroupCollectionProvider( + IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, + IEnumerable apiDescriptionProviders, + ILoggerFactory loggerFactory) : this(actionDescriptorCollectionProvider, apiDescriptionProviders) + { + _logger = loggerFactory.CreateLogger(); } /// @@ -52,6 +62,10 @@ private ApiDescriptionGroupCollection GetCollection(ActionDescriptorCollection a foreach (var provider in _apiDescriptionProviders) { + if (_logger is not null) + { + Log.ApiDescriptionProviderExecuting(_logger, provider.GetType().Name, provider.GetType().Assembly.GetName().Name, provider.GetType().Assembly.GetName().Version?.ToString()); + } provider.OnProvidersExecuting(context); } @@ -67,4 +81,10 @@ private ApiDescriptionGroupCollection GetCollection(ActionDescriptorCollection a return new ApiDescriptionGroupCollection(groups, actionDescriptors.Version); } + + private static partial class Log + { + [LoggerMessage(2, LogLevel.Debug, "Executing API description provider '{ProviderName}' from assembly {ProviderAssembly} v{AssemblyVersion}.", EventName = "ApiDescriptionProviderExecuting")] + public static partial void ApiDescriptionProviderExecuting(ILogger logger, string providerName, string? providerAssembly, string? assemblyVersion); + } } diff --git a/src/Mvc/Mvc.ApiExplorer/src/DependencyInjection/EndpointMethodInfoApiExplorerServiceCollectionExtensions.cs b/src/Mvc/Mvc.ApiExplorer/src/DependencyInjection/EndpointMethodInfoApiExplorerServiceCollectionExtensions.cs index f4648bab0df8..6144ddeb1769 100644 --- a/src/Mvc/Mvc.ApiExplorer/src/DependencyInjection/EndpointMethodInfoApiExplorerServiceCollectionExtensions.cs +++ b/src/Mvc/Mvc.ApiExplorer/src/DependencyInjection/EndpointMethodInfoApiExplorerServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; namespace Microsoft.Extensions.DependencyInjection; @@ -21,7 +22,10 @@ public static IServiceCollection AddEndpointsApiExplorer(this IServiceCollection { // Try to add default services in case MVC services aren't added. services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(sp => new ApiDescriptionGroupCollectionProvider( + sp.GetRequiredService(), + sp.GetServices(), + sp.GetRequiredService())); services.TryAddEnumerable( ServiceDescriptor.Transient()); diff --git a/src/Mvc/Mvc.ApiExplorer/src/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs b/src/Mvc/Mvc.ApiExplorer/src/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs index 35ef2e0c3275..6135d91a59b0 100644 --- a/src/Mvc/Mvc.ApiExplorer/src/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs +++ b/src/Mvc/Mvc.ApiExplorer/src/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs @@ -3,7 +3,9 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; namespace Microsoft.Extensions.DependencyInjection; @@ -30,7 +32,10 @@ public static IMvcCoreBuilder AddApiExplorer(this IMvcCoreBuilder builder) [RequiresUnreferencedCode("MVC does not currently support trimming or native AOT.", Url = "https://aka.ms/aspnet/trimming")] internal static void AddApiExplorerServices(IServiceCollection services) { - services.TryAddSingleton(); + services.TryAddSingleton(sp => new ApiDescriptionGroupCollectionProvider( + sp.GetRequiredService(), + sp.GetServices(), + sp.GetRequiredService())); services.TryAddEnumerable( ServiceDescriptor.Transient()); } diff --git a/src/Mvc/test/Mvc.FunctionalTests/ApiExplorerTest.cs b/src/Mvc/test/Mvc.FunctionalTests/ApiExplorerTest.cs index 0ed76f604947..71d1ab1cc1ee 100644 --- a/src/Mvc/test/Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/src/Mvc/test/Mvc.FunctionalTests/ApiExplorerTest.cs @@ -1552,6 +1552,19 @@ public async Task ApiAction_ForActionWithVoidResponseType(string path, Type type }); } + [Fact] + public async Task ApiExplorer_LogsInvokedDescriptionProvidersOnStartup() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/ApiExplorerHttpMethod/All"); + + // Assert + Assert.Contains(TestSink.Writes, w => w.EventId.Name?.Equals("ApiDescriptionProviderExecuting", StringComparison.Ordinal) == true); + Assert.Contains(TestSink.Writes, w => w.LoggerName.Equals("Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionGroupCollectionProvider", StringComparison.Ordinal)); + Assert.Contains(TestSink.Writes, w => w.Message.Equals("Executing API description provider 'DefaultApiDescriptionProvider' from assembly Microsoft.AspNetCore.Mvc.ApiExplorer v9.0.0.0.", StringComparison.Ordinal)); + Assert.Contains(TestSink.Writes, w => w.Message.Equals("Executing API description provider 'JsonPatchOperationsArrayProvider' from assembly Microsoft.AspNetCore.Mvc.NewtonsoftJson v42.42.42.42.", StringComparison.Ordinal)); + } + private IEnumerable GetSortedMediaTypes(ApiExplorerResponseType apiResponseType) { return apiResponseType.ResponseFormats