应用程序信息中心模块项目的源代码结构如下:
在这里可以看到:
1、接口项目主要都是服务的接口定义、基于接口的扩展方法以及接口中需要用到的一些实体的定义。
2、实现项目主要由几部分构成:
1)模块的配置
2)异常服务
3)自动附加信息的各种提供程序
4)日志服务
5)性能服务
6)状态服务
有关配置部分,没什么可以介绍的,全部依赖于配置服务,直接传递一个自定义类对象作为默认的配置,配置服务会初始化所有的配置节点和默认值:
var config = configService.GetConfigItemValue(false, "AppInfoCenterConfiguration", defaultConfig); return config;
而配置服务的实例是通过LocalServiceLocator获取的:
private static IConfigService configService = LocalServiceLocator.GetService<IConfigService>();
之前也说过了,正式由于这种接口和实现的分离,在这里我们只需要引用外部依赖组件的接口项目:
在这里可以看到依赖了两个外部服务:使用配置服务来配置,使用Monogbd数据服务来存储数据。但是需要注意的是,对于需要启动的Host项目是需要引用Imp实现项目的,否则Unity无法找到接口的实现。
再来看看异常服务,在这里我们定义了所有异常的基类:
public abstract class ExceptionInfo : AbstractInfo { [MongodbPersistenceItem(MongodbIndexOption = MongodbIndexOption.Ascending, ColumnName = "ExType")] [MongodbPresentationItem(MongodbFilterOption = MongodbFilterOption.CheckBoxListFilter, DisplayName = "异常类型名", ShowInTableView = true)] public string ExceptionTypeName { get; set; } [MongodbPresentationItem(DisplayName = "异常描述", ShowInTableView = true)] [MongodbPersistenceItem(ColumnName = "Desc")] public string Description { get; set; } [MongodbPresentationItem(DisplayName = "异常")] [MongodbPersistenceItem(ColumnName = "Ex")] public AppException Exception { get; set; } [MongodbPresentationItem(DisplayName = "异常消息", ShowInTableView = true)] [MongodbPersistenceItem(ColumnName = "ExMsg")] public string ExceptionMessage { get; set; } }
在这里可以忽略有关Mongodb元数据的一些定义,我们关心的是对于所有异常,我们都需要记录异常类型名、异常的描述、异常主体(重新定义一个自定义类型为了序列化的需要)以及异常的消息。可以看到ExceptionInfo又继承了AbstractInfo,来看看AbstractInfo的结构:
public abstract class AbstractInfo : BaseInfo { [MongodbPresentationItem(MongodbCascadeFilterOption = MongodbCascadeFilterOption.LevelOne, DisplayName = "大类", ShowInTableView = true)] [MongodbPersistenceItem(MongodbIndexOption = MongodbIndexOption.Ascending, ColumnName = "Cat")] public string CategoryName { get; set; } [MongodbPresentationItem(MongodbCascadeFilterOption = MongodbCascadeFilterOption.LevelTwo, DisplayName = "小类", ShowInTableView = true)] [MongodbPersistenceItem(MongodbIndexOption = MongodbIndexOption.Ascending, ColumnName = "SubCat")] public string SubCategoryName { get; set; } [MongodbPresentationItem(DisplayName = "上下文标识", ShowInTableView = true)] [MongodbPersistenceItem(MongodbIndexOption = MongodbIndexOption.Ascending, IsContextIdentityColumn = true, ColumnName = "CtxId")] public string ContextIdentity { get; set; } [MongodbPresentationItem(DisplayName = "环境信息")] [MongodbPersistenceItem(ColumnName = "Env")] public EnvironmentInfo EnvironmentInfo { get; set; } [MongodbPresentationItem(DisplayName = "额外信息")] [MongodbPersistenceItem(ColumnName = "Ext")] public ExtraInfo ExtraInfo { get; set; } [MongodbPresentationItem(DisplayName = "位置信息")] [MongodbPersistenceItem(ColumnName = "Loc")] public LocationInfo LocationInfo { get; set; } [MongodbPresentationItem(DisplayName = "Http上下文信息")] [MongodbPersistenceItem(ColumnName = "HttpCtx")] public HttpContextInfo HttpContextInfo { get; set; } [MongodbPresentationItem(DisplayName = "Mvc上下文信息")] [MongodbPersistenceItem(ColumnName = "MvcCtx")] public MvcContextInfo MvcContextInfo { get; set; } }
其实AbstractInfo不但是ExceptionInfo使用,后面可以看到LogInfo日志信息等也是基于这个基类,其中的信息包括大类、小类、上下文标识、环境信息、额外信息、位置信息、Http上下文信息、Mvc上下文信息等。可以对比这里的数据结构和前文介绍的API,可以发现有很多内容都不是API的方法参数,说明这些信息是系统自己获取的,这些信息往往有助于发现问题定位问题。AbstractInfo又是继承BaseInfo的:
public abstract class BaseInfo { [MongodbPersistenceItem(MongodbIndexOption = MongodbIndexOption.AscendingAndUnique, IsPrimaryKey = true)] [MongodbPresentationItem(ShowInTableView = true, DisplayName = "主键")] public string ID { get; set; } [MongodbPersistenceItem(IsTableName = true)] [MongodbPresentationItem(DisplayName = "应用程序名", ShowInTableView = true)] public string AppName { get; set; } [MongodbPersistenceItem(MongodbIndexOption = MongodbIndexOption.Descending, IsTimeColumn = true, ColumnName = "T")] [MongodbPresentationItem(MongodbSortOption = MongodbSortOption.Descending, DisplayName = "时间", ShowInTableView = true)] public DateTime ServerTime { get; set; } [MongodbPersistenceItem(MongodbIndexOption = MongodbIndexOption.Ascending, ColumnName = "IP")] [MongodbPresentationItem(MongodbFilterOption = MongodbFilterOption.DropDownListFilter, DisplayName = "服务器IP", ShowInTableView = true)] public string MachineIP { get; set; } public BaseInfo() { ID = Guid.NewGuid().ToString(); AppName = CommonConfiguration.GetConfig().ApplicationName; MachineIP = CommonConfiguration.MachineIP; ServerTime = DateTime.Now; } }
在第一个图中可以看到,BaseInfo是放在接口项目中的,其原因就是状态服务回调方法返回的类型是BaseInfo,之所以这么做是因为状态服务提供的每一条状态信息都至少应该有应用程序名、机器名、时间等信息,但是对于状态服务的数据来说,如果继承AbstractInfo又太重了一点,毕竟状态数据很可能是一秒一条的。现在再回到ExceptionInfo,我们来看看有多少类实现了这个抽象类,首先是已处理异常:
[MongodbPersistenceEntity("Aic", DisplayName = "已处理异常", Name = "Exception")] public class HandledExceptionInfo : ExceptionInfo { }
然后是网站未处理异常:
[MongodbPersistenceEntity("Aic", DisplayName = "网站未处理异常", Name = "WebsiteException")] public class WebSiteUnhandledExceptionInfo : ExceptionInfo { }
没什么特别的信息,然后是应用程序域的未处理异常:
[MongodbPersistenceEntity("Aic", DisplayName = "应用程序域未处理异常", Name = "AppDomainException")] public class AppDomainUnhandledExceptionInfo : ExceptionInfo { public bool IsTerminating { get; set; } }
增加了一个是否终止应用程序的信息,然后是mvc的未处理异常:
[MongodbPersistenceEntity("Aic", DisplayName = "Mvc未处理异常", Name = "MvcException")] public class MvcUnhandledExceptionInfo : WebSiteUnhandledExceptionInfo { [MongodbPersistenceItem(MongodbIndexOption = MongodbIndexOption.Ascending, ColumnName = "CType")] [MongodbPresentationItem(MongodbFilterOption = MongodbFilterOption.DropDownListFilter, DisplayName = "控制器类型名", ShowInTableView = true)] public string ControllerTypeName { get; set; } }
相当于网站未处理异常增加了控制器类型的名字,这会比页面URL更直观,然后是WCF的客户端和服务端异常:
[MongodbPersistenceEntity("Wcf", DisplayName = "客户端异常日志", Name = "ClientException")] public class WcfUnhandledClientExceptionInfo : ExceptionInfo, IWcfClientInfo { [MongodbPersistenceItem(MongodbIndexOption = MongodbIndexOption.Ascending)] [MongodbPresentationItem(MongodbFilterOption = MongodbFilterOption.DropDownListFilter, DisplayName = "契约名", ShowInTableView = true)] public string ContractName { get; set; } [MongodbPresentationItem(DisplayName = "服务端异常Id")] public string ServerExceptionID { get; set; } }
[MongodbPersistenceEntity("Wcf", DisplayName = "服务端异常日志", Name = "ServerException")] public class WcfUnhandledServerExceptionInfo : ExceptionInfo, IWcfServerInfo { [MongodbPersistenceItem(MongodbIndexOption = MongodbIndexOption.Ascending)] [MongodbPresentationItem(MongodbFilterOption = MongodbFilterOption.DropDownListFilter, DisplayName = "服务名", ShowInTableView = true)] public string ServiceName { get; set; } }
对于客户端异常来说,我们希望知道契约名以及对应的服务端异常ID,对于服务端异常来说,我们希望知道出错服务的名字。总而言之,每一种异常信息自己特有的数据都在派生类型中提供。我们的异常服务除了实现异常服务的接口之外还继承了BaseService:
public class ExceptionService : BaseService, IExceptionService
这么做的目的是提取出所有服务都需要的一些公共部分,比如附加额外的各种信息:
protected static List<IInfoProvider> infoProviderList = new List<IInfoProvider>(); static BaseService() { infoProviderList.Add(new LocationInfoProvider()); infoProviderList.Add(new EnvironmentInfoProvider()); infoProviderList.Add(new HttpContextInfoProvider()); infoProviderList.Add(new MvcContextInfoProvider()); infoProviderList.Add(new AbstractInfoProvider()); } protected static void ProcessInfo(AbstractInfo info) { var strategy = GetIncludeInfoStrategy(info); infoProviderList.Each(provider => { try { provider.ProcessInfo(strategy, info); } catch (Exception ex) { LocalLoggingService.Error("AdhesiveFramework.BaseService.ProcessInfo 执行 '{0}' 出错,异常信息:{1}!", provider.GetType().Name, ex.ToString()); } }); }
在BaseService里面我们添加了各种信息的提供程序,然后按次序调用ProcessInfo方法来为AbstractInfo附加上各种信息,当然,事先需要获得包含信息的策略。之前也提到过了,GetIncludeInfoStrategy是非常动态的,首先会根据类型名来获得包含信息的策略,如果有条件的话还会按照条件进行匹配。再来看看异常服务中的核心方法的片段:
if (localOnly) { LocalLog(exception, extraInfo, categoryName, subcategoryName, description); return; } if (AppInfoCenterConfiguration.GetConfig().ExceptionServiceConfig.Enabled) { var strategy = GetExceptionStrategy(info.GetType().Name, exception.GetType().Name); if (strategy != null) { if (strategy.LocalLog) LocalLog(exception, extraInfo, categoryName, subcategoryName, description); if (strategy.RemoteLog) { info.Exception = MapException(exception); info.Description = description; info.ExceptionTypeName = exception.GetType().FullName; info.ExtraInfo = extraInfo; info.CategoryName = categoryName; info.SubCategoryName = subcategoryName; info.ExceptionMessage = exception.Message; ProcessInfo(info); MongodbService.MongodbInsertService.Insert(info); } if (info is WebSiteUnhandledExceptionInfo && HttpContext.Current != null) { HttpContext.Current.Response.Clear(); HttpContext.Current.Response.StatusCode = strategy.ResponseStatusCode; if (!HttpContext.Current.Request.IsLocal) { if (!string.IsNullOrWhiteSpace(strategy.RedirectUrl)) { HttpContext.Current.Response.Redirect(string.Format("{0}/?ID={1}", strategy.RedirectUrl.TrimEnd('/'), info.ID), false); } else { HttpContext.Current.Response.Write(string.Format(AppInfoCenterConfiguration.GetConfig().ExceptionServiceConfig.UnhandledExceptionMessage, info.ID)); HttpContext.Current.Response.End(); } } } } }
在处理异常的时候,首先判断是否只是需要记录本地,如果是的话调用本地日志服务。否则,先获取异常策略,然后根据策略决定记录本地还是发送到远端。正是由于使用了Mongodb数据服务,这里数据记录的方法非常简单,只是一句Insert方法调用即可。在这之前无非是先进行自定义异常的转换,然后进行AbstractInfo的组装。注意到在方法的最后,我们判断如果这是网站未处理异常的话,需要处理跳转或输出错误语句的逻辑。
其实介绍了异常服务,日志服务就不用介绍了,其逻辑一模一样,无非是获取配置(比如包含信息策略、日志策略),然后是通过BaseService组装数据,然后是通过Mongodb数据服务提交数据。看看LogInfo的数据结构:
[MongodbPersistenceEntity("Aic", DisplayName = "日志", Name = "Log")] public class LogInfo : AbstractInfo { [MongodbPresentationItem(DisplayName = "日志消息", ShowInTableView = true)] [MongodbPersistenceItem(ColumnName = "Msg")] public string Message { get; set; } [MongodbPersistenceItem(MongodbIndexOption = MongodbIndexOption.Ascending, ColumnName = "Lev")] [MongodbPresentationItem(MongodbFilterOption = MongodbFilterOption.DropDownListFilter, DisplayName = "日志级别", ShowInTableView = true)] public LogLevel LogLevel { get; set; } }
我们现在就来看看AbstractInfo中有哪些附加数据:
每一种数据都有配套的提供程序用于获得数据,首先是环境信息:
public class EnvironmentInfo { [MongodbPresentationItem(DisplayName = "进程名")] public string ProcessName { get; set; } [MongodbPresentationItem(DisplayName = "进程Id")] public int ProcessId { get; set; } [MongodbPresentationItem(DisplayName = "当前目录")] public string CurrentDirectory { get; set; } [MongodbPresentationItem(DisplayName = "是否是64位操作系统")] public bool Is64BitOperatingSystem { get; set; } [MongodbPresentationItem(DisplayName = "是否是64位进程")] public bool Is64BitProcess { get; set; } [MongodbPresentationItem(DisplayName = "机器名")] public string MachineName { get; set; } [MongodbPresentationItem(DisplayName = "操作系统")] public string OperatingSystem { get; set; } [MongodbPresentationItem(DisplayName = "当前用户的域")] public string UserDomainName { get; set; } [MongodbPresentationItem(DisplayName = "是否可交互")] public bool UserInteractive { get; set; } [MongodbPresentationItem(DisplayName = "当前用户名")] public string UserName { get; set; } [MongodbPresentationItem(DisplayName = "CLR版本")] public string Version { get; set; } }
环境信息应该是比较固定的信息,想表明应用程序所处的运行环境。然后是Http上下文信息:
public class HttpContextInfo { [MongodbPresentationItem(DisplayName = "处理程序名字")] public string Handler { get; set; } [MongodbPresentationItem(DisplayName = "请求上下文信息")] public Dictionary<string, string> Items { get; set; } [MongodbPresentationItem(DisplayName = "请求会话信息")] public Dictionary<string, string> Sessions { get; set; } [MongodbPresentationItem(DisplayName = "是否开启自定义错误")] public bool IsCustomErrorEnabled { get; set; } [MongodbPresentationItem(DisplayName = "是否开启Debug")] public bool IsDebuggingEnabled { get; set; } [MongodbPresentationItem(DisplayName = "是否跳过验证")] public bool SkipAuthorization { get; set; } [MongodbPresentationItem(DisplayName = "响应信息")] public ResponseInfo ResponseInfo { get; set; } [MongodbPresentationItem(DisplayName = "请求信息")] public RequestInfo RequestInfo { get; set; } }
很明显只有网站才有Http上下文信息,其中包括了请求和响应信息的子类:
public class ResponseInfo { [MongodbPresentationItem(DisplayName = "网页的缓存策略")] public string CacheControl { get; set; } [MongodbPresentationItem(DisplayName = "输出流的 HTTP 字符集")] public string Charset { get; set; } [MongodbPresentationItem(DisplayName = "输出流的 HTTP 字符集")] public string ContentEncoding { get; set; } [MongodbPresentationItem(DisplayName = "输出流的 HTTP MIME 类型")] public string ContentType { get; set; } [MongodbPresentationItem(DisplayName = "响应 Cookie 集合")] public Dictionary<string, string> Cookies { get; set; } [MongodbPresentationItem(DisplayName = "响应 Header 集合")] public Dictionary<string, string> Headers { get; set; } [MongodbPresentationItem(DisplayName = "从缓存中移除缓存信息的绝对日期和时间")] public DateTime ExpiresAbsolute { get; set; } [MongodbPresentationItem(DisplayName = "当前标头输出流的编码")] public string HeaderEncoding { get; set; } [MongodbPresentationItem(DisplayName = "客户端是否仍连接在服务器上")] public bool IsClientConnected { get; set; } [MongodbPresentationItem(DisplayName = "客户端是否正在被传输到新的位置")] public bool IsRequestBeingRedirected { get; set; } [MongodbPresentationItem(DisplayName = "Http“位置”标头的值")] public string RedirectLocation { get; set; } [MongodbPresentationItem(DisplayName = "返回到客户端的 Status ")] public string Status { get; set; } [MongodbPresentationItem(DisplayName = "返回给客户端的输出的 HTTP 状态代码")] public int StatusCode { get; set; } [MongodbPresentationItem(DisplayName = "返回给客户端的输出的 HTTP 状态字符串")] public string StatusDescription { get; set; } [MongodbPresentationItem(DisplayName = "是否将 HTTP 内容发送到客户端")] public bool SuppressContent { get; set; } [MongodbPresentationItem(DisplayName = "是否禁用 IIS 7.0 自定义错误")] public bool TrySkipIisCustomErrors { get; set; } }
public class RequestInfo { [MongodbPresentationItem(DisplayName = "客户端支持的 MIME 接受类型的字符串数组")] public string AcceptTypes { get; set; } [MongodbPresentationItem(DisplayName = "该用户的匿名标识符")] public string AnonymousID { get; set; } [MongodbPresentationItem(DisplayName = "服务器上 ASP.NET 应用程序的虚拟应用程序根路径")] public string ApplicationPath { get; set; } [MongodbPresentationItem(DisplayName = "应用程序根的虚拟路径")] public string AppRelativeCurrentExecutionFilePath { get; set; } [MongodbPresentationItem(DisplayName = "有关正在请求的客户端的浏览器功能的信息")] public string Browser { get; set; } [MongodbPresentationItem(DisplayName = "实体主体的字符集")] public string ContentEncoding { get; set; } [MongodbPresentationItem(DisplayName = "客户端发送的内容长度")] public int ContentLength { get; set; } [MongodbPresentationItem(DisplayName = "传入请求的 MIME 内容类型")] public string ContentType { get; set; } [MongodbPresentationItem(DisplayName = "客户端发送的 cookie 的集合")] public Dictionary<string, string> Cookies { get; set; } [MongodbPresentationItem(DisplayName = "当前请求的虚拟路径")] public string CurrentExecutionFilePath { get; set; } [MongodbPresentationItem(DisplayName = "当前请求的虚拟路径")] public string FilePath { get; set; } [MongodbPresentationItem(DisplayName = "窗体变量集合")] public Dictionary<string, string> Forms { get; set; } [MongodbPresentationItem(DisplayName = "HTTP 头集合")] public Dictionary<string, string> Headers { get; set; } [MongodbPresentationItem(DisplayName = "客户端使用的 HTTP 数据传输方法")] public string HttpMethod { get; set; } [MongodbPresentationItem(DisplayName = "是否验证了请求")] public bool IsAuthenticated { get; set; } [MongodbPresentationItem(DisplayName = "该请求是否来自本地计算机")] public bool IsLocal { get; set; } [MongodbPresentationItem(DisplayName = "HTTP 连接是否使用安全套接字")] public bool IsSecureConnection { get; set; } [MongodbPresentationItem(DisplayName = "当前用户的 WindowsIdentity 类型")] public string LogonUserIdentity { get; set; } [MongodbPresentationItem(DisplayName = "当前请求的虚拟路径")] public string Path { get; set; } [MongodbPresentationItem(DisplayName = "具有 URL 扩展名的资源的附加路径信息")] public string PathInfo { get; set; } [MongodbPresentationItem(DisplayName = "当前正在执行的服务器应用程序的根目录的物理文件系统路径")] public string PhysicalApplicationPath { get; set; } [MongodbPresentationItem(DisplayName = "与请求的 URL 相对应的物理文件系统路径")] public string PhysicalPath { get; set; } [MongodbPresentationItem(DisplayName = "HTTP 查询字符串变量集合")] public Dictionary<string, string> QueryStrings { get; set; } [MongodbPresentationItem(DisplayName = "客户端使用的 HTTP 数据传输方法")] public string RequestType { get; set; } [MongodbPresentationItem(DisplayName = "当前输入流中的字节数")] public int TotalBytes { get; set; } [MongodbPresentationItem(DisplayName = "有关当前请求的 URL 的信息")] public string Url { get; set; } [MongodbPresentationItem(DisplayName = "有关客户端上次请求的 URL 的信息")] public string UrlReferrer { get; set; } [MongodbPresentationItem(DisplayName = "客户端浏览器的原始用户代理信息")] public string UserAgent { get; set; } [MongodbPresentationItem(DisplayName = "远程客户端的 IP 主机地址")] public string UserHostAddress { get; set; } [MongodbPresentationItem(DisplayName = "远程客户端的 DNS 名称")] public string UserHostName { get; set; } [MongodbPresentationItem(DisplayName = "客户端语言首选项的排序字符串")] public string UserLanguages { get; set; } }
信息量还是非常丰富的,其实属性虽然多但是这些数据不会占用过多的数据,对于数据量可能会很多的数据在IncludeInfoStrategy中都可以配置独立的开关。然后是定位信息,所谓定位信息是有助于定位日志、异常发生的位置:
public class LocationInfo { [MongodbPresentationItem(DisplayName = "托管线程Id")] public int ManagedThreadId { get; set; } [MongodbPresentationItem(DisplayName = "应用程序域名")] public string AppDomainName { get; set; } [MongodbPresentationItem(DisplayName = "程序集名")] public string AssemblyName { get; set; } [MongodbPresentationItem(DisplayName = "模块名")] public string ModuleName { get; set; } [MongodbPresentationItem(DisplayName = "类型名")] public string TypeName { get; set; } [MongodbPresentationItem(DisplayName = "方法名")] public string MethodName { get; set; } [MongodbPresentationItem(DisplayName = "堆栈")] public string StackTrace { get; set; } }
一般对于异常来说都是有堆栈的,对于日志来说也可以通过包含信息策略的配置来包含堆栈。然后是MVC相关的信息:
public class MvcContextInfo { [MongodbPresentationItem(DisplayName = "路由信息")] public Dictionary<string, string> RouteData { get; set; } [MongodbPresentationItem(DisplayName = "临时数据")] public Dictionary<string, string> TempData { get; set; } [MongodbPresentationItem(DisplayName = "视图数据")] public Dictionary<string, string> ViewData { get; set; } [MongodbPresentationItem(DisplayName = "参数数据")] public Dictionary<string, string> ParameterData { get; set; } [MongodbPresentationItem(DisplayName = "输出类型")] public string ActionResultType { get; set; } [MongodbPresentationItem(DisplayName = "控制器名")] public string ControllerName { get; set; } [MongodbPresentationItem(DisplayName = "行为名")] public string ActionName { get; set; } [MongodbPresentationItem(DisplayName = "是否是子行为")] public bool IsChildAction { get; set; } [MongodbPresentationItem(DisplayName = "执行阶段")] public MvcActionStage State { get; set; } }
至于怎么提取这些信息的不想多少了,每一个类型都配有自己的Provider一清二楚。
下面介绍一下性能服务,在上文中说过性能服务包括两部分,第一是自动记录慢页面的执行,第二就是编程加点方式测量代码的性能。
对于慢页面的执行信息,没什么特殊的:
[MongodbPersistenceEntity("Aic", DisplayName = "网站页面执行性能", Name = "WebsiteSlowExec")] public class WebsitePageExecutionInfo : AbstractInfo { [MongodbPersistenceItem(MongodbIndexOption = MongodbIndexOption.Ascending)] [MongodbPresentationItem(MongodbFilterOption = MongodbFilterOption.DropDownListFilter, DisplayName = "页面执行时间分类")] public ExecutionTime ExecutionTime { get; set; } [MongodbPresentationItem(DisplayName = "页面执行毫秒", ShowInTableView = true)] public long PageExecutionTime { get; set; } }
只是记录了页面执行时间以及慢到什么程度的枚举,这样可以进行统计:
public enum ExecutionTime { Less_1, Between_1_2, Between_2_3, Between_3_5, Greater_5 } }
在这里对于加点方式测量性能想说两点:
1、获取CPU时间是通过kernel32.dll获取的:
[DllImport("kernel32.dll", SetLastError = true)] static extern bool GetThreadTimes(IntPtr hThread, out long lpCreationTime, out long lpExitTime, out long lpKernelTime, out long lpUserTime); [DllImport("kernel32.dll")] static extern IntPtr GetCurrentThread(); private long GetCurrentThreadTimes() { long l; long kernelTime, userTimer; GetThreadTimes(GetCurrentThread(), out l, out l, out kernelTime, out userTimer); return kernelTime + userTimer; }
2、我们把HttpContext.Current.Items作为一个临时数据的存放地,在请求结束的时候,从中获取数据并提交到服务端:
[MongodbPersistenceEntity("Aic", DisplayName = "代码性能测量", Name = "Performance")] public class PerformanceInfo : AbstractInfo { [MongodbPersistenceItem(IsIgnore = true)] public Stopwatch sw { get; set; } [MongodbPersistenceItem(IsIgnore = true)] public long threadTime { get; set; } [MongodbPersistenceItem(MongodbIndexOption = MongodbIndexOption.Ascending)] [MongodbPresentationItem(MongodbFilterOption = MongodbFilterOption.DropDownListFilter, DisplayName = "性能测试名", ShowInTableView = true)] public string Name { get; set; } [MongodbPresentationItem(DisplayName = "总CPU时间", ShowInTableView = true)] public long TotalCPUTime { get; set; } [MongodbPresentationItem(DisplayName = "总消耗时间", ShowInTableView = true)] public long TotalTimeElapsed { get; set; } [MongodbPresentationItem(DisplayName = "总测量点数", ShowInTableView = true)] public int TotalPerformancePointCount { get; set; } [MongodbPresentationItem(DisplayName = "所有测量点")] public Dictionary<string, PerformancePoint> PerformancePoints { get; set; } }
对于每一条数据我们都会记录性能测量的名字、总CPU时间、总消耗时间,然后会是详细的每一个测量点的细节数据:
public class PerformancePoint { [MongodbPresentationItem(DisplayName = "平均CPU时间")] public int AverageCPUTime { get; set; } [MongodbPresentationItem(DisplayName = "平均消耗时间")] public int AverageTimeElapsed { get; set; } [MongodbPresentationItem(DisplayName = "测量点迭代总数")] public int TotalPerformancePointItemCount { get; set; } [MongodbPresentationItem(DisplayName = "测量点的每一次迭代")] public List<PerformancePointItem> PerformancePointItems { get; set; } }
对于每一个测量点可能都会有多次迭代,因此详细记录了每一次迭代的具体信息:
public class PerformancePointItem { [MongodbPresentationItem(DisplayName = "时间")] public DateTime Time { get; set; } [MongodbPresentationItem(DisplayName = "一次迭代消耗CPU时间")] public long CPUTime { get; set; } [MongodbPresentationItem(DisplayName = "一次迭代消耗时间")] public long TimeElapsed { get; set; } }
最后说一下状态服务,其中也有两个功能,首先是应用程序的状态,然后是网站请求的执行状态。应用程序状态的数据结构如下:
[MongodbPersistenceEntity("State", DisplayName = "应用程序状态", Name = "Application")] public class ApplicationStateInfo : BaseInfo { [MongodbPresentationItem(DisplayName = "进程名")] public string ProcessName { get; set; } [MongodbPresentationItem(DisplayName = "工作集内存")] public long WorkingSet64 { get; set; } [MongodbPresentationItem(DisplayName = "非分页系统内存")] public long NonpagedSystemMemorySize64 { get; set; } [MongodbPresentationItem(DisplayName = "分页内存")] public long PagedMemorySize64 { get; set; } [MongodbPresentationItem(DisplayName = "分页的系统内存")] public long PagedSystemMemorySize64 { get; set; } [MongodbPresentationItem(DisplayName = "私有内存")] public long PrivateMemorySize64 { get; set; } [MongodbPresentationItem(DisplayName = "虚拟内存")] public long VirtualMemorySize64 { get; set; } [MongodbPresentationItem(DisplayName = "工作线程")] public int CurrentWorkThreadCount { get; set; } [MongodbPresentationItem(DisplayName = "完成端口线程")] public int CurrentCompletionPortThreadCount { get; set; } }
在这里,只是给出一个数据结构,而没给出获取数据的方法,其实获取这些数据对于.NET来说都很简单的都是体力劳动,数据结构可以反映一个思路。网站请求状态的结构如下:
[MongodbPersistenceEntity("State", DisplayName = "网站请求状态", Name = "WebsiteRequest")] public class WebsiteRequestStateInfo : BaseInfo { public Dictionary<string, WebsiteRequestStateItem> WebsiteRequestStateItems { get; set; } }
其中包含了所有当前网站访问过请求的状态,对于每一个请求我们提供的数据有:
public class WebsiteRequestStateItem { [MongodbPresentationItem(DisplayName = "当前请求数量")] public long CurrentRequestCount { get; set; } [MongodbPresentationItem(DisplayName = "最大请求数量")] public long MaxRequestCount { get; set; } [MongodbPresentationItem(DisplayName = "最大请求数量发生在")] public DateTime MaxRequestCountOccur { get; set; } [MongodbPresentationItem(DisplayName = "总共请求数量")] public long TotalRequestCount { get; set; } [MongodbPresentationItem(DisplayName = "总共请求执行时间")] public long TotalRequestExecutionTime { get; set; } [MongodbPresentationItem(DisplayName = "平均请求执行时间")] public long AverageRequestExecutionTime { get; set; } [MongodbPresentationItem(DisplayName = "最大请求执行时间")] public long MaxRequestExecutionTime { get; set; } [MongodbPresentationItem(DisplayName = "最大请求执行发生在")] public DateTime MaxRequestExecutionTimeOccur { get; set; } [MongodbPresentationItem(DisplayName = "上一次请求执行时间")] public long LastRequestExecutionTime { get; set; } [MongodbPresentationItem(DisplayName = "上一次请求执行发生在")] public DateTime LastRequestExecutionTimeOccur { get; set; } [MongodbPresentationItem(DisplayName = "页面地址")] public string Url { get; set; } }
很明显,我们在程序内部需要维护一个字典,在每次页面请求完成的时候去更新URL找到数据条目并且更新。这些数据其实还是挺有用的,我们可以知道整个网站中哪些地址访问量最大,哪些地址最慢等等。
最后,我们来看一下InitServiceTask,我们的信息中心服务是如何初始化的:
public class InitServiceTask : InitServiceBootstrapperTask { private static IStateService applicationStateService; private static IStateService activeRequestStateService; public override string Description { get { return "启动应用程序状态服务、启动网站请求状态服务、挂载应用程序未处理异常"; } } public InitServiceTask(IUnityContainer container) : base(container) { } public override TaskContinuation Execute() { try { AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); applicationStateService = AppInfoCenterService.StateService; applicationStateService.Init(new StateServiceConfiguration(typeof(ApplicationStateInfo).FullName, ApplicationStateService.GetState)); if (HttpContext.Current != null) { activeRequestStateService = AppInfoCenterService.StateService; activeRequestStateService.Init(new StateServiceConfiguration(typeof(WebsiteRequestStateInfo).FullName, ActiveRequestStateService.GetState)); } } catch (Exception ex) { LocalLoggingService.Error("初始化信息中心服务出错,异常信息:{0}", ex); return TaskContinuation.Break; } return TaskContinuation.Continue; } private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { try { ((ExceptionService)AppInfoCenterService.ExceptionService).AppDomainUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); } catch (Exception ex) { LocalLoggingService.Error("处理应用程序域未处理异常出错,异常信息:{0}", ex); } } }
在这里可以看到在启动的时候,我们挂载了应用程序域的未处理异常事件,用于处理未处理异常。然后初始化了应用程序状态服务,对于网站来说还需要额外初始化网站请求状态服务。至此,本文完整介绍了应用程序信息模块的实现细节,并列出了每一个数据结构的代码。拥有日志、异常、性能、状态四个模块,并且可以自动附加非常丰富的上下文数据,以及处理程序的未处理异常,不知道这样的一个信息中心是否名副其实呢。