Skip to content

Commit f69b2d7

Browse files
committed
Update MvcCoreLoggerExtensions to use LoggerMessageAttribute
MvcCoreLoggerExtensions includes the log messages for a whole slew of MVC types. Consequently it re-uses event ids and is hard maintain. This PR simultaenously updates the type to use LoggerMessageAttribute, while also attempts to move as many of the individual log statements to the appropriate type. This change unearthed incorrect reuse of event ids within the same type that has also been corrected by assigning new ids. Fixes dotnet#32087
1 parent 4bfc1a6 commit f69b2d7

24 files changed

+870
-1156
lines changed

src/Mvc/Mvc.Core/src/ChallengeResult.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Linq;
45
using Microsoft.AspNetCore.Authentication;
56
using Microsoft.Extensions.DependencyInjection;
67
using Microsoft.Extensions.Logging;
@@ -10,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc;
1011
/// <summary>
1112
/// An <see cref="ActionResult"/> that on execution invokes <see cref="M:HttpContext.ChallengeAsync"/>.
1213
/// </summary>
13-
public class ChallengeResult : ActionResult
14+
public partial class ChallengeResult : ActionResult
1415
{
1516
/// <summary>
1617
/// Initializes a new instance of <see cref="ChallengeResult"/>.
@@ -97,8 +98,7 @@ public override async Task ExecuteResultAsync(ActionContext context)
9798
var httpContext = context.HttpContext;
9899
var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
99100
var logger = loggerFactory.CreateLogger<ChallengeResult>();
100-
101-
logger.ChallengeResultExecuting(AuthenticationSchemes);
101+
Log.ChallengeResultExecuting(logger, AuthenticationSchemes);
102102

103103
if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0)
104104
{
@@ -112,4 +112,18 @@ public override async Task ExecuteResultAsync(ActionContext context)
112112
await httpContext.ChallengeAsync(Properties);
113113
}
114114
}
115+
116+
private static partial class Log
117+
{
118+
public static void ChallengeResultExecuting(ILogger logger, IList<string> schemes)
119+
{
120+
if (logger.IsEnabled(LogLevel.Information))
121+
{
122+
ChallengeResultExecuting(logger, schemes.ToArray());
123+
}
124+
}
125+
126+
[LoggerMessage(1, LogLevel.Information, "Executing ChallengeResult with authentication schemes ({Schemes}).", EventName = "ChallengeResultExecuting", SkipEnabledCheck = true)]
127+
private static partial void ChallengeResultExecuting(ILogger logger, string[] schemes);
128+
}
115129
}

src/Mvc/Mvc.Core/src/ForbidResult.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Linq;
45
using Microsoft.AspNetCore.Authentication;
56
using Microsoft.Extensions.DependencyInjection;
67
using Microsoft.Extensions.Logging;
@@ -10,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc;
1011
/// <summary>
1112
/// An <see cref="ActionResult"/> that on execution invokes <see cref="M:HttpContext.ForbidAsync"/>.
1213
/// </summary>
13-
public class ForbidResult : ActionResult
14+
public partial class ForbidResult : ActionResult
1415
{
1516
/// <summary>
1617
/// Initializes a new instance of <see cref="ForbidResult"/>.
@@ -98,8 +99,7 @@ public override async Task ExecuteResultAsync(ActionContext context)
9899

99100
var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
100101
var logger = loggerFactory.CreateLogger<ForbidResult>();
101-
102-
logger.ForbidResultExecuting(AuthenticationSchemes);
102+
Log.ForbidResultExecuting(logger, AuthenticationSchemes);
103103

104104
if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0)
105105
{
@@ -113,4 +113,18 @@ public override async Task ExecuteResultAsync(ActionContext context)
113113
await httpContext.ForbidAsync(Properties);
114114
}
115115
}
116+
117+
private static partial class Log
118+
{
119+
public static void ForbidResultExecuting(ILogger logger, IList<string> authenticationSchemes)
120+
{
121+
if (logger.IsEnabled(LogLevel.Information))
122+
{
123+
ForbidResultExecuting(logger, authenticationSchemes.ToArray());
124+
}
125+
}
126+
127+
[LoggerMessage(1, LogLevel.Information, $"Executing {nameof(ForbidResult)} with authentication schemes ({{Schemes}}).", EventName = "ForbidResultExecuting", SkipEnabledCheck = true)]
128+
private static partial void ForbidResultExecuting(ILogger logger, string[] schemes);
129+
}
116130
}

