// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using Microsoft.AspNetCore.Mvc.ViewComponents; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal { internal static class MvcViewFeaturesLoggerExtensions { private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; private static readonly string[] EmptyArguments = new string[0]; private static readonly Action _viewComponentExecuting; private static readonly Action _viewComponentExecuted; private static readonly Action _partialViewFound; private static readonly Action, Exception> _partialViewNotFound; private static readonly Action _partialViewResultExecuting; private static readonly Action _antiforgeryTokenInvalid; private static readonly Action _viewComponentResultExecuting; private static readonly Action _viewResultExecuting; private static readonly Action _viewFound; private static readonly Action, Exception> _viewNotFound; static MvcViewFeaturesLoggerExtensions() { _viewComponentExecuting = LoggerMessage.Define( LogLevel.Debug, 1, "Executing view component {ViewComponentName} with arguments ({Arguments})."); _viewComponentExecuted = LoggerMessage.Define( LogLevel.Debug, 2, "Executed view component {ViewComponentName} in {ElapsedMilliseconds}ms and returned " + "{ViewComponentResult}"); _partialViewResultExecuting = LoggerMessage.Define( LogLevel.Information, 1, "Executing PartialViewResult, running view at path {Path}."); _partialViewFound = LoggerMessage.Define( LogLevel.Debug, 2, "The partial view '{PartialViewName}' was found."); _partialViewNotFound = LoggerMessage.Define>( LogLevel.Error, 3, "The partial view '{PartialViewName}' was not found. Searched locations: {SearchedViewLocations}"); _antiforgeryTokenInvalid = LoggerMessage.Define( LogLevel.Information, 1, "Antiforgery token validation failed. {Message}"); _viewComponentResultExecuting = LoggerMessage.Define( LogLevel.Information, 1, "Executing ViewComponentResult, running {ViewComponentName}."); _viewResultExecuting = LoggerMessage.Define( LogLevel.Information, 1, "Executing ViewResult, running view at path {Path}."); _viewFound = LoggerMessage.Define( LogLevel.Debug, 2, "The view '{ViewName}' was found."); _viewNotFound = LoggerMessage.Define>( LogLevel.Error, 3, "The view '{ViewName}' was not found. Searched locations: {SearchedViewLocations}"); } public static IDisposable ViewComponentScope(this ILogger logger, ViewComponentContext context) { return logger.BeginScope(new ViewComponentLogScope(context.ViewComponentDescriptor)); } public static void ViewComponentExecuting( this ILogger logger, ViewComponentContext context, object[] arguments) { var formattedArguments = GetFormattedArguments(arguments); _viewComponentExecuting(logger, context.ViewComponentDescriptor.DisplayName, formattedArguments, null); } private static string[] GetFormattedArguments(object[] arguments) { if (arguments == null || arguments.Length == 0) { return EmptyArguments; } var formattedArguments = new string[arguments.Length]; for (var i = 0; i < formattedArguments.Length; i++) { formattedArguments[i] = Convert.ToString(arguments[i]); } return formattedArguments; } public static void ViewComponentExecuted( this ILogger logger, ViewComponentContext context, long startTimestamp, object result) { // Don't log if logging wasn't enabled at start of request as time will be wildly wrong. if (startTimestamp != 0) { var currentTimestamp = Stopwatch.GetTimestamp(); var elapsed = new TimeSpan((long)(TimestampToTicks * (currentTimestamp - startTimestamp))); _viewComponentExecuted( logger, context.ViewComponentDescriptor.DisplayName, elapsed.TotalMilliseconds, Convert.ToString(result), null); } } public static void PartialViewFound( this ILogger logger, string partialViewName) { _partialViewFound(logger, partialViewName, null); } public static void PartialViewNotFound( this ILogger logger, string partialViewName, IEnumerable searchedLocations) { _partialViewNotFound(logger, partialViewName, searchedLocations, null); } public static void PartialViewResultExecuting(this ILogger logger, IView view) { _partialViewResultExecuting(logger, view.Path, null); } public static void AntiforgeryTokenInvalid(this ILogger logger, string message, Exception exception) { _antiforgeryTokenInvalid(logger, message, exception); } public static void ViewComponentResultExecuting(this ILogger logger, string viewComponentName) { if (logger.IsEnabled(LogLevel.Information)) { _viewComponentResultExecuting(logger, viewComponentName, null); } } public static void ViewComponentResultExecuting(this ILogger logger, Type viewComponentType) { if (logger.IsEnabled(LogLevel.Information)) { _viewComponentResultExecuting(logger, viewComponentType.Name, null); } } public static void ViewResultExecuting(this ILogger logger, IView view) { _viewResultExecuting(logger, view.Path, null); } public static void ViewFound(this ILogger logger, string viewName) { _viewFound(logger, viewName, null); } public static void ViewNotFound(this ILogger logger, string viewName, IEnumerable searchedLocations) { _viewNotFound(logger, viewName, searchedLocations, null); } private class ViewComponentLogScope : IReadOnlyList> { private readonly ViewComponentDescriptor _descriptor; public ViewComponentLogScope(ViewComponentDescriptor descriptor) { _descriptor = descriptor; } public KeyValuePair this[int index] { get { if (index == 0) { return new KeyValuePair("ViewComponentName", _descriptor.DisplayName); } else if (index == 1) { return new KeyValuePair("ViewComponentId", _descriptor.Id); } throw new IndexOutOfRangeException(nameof(index)); } } public int Count { get { return 2; } } public IEnumerator> GetEnumerator() { for (int i = 0; i < Count; ++i) { yield return this[i]; } } public override string ToString() { return _descriptor.DisplayName; } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } } }