Skip to content
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

Update MvcCoreLoggerExtensions to use LoggerMessageAttribute #40439

Merged
merged 1 commit into from
Mar 1, 2022
Merged
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
20 changes: 17 additions & 3 deletions src/Mvc/Mvc.Core/src/ChallengeResult.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand All @@ -10,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc;
/// <summary>
/// An <see cref="ActionResult"/> that on execution invokes <see cref="M:HttpContext.ChallengeAsync"/>.
/// </summary>
public class ChallengeResult : ActionResult
public partial class ChallengeResult : ActionResult
{
/// <summary>
/// Initializes a new instance of <see cref="ChallengeResult"/>.
Expand Down Expand Up @@ -97,8 +98,7 @@ public override async Task ExecuteResultAsync(ActionContext context)
var httpContext = context.HttpContext;
var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<ChallengeResult>();

logger.ChallengeResultExecuting(AuthenticationSchemes);
Log.ChallengeResultExecuting(logger, AuthenticationSchemes);

if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0)
{
Expand All @@ -112,4 +112,18 @@ public override async Task ExecuteResultAsync(ActionContext context)
await httpContext.ChallengeAsync(Properties);
}
}

private static partial class Log
{
public static void ChallengeResultExecuting(ILogger logger, IList<string> schemes)
{
if (logger.IsEnabled(LogLevel.Information))
{
ChallengeResultExecuting(logger, schemes.ToArray());
}
}

[LoggerMessage(1, LogLevel.Information, "Executing ChallengeResult with authentication schemes ({Schemes}).", EventName = "ChallengeResultExecuting", SkipEnabledCheck = true)]
private static partial void ChallengeResultExecuting(ILogger logger, string[] schemes);
}
}
20 changes: 17 additions & 3 deletions src/Mvc/Mvc.Core/src/ForbidResult.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand All @@ -10,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc;
/// <summary>
/// An <see cref="ActionResult"/> that on execution invokes <see cref="M:HttpContext.ForbidAsync"/>.
/// </summary>
public class ForbidResult : ActionResult
public partial class ForbidResult : ActionResult
{
/// <summary>
/// Initializes a new instance of <see cref="ForbidResult"/>.
Expand Down Expand Up @@ -98,8 +99,7 @@ public override async Task ExecuteResultAsync(ActionContext context)

var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<ForbidResult>();

logger.ForbidResultExecuting(AuthenticationSchemes);
Log.ForbidResultExecuting(logger, AuthenticationSchemes);

if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0)
{
Expand All @@ -113,4 +113,18 @@ public override async Task ExecuteResultAsync(ActionContext context)
await httpContext.ForbidAsync(Properties);
}
}

private static partial class Log
{
public static void ForbidResultExecuting(ILogger logger, IList<string> authenticationSchemes)
{
if (logger.IsEnabled(LogLevel.Information))
{
ForbidResultExecuting(logger, authenticationSchemes.ToArray());
}
}

[LoggerMessage(1, LogLevel.Information, $"Executing {nameof(ForbidResult)} with authentication schemes ({{Schemes}}).", EventName = "ForbidResultExecuting", SkipEnabledCheck = true)]
private static partial void ForbidResultExecuting(ILogger logger, string[] schemes);
}
}
5 changes: 4 additions & 1 deletion src/Mvc/Mvc.Core/src/Formatters/FormatFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public void OnResourceExecuting(ResourceExecutingContext context)
// type than requested e.g. OK if "text/*" requested and action supports "text/plain".
if (!IsSuperSetOfAnySupportedMediaType(contentType, supportedMediaTypes))
{
_logger.ActionDoesNotSupportFormatFilterContentType(contentType, supportedMediaTypes);
Log.ActionDoesNotSupportFormatFilterContentType(_logger, contentType, supportedMediaTypes);
context.Result = new NotFoundResult();
}
}
Expand Down Expand Up @@ -185,6 +185,9 @@ private static partial class Log
[LoggerMessage(1, LogLevel.Debug, "Could not find a media type for the format '{FormatFilterContentType}'.", EventName = "UnsupportedFormatFilterContentType")]
public static partial void UnsupportedFormatFilterContentType(ILogger logger, string formatFilterContentType);

[LoggerMessage(2, LogLevel.Debug, "Current action does not support the content type '{FormatFilterContentType}'. The supported content types are '{SupportedMediaTypes}'.", EventName = "ActionDoesNotSupportFormatFilterContentType")]
public static partial void ActionDoesNotSupportFormatFilterContentType(ILogger logger, string formatFilterContentType, MediaTypeCollection supportedMediaTypes);

[LoggerMessage(3, LogLevel.Debug, "Cannot apply content type '{FormatFilterContentType}' to the response as current action had explicitly set a preferred content type.", EventName = "CannotApplyFormatFilterContentType")]
public static partial void CannotApplyFormatFilterContentType(ILogger logger, string formatFilterContentType);

Expand Down
12 changes: 8 additions & 4 deletions src/Mvc/Mvc.Core/src/Infrastructure/ClientErrorResultFilter.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;

namespace Microsoft.AspNetCore.Mvc.Infrastructure;

internal class ClientErrorResultFilter : IAlwaysRunResultFilter, IOrderedFilter
internal partial class ClientErrorResultFilter : IAlwaysRunResultFilter, IOrderedFilter
{
internal const int FilterOrder = -2000;
private readonly IClientErrorFactory _clientErrorFactory;
Expand Down Expand Up @@ -56,7 +54,13 @@ public void OnResultExecuting(ResultExecutingContext context)
return;
}

_logger.TransformingClientError(context.Result.GetType(), result.GetType(), clientError.StatusCode);
Log.TransformingClientError(_logger, context.Result.GetType(), result.GetType(), clientError.StatusCode);
context.Result = result;
}

private static partial class Log
{
[LoggerMessage(49, LogLevel.Trace, "Replacing {InitialActionResultType} with status code {StatusCode} with {ReplacedActionResultType}.", EventName = "ClientErrorResultFilter")]
public static partial void TransformingClientError(ILogger logger, Type initialActionResultType, Type replacedActionResultType, int? statusCode);
}
}
88 changes: 81 additions & 7 deletions src/Mvc/Mvc.Core/src/Infrastructure/ControllerActionInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
#nullable enable

using System.Diagnostics;
using System.Globalization;
using System.Runtime.ExceptionServices;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging;
using Resources = Microsoft.AspNetCore.Mvc.Core.Resources;

namespace Microsoft.AspNetCore.Mvc.Infrastructure;

internal class ControllerActionInvoker : ResourceInvoker, IActionInvoker
internal partial class ControllerActionInvoker : ResourceInvoker, IActionInvoker
{
private readonly ControllerActionInvokerCacheEntry _cacheEntry;
private readonly ControllerContext _controllerContext;
Expand Down Expand Up @@ -64,12 +66,10 @@ private Task Next(ref State next, ref Scope scope, ref object? state, ref bool i
var controllerContext = _controllerContext;

_cursor.Reset();

_logger.ExecutingControllerFactory(controllerContext);
Log.ExecutingControllerFactory(_logger, controllerContext);

_instance = _cacheEntry.ControllerFactory(controllerContext);

_logger.ExecutedControllerFactory(controllerContext);
Log.ExecutedControllerFactory(_logger, controllerContext);

_arguments = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);

Expand Down Expand Up @@ -424,7 +424,7 @@ static async Task Logged(ControllerActionInvoker invoker)
controllerContext,
arguments,
controller);
logger.ActionMethodExecuting(controllerContext, orderedArguments);
Log.ActionMethodExecuting(logger, controllerContext, orderedArguments);
var stopwatch = ValueStopwatch.StartNew();
var actionResultValueTask = actionMethodExecutor.Execute(invoker._mapper, objectMethodExecutor, controller!, orderedArguments);
if (actionResultValueTask.IsCompletedSuccessfully)
Expand All @@ -437,7 +437,7 @@ static async Task Logged(ControllerActionInvoker invoker)
}