src/Mvc/Mvc.Core/src/Formatters/FormatFilter.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public void OnResourceExecuting(ResourceExecutingContext context)
110110
// type than requested e.g. OK if "text/*" requested and action supports "text/plain".
111111
if (!IsSuperSetOfAnySupportedMediaType(contentType, supportedMediaTypes))
112112
{
113-
_logger.ActionDoesNotSupportFormatFilterContentType(contentType, supportedMediaTypes);
113+
Log.ActionDoesNotSupportFormatFilterContentType(_logger, contentType, supportedMediaTypes);
114114
context.Result = new NotFoundResult();
115115
}
116116
}
@@ -185,6 +185,9 @@ private static partial class Log
185185
[LoggerMessage(1, LogLevel.Debug, "Could not find a media type for the format '{FormatFilterContentType}'.", EventName = "UnsupportedFormatFilterContentType")]
186186
public static partial void UnsupportedFormatFilterContentType(ILogger logger, string formatFilterContentType);
187187

188+
[LoggerMessage(2, LogLevel.Debug, "Current action does not support the content type '{FormatFilterContentType}'. The supported content types are '{SupportedMediaTypes}'.", EventName = "ActionDoesNotSupportFormatFilterContentType")]
189+
public static partial void ActionDoesNotSupportFormatFilterContentType(ILogger logger, string formatFilterContentType, MediaTypeCollection supportedMediaTypes);
190+
188191
[LoggerMessage(3, LogLevel.Debug, "Cannot apply content type '{FormatFilterContentType}' to the response as current action had explicitly set a preferred content type.", EventName = "CannotApplyFormatFilterContentType")]
189192
public static partial void CannotApplyFormatFilterContentType(ILogger logger, string formatFilterContentType);
190193

src/Mvc/Mvc.Core/src/Infrastructure/ClientErrorResultFilter.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
#nullable enable
5-
64
using Microsoft.AspNetCore.Mvc.Filters;
75
using Microsoft.Extensions.Logging;
86

97
namespace Microsoft.AspNetCore.Mvc.Infrastructure;
108

11-
internal class ClientErrorResultFilter : IAlwaysRunResultFilter, IOrderedFilter
9+
internal partial class ClientErrorResultFilter : IAlwaysRunResultFilter, IOrderedFilter
1210
{
1311
internal const int FilterOrder = -2000;
1412
private readonly IClientErrorFactory _clientErrorFactory;
@@ -56,7 +54,13 @@ public void OnResultExecuting(ResultExecutingContext context)
5654
return;
5755
}
5856

59-
_logger.TransformingClientError(context.Result.GetType(), result.GetType(), clientError.StatusCode);
57+
Log.TransformingClientError(_logger, context.Result.GetType(), result.GetType(), clientError.StatusCode);
6058
context.Result = result;
6159
}
60+
61+
private static partial class Log
62+
{
63+
[LoggerMessage(49, LogLevel.Trace, "Replacing {InitialActionResultType} with status code {StatusCode} with {ReplacedActionResultType}.", EventName = "ClientErrorResultFilter")]
64+
public static partial void TransformingClientError(ILogger logger, Type initialActionResultType, Type replacedActionResultType, int? statusCode);
65+
}
6266
}

src/Mvc/Mvc.Core/src/Infrastructure/ControllerActionInvoker.cs

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44
#nullable enable
55

66
using System.Diagnostics;
7+
using System.Globalization;
78
using System.Runtime.ExceptionServices;
89
using Microsoft.AspNetCore.Mvc.Abstractions;
910
using Microsoft.AspNetCore.Mvc.Filters;
11+
using Microsoft.AspNetCore.Mvc.ModelBinding;
1012
using Microsoft.Extensions.Internal;
1113
using Microsoft.Extensions.Logging;
1214
using Resources = Microsoft.AspNetCore.Mvc.Core.Resources;
1315

1416
namespace Microsoft.AspNetCore.Mvc.Infrastructure;
1517

16-
internal class ControllerActionInvoker : ResourceInvoker, IActionInvoker
18+
internal partial class ControllerActionInvoker : ResourceInvoker, IActionInvoker
1719
{
1820
private readonly ControllerActionInvokerCacheEntry _cacheEntry;
1921
private readonly ControllerContext _controllerContext;
@@ -64,12 +66,10 @@ private Task Next(ref State next, ref Scope scope, ref object? state, ref bool i
6466
var controllerContext = _controllerContext;
6567

6668
_cursor.Reset();
67-
68-
_logger.ExecutingControllerFactory(controllerContext);
69+
Log.ExecutingControllerFactory(_logger, controllerContext);
6970

7071
_instance = _cacheEntry.ControllerFactory(controllerContext);
71-
72-
_logger.ExecutedControllerFactory(controllerContext);
72+
Log.ExecutedControllerFactory(_logger, controllerContext);
7373

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

@@ -424,7 +424,7 @@ static async Task Logged(ControllerActionInvoker invoker)
424424
controllerContext,
425425
arguments,
426426
controller);
427-
logger.ActionMethodExecuting(controllerContext, orderedArguments);
427+
Log.ActionMethodExecuting(logger, controllerContext, orderedArguments);
428428
var stopwatch = ValueStopwatch.StartNew();
429429
var actionResultValueTask = actionMethodExecutor.Execute(invoker._mapper, objectMethodExecutor, controller!, orderedArguments);
430430
if (actionResultValueTask.IsCompletedSuccessfully)
@@ -437,7 +437,7 @@ static async Task Logged(ControllerActionInvoker invoker)
437437
}
438438

439439
invoker._result = result;
440-
logger.ActionMethodExecuted(controllerContext, result, stopwatch.GetElapsedTime());
440+
Log.ActionMethodExecuted(logger, controllerContext, result, stopwatch.GetElapsedTime());
441441
}
442442
finally
443443
{
@@ -584,4 +584,78 @@ private sealed class ActionExecutedContextSealed : ActionExecutedContext
584584
{
585585
public ActionExecutedContextSealed(ActionContext actionContext, IList<IFilterMetadata> filters, object controller) : base(actionContext, filters, controller) { }
586586
}
587+
588+
// Internal for unit testing
589+
internal static new partial class Log
590+
{
591+
public static void ExecutingControllerFactory(ILogger logger, ControllerContext context)
592+
{
593+
if (!logger.IsEnabled(LogLevel.Debug))
594+
{
595+
return;
596+
}
597+
598+
var controllerType = context.ActionDescriptor.ControllerTypeInfo.AsType();
599+
var controllerName = TypeNameHelper.GetTypeDisplayName(controllerType);
600+
ExecutingControllerFactory(logger, controllerName, controllerType.Assembly.GetName().Name);
601+
}
602+
603+
[LoggerMessage(1, LogLevel.Debug, "Executing controller factory for controller {Controller} ({AssemblyName})", EventName = "ControllerFactoryExecuting", SkipEnabledCheck = true)]
604+
private static partial void ExecutingControllerFactory(ILogger logger, string controller, string? assemblyName);
605+
606+
public static void ExecutedControllerFactory(ILogger logger, ControllerContext context)
607+
{
608+
if (!logger.IsEnabled(LogLevel.Debug))
609+
{
610+
return;
611+
}
612+
613+
var controllerType = context.ActionDescriptor.ControllerTypeInfo.AsType();
614+
var controllerName = TypeNameHelper.GetTypeDisplayName(controllerType);
615+
ExecutedControllerFactory(logger, controllerName, controllerType.Assembly.GetName().Name);
616+
}
617+
618+
[LoggerMessage(2, LogLevel.Debug, "Executed controller factory for controller {Controller} ({AssemblyName})", EventName = "ControllerFactoryExecuted", SkipEnabledCheck = true)]
619+
private static partial void ExecutedControllerFactory(ILogger logger, string controller, string? assemblyName);
620+
621+
public static void ActionMethodExecuting(ILogger logger, ControllerContext context, object?[]? arguments)
622+
{
623+
if (logger.IsEnabled(LogLevel.Information))
624+
{
625+
var actionName = context.ActionDescriptor.DisplayName;
626+
627+
var validationState = context.ModelState.ValidationState;
628+
ActionMethodExecuting(logger, actionName, validationState);
629+
630+
if (arguments != null && logger.IsEnabled(LogLevel.Trace))
631+
{
632+
var convertedArguments = new string?[arguments.Length];
633+
for (var i = 0; i < arguments.Length; i++)
634+
{
635+
convertedArguments[i] = Convert.ToString(arguments[i], CultureInfo.InvariantCulture);
636+
}
637+
638+
ActionMethodExecutingWithArguments(logger, actionName, convertedArguments);
639+
}
640+
}
641+
}
642+
643+
[LoggerMessage(101, LogLevel.Information, "Executing action method {ActionName} - Validation state: {ValidationState}", EventName = "ActionMethodExecuting", SkipEnabledCheck = true)]
644+
private static partial void ActionMethodExecuting(ILogger logger, string? actionName, ModelValidationState validationState);
645+
646+
[LoggerMessage(102, LogLevel.Trace, "Executing action method {ActionName} with arguments ({Arguments})", EventName = "ActionMethodExecutingWithArguments", SkipEnabledCheck = true)]
647+
private static partial void ActionMethodExecutingWithArguments(ILogger logger, string? actionName, string?[] arguments);
648+
649+
public static void ActionMethodExecuted(ILogger logger, ControllerContext context, IActionResult result, TimeSpan timeSpan)
650+
{
651+
if (logger.IsEnabled(LogLevel.Information))
652+
{
653+
var actionName = context.ActionDescriptor.DisplayName;
654+
ActionMethodExecuted(logger, actionName, Convert.ToString(result, CultureInfo.InvariantCulture), timeSpan.TotalMilliseconds);
655+
}
656+
}
657+
658+
[LoggerMessage(103, LogLevel.Information, "Executed action method {ActionName}, returned result {ActionResult} in {ElapsedMilliseconds}ms.", EventName = "ActionMethodExecuted", SkipEnabledCheck = true)]
659+
private static partial void ActionMethodExecuted(ILogger logger, string? actionName, string? actionResult, double elapsedMilliseconds);
660+
}
587661
}

