由于MVC自身的特点,可以让我们记录每一个Controller下Action的执行时间以及View视图渲染完成的时间,本文采用log4net记录MVC每个Action的执行时间和View视图渲染完成时间,以及请求Action时post或get的数据。这样通过日志记录的时间方便我们定位哪一个Action和View执行的时间过长,进而采取优化的手段。
改监控程序主要继承ActionFilterAttribute类,并重写其中的OnActionExecuted、OnActionExecuting、OnResultExecuted、OnResultExecuting几个方法实现。
/// <summary>
/// 监控日志对象
/// </summary>
public class MonitorLog
{
public string ControllerName
{
get;
set;
}
public string ActionName
{
get;
set;
}
public DateTime ExecuteStartTime
{
get;
set;
}
public DateTime ExecuteEndTime
{
get;
set;
}
/// <summary>
/// Form 表单数据
/// </summary>
public NameValueCollection FormCollections
{
get;
set;
}
/// <summary>
/// URL 参数
/// </summary>
public NameValueCollection QueryCollections
{
get;
set;
}
/// <summary>
/// 监控类型
/// </summary>
public enum MonitorType
{
Action = 1,
View = 2
}
/// <summary>
/// 获取监控指标日志
/// </summary>
/// <param name="mtype"></param>
/// <returns></returns>
public string GetLoginfo(MonitorType mtype = MonitorType.Action)
{
string ActionView = "Action执行时间监控:";
string Name = "Action";
if (mtype == MonitorType.View)
{
ActionView = "View视图生成时间监控:";
Name = "View";
}
string Msg = @"
{0}
ControllerName:{1}Controller
{8}Name:{2}
开始时间:{3}
结束时间:{4}
总 时 间:{5}秒
Form表单数据:{6}
URL参数:{7}
";
return string.Format(Msg,
ActionView,
ControllerName,
ActionName,
ExecuteStartTime,
ExecuteEndTime,
(ExecuteEndTime - ExecuteStartTime).TotalSeconds,
GetCollections(FormCollections),
GetCollections(QueryCollections),
Name);
}
/// <summary>
/// 获取Post 或Get 参数
/// </summary>
/// <param name="Collections"></param>
/// <returns></returns>
public string GetCollections(NameValueCollection Collections)
{
string Parameters = string.Empty;
if (Collections == null || Collections.Count == 0)
{
return Parameters;
}
foreach (string key in Collections.Keys)
{
Parameters += string.Format("{0}={1}&", key, Collections[key]);
}
if (!string.IsNullOrWhiteSpace(Parameters) && Parameters.EndsWith("&"))
{
Parameters = Parameters.Substring(0, Parameters.Length - 1);
}
return Parameters;
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class StatisticsTrackerAttribute : ActionFilterAttribute,IExceptionFilter
{
private readonly string Key = "_thisOnActionMonitorLog_";
#region Action时间监控
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
MonitorLog MonLog = new MonitorLog();
MonLog.ExecuteStartTime = Convert.ToDateTime(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.ffff", DateTimeFormatInfo.InvariantInfo));
MonLog.ControllerName = filterContext.RouteData.Values["controller"] as string;
MonLog.ActionName = filterContext.RouteData.Values["action"] as string;
filterContext.Controller.ViewData[Key] = MonLog;
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
MonitorLog MonLog = filterContext.Controller.ViewData[Key] as MonitorLog;
MonLog.ExecuteEndTime = DateTime.Now;
MonLog.FormCollections = filterContext.HttpContext.Request.Form;//form表单提交的数据
MonLog.QueryCollections = filterContext.HttpContext.Request.QueryString;//Url 参数
LoggerHelper.Monitor(MonLog.GetLoginfo());
}
#endregion
#region View 视图生成时间监控
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
MonitorLog MonLog = filterContext.Controller.ViewData[Key] as MonitorLog;
MonLog.ExecuteStartTime = DateTime.Now;
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
MonitorLog MonLog = filterContext.Controller.ViewData[Key] as MonitorLog;
MonLog.ExecuteEndTime = DateTime.Now;
LoggerHelper.Monitor(MonLog.GetLoginfo(MonitorLog.MonitorType.View));
filterContext.Controller.ViewData.Remove(Key);
}
#endregion
#region 错误日志
public void OnException(ExceptionContext filterContext)
{
if (!filterContext.ExceptionHandled)
{
string ControllerName =string.Format("{0}Controller",filterContext.RouteData.Values["controller"] as string);
string ActionName = filterContext.RouteData.Values["action"] as string;
string ErrorMsg = string.Format("在执行 controller[{0}] 的 action[{1}] 时产生异常", ControllerName, ActionName);
LoggerHelper.Error(ErrorMsg, filterContext.Exception);
}
}
#endregion
}
我们可以在每个Controller类上或Action上直接引用 [StatisticsTracker]即可完成对该Controller或Action的监控。
我们也可以在FilterConfig.cs中注册全局监控,这样我们就可以监控每一个Controller中的Action,代码如下:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
//监控引用
filters.Add(new StatisticsTrackerAttribute());
}
}
log文件的记录采用log4net,log4net是.Net下一个非常优秀的开源日志记录组件。log4net记录日志的功能非常强大。具体配置如下。
log4Net的配置文件名称为log4net.config,具体配置如下。
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<log4net>
<!--错误日志-->
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="log\\LogError\\"/>
<appendToFile value="true"/>
<rollingStyle value="Date"/>
<datePattern value="yyyy\\yyyyMM\\yyyyMMdd'.txt'"/>
<staticLogFileName value="false"/>
<param name="MaxSizeRollBackups" value="100"/>
<layout type="log4net.Layout.PatternLayout">
<!--每条日志末尾的文字说明-->
<!--输出格式-->
<!--样例:2008-03-26 13:42:32,111 [10] INFO Log4NetDemo.MainClass [(null)] - info-->
<conversionPattern value="%newline %n记录时间:%date %n线程ID:[%thread] %n日志级别: %-5level %n错误描述:%message%newline %n"/>
</layout>
</appender>
<!--Info日志-->
<appender name="InfoAppender" type="log4net.Appender.RollingFileAppender">
<param name="File" value="Log\\LogInfo\\" />
<param name="AppendToFile" value="true" />
<param name="MaxFileSize" value="10240" />
<param name="MaxSizeRollBackups" value="100" />
<param name="StaticLogFileName" value="false" />
<param name="DatePattern" value="yyyy\\yyyyMM\\yyyyMMdd'.txt'" />
<param name="RollingStyle" value="Date" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%newline %n记录时间:%date %n线程ID:[%thread] %n日志级别: %-5level %n日志描述:%message%newline %n"/>
</layout>
</appender>
<!--监控日志-->
<appender name="MonitorAppender" type="log4net.Appender.RollingFileAppender">
<param name="File" value="Log\\LogMonitor\\" />
<param name="AppendToFile" value="true" />
<param name="MaxFileSize" value="10240" />
<param name="MaxSizeRollBackups" value="100" />
<param name="StaticLogFileName" value="false" />
<param name="DatePattern" value="yyyy\\yyyyMM\\yyyyMMdd'.txt'" />
<param name="RollingStyle" value="Date" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%newline %n记录时间:%date %n线程ID:[%thread] %n日志级别: %-5level %n跟踪描述:%message%newline %n"/>
</layout>
</appender>
<!--Error日志-->
<logger name="logerror">
<level value="ERROR" />
<appender-ref ref="RollingLogFileAppender" />
</logger>
<!--Info日志-->
<logger name="loginfo">
<level value="INFO" />
<appender-ref ref="InfoAppender" />
</logger>
<!--监控日志-->
<logger name="logmonitor">
<level value="Monitor" />
<appender-ref ref="MonitorAppender" />
</logger>
</log4net>
</configuration>
在Global.asax中注册log4net配置文件,代码如下
protected void Application_Start()
{
//注册 log4net
log4net.Config.XmlConfigurator.Configure(
new System.IO.FileInfo(AppDomain.CurrentDomain.BaseDirectory + "\\Config\\log4net.config")
);
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Monitor.Models.ActionFilters
{
public class LoggerHelper
{
static readonly log4net.ILog loginfo = log4net.LogManager.GetLogger("loginfo");
static readonly log4net.ILog logerror = log4net.LogManager.GetLogger("logerror");
static readonly log4net.ILog logmonitor = log4net.LogManager.GetLogger("logmonitor");
public static void Error(string ErrorMsg, Exception ex = null)
{
if (ex != null)
{
logerror.Error(ErrorMsg, ex);
}
else
{
logerror.Error(ErrorMsg);
}
}
public static void Info(string Msg)
{
loginfo.Info(Msg);
}
public static void Monitor(string Msg)
{
logmonitor.Info(Msg);
}
}
}
目录结构我们区分开了错误日志、Info日志、监控日志,并且会按照日期生成日志,方便我们查看。