invoker._result = result;
logger.ActionMethodExecuted(controllerContext, result, stopwatch.GetElapsedTime());
Log.ActionMethodExecuted(logger, controllerContext, result, stopwatch.GetElapsedTime());
}
finally
{
Expand Down Expand Up @@ -584,4 +584,78 @@ private sealed class ActionExecutedContextSealed : ActionExecutedContext
{
public ActionExecutedContextSealed(ActionContext actionContext, IList<IFilterMetadata> filters, object controller) : base(actionContext, filters, controller) { }
}

// Internal for unit testing
internal static new partial class Log
Copy link
Contributor Author

Choose a reason for hiding this comment

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

{
public static void ExecutingControllerFactory(ILogger logger, ControllerContext context)
{
if (!logger.IsEnabled(LogLevel.Debug))
{
return;
}

var controllerType = context.ActionDescriptor.ControllerTypeInfo.AsType();
var controllerName = TypeNameHelper.GetTypeDisplayName(controllerType);
ExecutingControllerFactory(logger, controllerName, controllerType.Assembly.GetName().Name);
}

[LoggerMessage(1, LogLevel.Debug, "Executing controller factory for controller {Controller} ({AssemblyName})", EventName = "ControllerFactoryExecuting", SkipEnabledCheck = true)]
private static partial void ExecutingControllerFactory(ILogger logger, string controller, string? assemblyName);

public static void ExecutedControllerFactory(ILogger logger, ControllerContext context)
{
if (!logger.IsEnabled(LogLevel.Debug))
{
return;
}

var controllerType = context.ActionDescriptor.ControllerTypeInfo.AsType();
var controllerName = TypeNameHelper.GetTypeDisplayName(controllerType);
ExecutedControllerFactory(logger, controllerName, controllerType.Assembly.GetName().Name);
}

[LoggerMessage(2, LogLevel.Debug, "Executed controller factory for controller {Controller} ({AssemblyName})", EventName = "ControllerFactoryExecuted", SkipEnabledCheck = true)]
private static partial void ExecutedControllerFactory(ILogger logger, string controller, string? assemblyName);

public static void ActionMethodExecuting(ILogger logger, ControllerContext context, object?[]? arguments)
{
if (logger.IsEnabled(LogLevel.Information))
{
var actionName = context.ActionDescriptor.DisplayName;

var validationState = context.ModelState.ValidationState;
ActionMethodExecuting(logger, actionName, validationState);

if (arguments != null && logger.IsEnabled(LogLevel.Trace))
{
var convertedArguments = new string?[arguments.Length];
for (var i = 0; i < arguments.Length; i++)
{
convertedArguments[i] = Convert.ToString(arguments[i], CultureInfo.InvariantCulture);
}

ActionMethodExecutingWithArguments(logger, actionName, convertedArguments);
}
}
}

[LoggerMessage(101, LogLevel.Information, "Executing action method {ActionName} - Validation state: {ValidationState}", EventName = "ActionMethodExecuting", SkipEnabledCheck = true)]
private static partial void ActionMethodExecuting(ILogger logger, string? actionName, ModelValidationState validationState);

[LoggerMessage(102, LogLevel.Trace, "Executing action method {ActionName} with arguments ({Arguments})", EventName = "ActionMethodExecutingWithArguments", SkipEnabledCheck = true)]
private static partial void ActionMethodExecutingWithArguments(ILogger logger, string? actionName, string?[] arguments);

public static void ActionMethodExecuted(ILogger logger, ControllerContext context, IActionResult result, TimeSpan timeSpan)
{
if (logger.IsEnabled(LogLevel.Information))
{
var actionName = context.ActionDescriptor.DisplayName;
ActionMethodExecuted(logger, actionName, Convert.ToString(result, CultureInfo.InvariantCulture), timeSpan.TotalMilliseconds);
}
}

[LoggerMessage(103, LogLevel.Information, "Executed action method {ActionName}, returned result {ActionResult} in {ElapsedMilliseconds}ms.", EventName = "ActionMethodExecuted", SkipEnabledCheck = true)]
private static partial void ActionMethodExecuted(ILogger logger, string? actionName, string? actionResult, double elapsedMilliseconds);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#nullable enable

using System.Collections.ObjectModel;
using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Formatters;
Expand Down Expand Up @@ -157,7 +158,7 @@ public DefaultOutputFormatterSelector(IOptions<MvcOptions> options, ILoggerFacto

if (selectedFormatter != null)
{
_logger.FormatterSelected(selectedFormatter, context);
Log.FormatterSelected(_logger, selectedFormatter, context);
}

return selectedFormatter;
Expand Down Expand Up @@ -299,6 +300,21 @@ private static void ValidateContentTypes(MediaTypeCollection contentTypes)

private static partial class Log
{
public static void FormatterSelected(
ILogger logger,
IOutputFormatter outputFormatter,
OutputFormatterCanWriteContext context)
{
if (logger.IsEnabled(LogLevel.Debug))
{
var contentType = Convert.ToString(context.ContentType, CultureInfo.InvariantCulture);
FormatterSelected(logger, outputFormatter, contentType);
}
}

[LoggerMessage(2, LogLevel.Debug, "Selected output formatter '{OutputFormatter}' and content type '{ContentType}' to write the response.", EventName = "FormatterSelected", SkipEnabledCheck = true)]
public static partial void FormatterSelected(ILogger logger, IOutputFormatter outputFormatter, string? contentType);

[LoggerMessage(4, LogLevel.Debug, "No information found on request to perform content negotiation.", EventName = "NoAcceptForNegotiation")]
public static partial void NoAcceptForNegotiation(ILogger logger);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public virtual Task ExecuteAsync(ActionContext context, FileContentResult result
throw new ArgumentNullException(nameof(result));
}

Logger.ExecutingFileResult(result);
Log.ExecutingFileResult(Logger, result);

var (range, rangeLength, serveBody) = SetHeadersAndLog(
context,
Expand Down Expand Up @@ -88,6 +88,18 @@ protected virtual Task WriteFileAsync(ActionContext context, FileContentResult r

private static partial class Log
{
public static void ExecutingFileResult(ILogger logger, FileResult fileResult)
{
if (logger.IsEnabled(LogLevel.Information))
{
var fileResultType = fileResult.GetType().Name;
ExecutingFileResultWithNoFileName(logger, fileResultType, fileResult.FileDownloadName);
}
}

[LoggerMessage(2, LogLevel.Information, "Executing {FileResultType}, sending file with download name '{FileDownloadName}' ...", EventName = "ExecutingFileResultWithNoFileName", SkipEnabledCheck = true)]
private static partial void ExecutingFileResultWithNoFileName(ILogger logger, string fileResultType, string fileDownloadName);

[LoggerMessage(17, LogLevel.Debug, "Writing the requested range of bytes to the body...", EventName = "WritingRangeToBody")]
public static partial void WritingRangeToBody(ILogger logger);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public virtual async Task ExecuteAsync(ActionContext context, FileStreamResult r

using (result.FileStream)
{
Logger.ExecutingFileResult(result);
Log.ExecutingFileResult(Logger, result);

long? fileLength = null;
if (result.FileStream.CanSeek)
Expand Down Expand Up @@ -100,6 +100,18 @@ protected virtual Task WriteFileAsync(

private static partial class Log
{
public static void ExecutingFileResult(ILogger logger, FileResult fileResult)
{
if (logger.IsEnabled(LogLevel.Information))
{
var fileResultType = fileResult.GetType().Name;
ExecutingFileResultWithNoFileName(logger, fileResultType, fileResult.FileDownloadName);
}
}

[LoggerMessage(1, LogLevel.Information, "Executing {FileResultType}, sending file with download name '{FileDownloadName}' ...", EventName = "ExecutingFileResultWithNoFileName", SkipEnabledCheck = true)]
private static partial void ExecutingFileResultWithNoFileName(ILogger logger, string fileResultType, string fileDownloadName);

[LoggerMessage(17, LogLevel.Debug, "Writing the requested range of bytes to the body...", EventName = "WritingRangeToBody")]
public static partial void WritingRangeToBody(ILogger logger);
}
Expand Down
10 changes: 8 additions & 2 deletions src/Mvc/Mvc.Core/src/Infrastructure/ModelStateInvalidFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure;
/// added to all types and actions annotated with <see cref="ApiControllerAttribute"/>.
/// See <see cref="ApiBehaviorOptions"/> for ways to configure this filter.
/// </summary>
public class ModelStateInvalidFilter : IActionFilter, IOrderedFilter
public partial class ModelStateInvalidFilter : IActionFilter, IOrderedFilter
{
internal const int FilterOrder = -2000;

Expand Down Expand Up @@ -75,8 +75,14 @@ public void OnActionExecuting(ActionExecutingContext context)
{
if (context.Result == null && !context.ModelState.IsValid)
{
_logger.ModelStateInvalidFilterExecuting();
Log.ModelStateInvalidFilterExecuting(_logger);
context.Result = _apiBehaviorOptions.InvalidModelStateResponseFactory(context);
}
}

private static partial class Log
{
[LoggerMessage(1, LogLevel.Debug, "The request has model state errors, returning an error response.", EventName = "ModelStateInvalidFilterExecuting")]
public static partial void ModelStateInvalidFilterExecuting(ILogger logger);
}
}
Loading