src/Mvc/Mvc.Core/src/Infrastructure/DefaultOutputFormatterSelector.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#nullable enable
55

66
using System.Collections.ObjectModel;
7+
using System.Globalization;
78
using Microsoft.AspNetCore.Http;
89
using Microsoft.AspNetCore.Mvc.Core;
910
using Microsoft.AspNetCore.Mvc.Formatters;
@@ -157,7 +158,7 @@ public DefaultOutputFormatterSelector(IOptions<MvcOptions> options, ILoggerFacto
157158

158159
if (selectedFormatter != null)
159160
{
160-
_logger.FormatterSelected(selectedFormatter, context);
161+
Log.FormatterSelected(_logger, selectedFormatter, context);
161162
}
162163

163164
return selectedFormatter;
@@ -299,6 +300,21 @@ private static void ValidateContentTypes(MediaTypeCollection contentTypes)
299300

300301
private static partial class Log
301302
{
303+
public static void FormatterSelected(
304+
ILogger logger,
305+
IOutputFormatter outputFormatter,
306+
OutputFormatterCanWriteContext context)
307+
{
308+
if (logger.IsEnabled(LogLevel.Debug))
309+
{
310+
var contentType = Convert.ToString(context.ContentType, CultureInfo.InvariantCulture);
311+
FormatterSelected(logger, outputFormatter, contentType);
312+
}
313+
}
314+
315+
[LoggerMessage(2, LogLevel.Debug, "Selected output formatter '{OutputFormatter}' and content type '{ContentType}' to write the response.", EventName = "FormatterSelected", SkipEnabledCheck = true)]
316+
public static partial void FormatterSelected(ILogger logger, IOutputFormatter outputFormatter, string? contentType);
317+
302318
[LoggerMessage(4, LogLevel.Debug, "No information found on request to perform content negotiation.", EventName = "NoAcceptForNegotiation")]
303319
public static partial void NoAcceptForNegotiation(ILogger logger);
304320

src/Mvc/Mvc.Core/src/Infrastructure/FileContentResultExecutor.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public virtual Task ExecuteAsync(ActionContext context, FileContentResult result
3535
throw new ArgumentNullException(nameof(result));
3636
}
3737

38-
Logger.ExecutingFileResult(result);
38+
Log.ExecutingFileResult(Logger, result);
3939

4040
var (range, rangeLength, serveBody) = SetHeadersAndLog(
4141
context,
@@ -88,6 +88,18 @@ protected virtual Task WriteFileAsync(ActionContext context, FileContentResult r
8888

8989
private static partial class Log
9090
{
91+
public static void ExecutingFileResult(ILogger logger, FileResult fileResult)
92+
{
93+
if (logger.IsEnabled(LogLevel.Information))
94+
{
95+
var fileResultType = fileResult.GetType().Name;
96+
ExecutingFileResultWithNoFileName(logger, fileResultType, fileResult.FileDownloadName);
97+
}
98+
}
99+
100+
[LoggerMessage(2, LogLevel.Information, "Executing {FileResultType}, sending file with download name '{FileDownloadName}' ...", EventName = "ExecutingFileResultWithNoFileName", SkipEnabledCheck = true)]
101+
private static partial void ExecutingFileResultWithNoFileName(ILogger logger, string fileResultType, string fileDownloadName);
102+
91103
[LoggerMessage(17, LogLevel.Debug, "Writing the requested range of bytes to the body...", EventName = "WritingRangeToBody")]
92104
public static partial void WritingRangeToBody(ILogger logger);
93105
}

src/Mvc/Mvc.Core/src/Infrastructure/FileStreamResultExecutor.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public virtual async Task ExecuteAsync(ActionContext context, FileStreamResult r
3737

3838
using (result.FileStream)
3939
{
40-
Logger.ExecutingFileResult(result);
40+
Log.ExecutingFileResult(Logger, result);
4141

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

101101
private static partial class Log
102102
{
103+
public static void ExecutingFileResult(ILogger logger, FileResult fileResult)
104+
{
105+
if (logger.IsEnabled(LogLevel.Information))
106+
{
107+
var fileResultType = fileResult.GetType().Name;
108+
ExecutingFileResultWithNoFileName(logger, fileResultType, fileResult.FileDownloadName);
109+
}
110+
}
111+
112+
[LoggerMessage(1, LogLevel.Information, "Executing {FileResultType}, sending file with download name '{FileDownloadName}' ...", EventName = "ExecutingFileResultWithNoFileName", SkipEnabledCheck = true)]
113+
private static partial void ExecutingFileResultWithNoFileName(ILogger logger, string fileResultType, string fileDownloadName);
114+
103115
[LoggerMessage(17, LogLevel.Debug, "Writing the requested range of bytes to the body...", EventName = "WritingRangeToBody")]
104116
public static partial void WritingRangeToBody(ILogger logger);
105117
}

src/Mvc/Mvc.Core/src/Infrastructure/ModelStateInvalidFilter.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure;
1414
/// added to all types and actions annotated with <see cref="ApiControllerAttribute"/>.
1515
/// See <see cref="ApiBehaviorOptions"/> for ways to configure this filter.
1616
/// </summary>
17-
public class ModelStateInvalidFilter : IActionFilter, IOrderedFilter
17+
public partial class ModelStateInvalidFilter : IActionFilter, IOrderedFilter
1818
{
1919
internal const int FilterOrder = -2000;
2020

@@ -75,8 +75,14 @@ public void OnActionExecuting(ActionExecutingContext context)
7575
{
7676
if (context.Result == null && !context.ModelState.IsValid)
7777
{
78-
_logger.ModelStateInvalidFilterExecuting();
78+
Log.ModelStateInvalidFilterExecuting(_logger);
7979
context.Result = _apiBehaviorOptions.InvalidModelStateResponseFactory(context);
8080
}
8181
}
82+
83+
private static partial class Log
84+
{
85+
[LoggerMessage(1, LogLevel.Debug, "The request has model state errors, returning an error response.", EventName = "ModelStateInvalidFilterExecuting")]
86+
public static partial void ModelStateInvalidFilterExecuting(ILogger logger);
87+
}
8288
}

0 commit comments

Comments
 (0)