Adhesive框架系列文章--应用程序信息中心模块使用实践
应用程序信息中心模块提供了日志、异常、性能、状态四大服务。来看一下最新的日志服务定义:
void Debug(string message); void Debug(string moduleName, string message); void Debug(string categoryName, string subcategoryName, string message); void Debug(string moduleName, string categoryName, string subcategoryName, string message); void Debug(string message, ExtraInfo extraInfo); void Debug(string moduleName, string message, ExtraInfo extraInfo); void Debug(string categoryName, string subcategoryName, string message, ExtraInfo extraInfo); void Debug(string moduleName, string categoryName, string subcategoryName, string message, ExtraInfo extraInfo);
和之前版本的Adhesive相比,最新的日志服务增加了模块名,我们来看一下这些重载的应用范围:
1、对于基础模块,推荐定义模块名、大类和小类。这三个参数是级联的:
[MongodbPresentationItem(MongodbCascadeFilterOption = MongodbCascadeFilterOption.LevelOne, DisplayName = "模块名", ShowInTableView = true)] [MongodbPersistenceItem(MongodbIndexOption = MongodbIndexOption.Ascending, ColumnName = "MDN")] public string ModuleName { get; set; } [MongodbPresentationItem(MongodbCascadeFilterOption = MongodbCascadeFilterOption.LevelTwo, DisplayName = "大类", ShowInTableView = true)] [MongodbPersistenceItem(MongodbIndexOption = MongodbIndexOption.Ascending, ColumnName = "C")] public string CategoryName { get; set; } [MongodbPresentationItem(MongodbCascadeFilterOption = MongodbCascadeFilterOption.LevelThree, DisplayName = "小类", ShowInTableView = true)] [MongodbPersistenceItem(MongodbIndexOption = MongodbIndexOption.Ascending, ColumnName = "SC")]
如果没有定义模块名,那么使用的是“主模块”,也就表明这个日志是主程序的日志,而不是其中某一个模块的日志。
在框架内部的所有模块的日志都定义了模块名,这样我们很容易区分日志的来源,比如:
AppInfoCenterService.LoggingService.Warning(MongodbServiceConfiguration.ModuleName, "InternalGetMongodbData", typeFullName, string.Format("遇到重复的列,类型:{0},列名:{1}", typeFullName, columnName));
这是我们的Mongodb数据服务记录的日志,我们除了定义模块名,还使用方法名来作为大类,使用数据的类型名作为小类。以便我们搜索日志。
对于非基础的模块,无需指定模块名,至于大类小类可以指定,比如可以把大类设置为页面名,把小类设置为方法名。当然,对于主模块,大类默认就是页面名或是类名。
2、请善于利用ExtraInfo,我们知道日志的消息是不允许搜索的,如果需要搜索请通过ExtraInfo,比如:
AppInfoCenterService.LoggingService.Info(AlarmConfiguration.ModuleName, "AlarmService", "InternalHandleAlarm", string.Format("{0} {1} <= {2} 没达到邮件发送条件", logMessgae, mailPastTime.ToString(), group.MailMessageIntervalTimeSpan.ToString()), new ExtraInfo { DropDownListFilterItem1 = "没达到邮件发送条件", DropDownListFilterItem2 = configItem.ConfigName, });
在这里是一条报警服务的日志,我们把ExtraInfo中的两个下拉框设置都设置了值,这样我们就可以搜索了:
同样,异常服务的接口中也允许我们指定模块名、大类、小类以及ExtraInfo:
void Handle(Exception exception); void Handle(string moduleName, Exception exception); void Handle(string categoryName, string subcategoryName, Exception exception); void Handle(string moduleName, string categoryName, string subcategoryName, Exception exception); void Handle(Exception exception, string description); void Handle(string moduleName, Exception exception, string description); void Handle(string categoryName, string subcategoryName, Exception exception, string description); void Handle(string moduleName, string categoryName, string subcategoryName, Exception exception, string description); void Handle(Exception exception, ExtraInfo extraInfo); void Handle(string moduleName, Exception exception, ExtraInfo extraInfo); void Handle(string categoryName, string subcategoryName, Exception exception, ExtraInfo extraInfo); void Handle(string moduleName, string categoryName, string subcategoryName, Exception exception, ExtraInfo extraInfo); void Handle(Exception exception, string description, ExtraInfo extraInfo); void Handle(string moduleName, Exception exception, string description, ExtraInfo extraInfo); void Handle(string categoryName, string subcategoryName, Exception exception, string description, ExtraInfo extraInfo); void Handle(string moduleName, string categoryName, string subcategoryName, Exception exception, string description, ExtraInfo extraInfo);
在框架内部的各个模块中我们捕获的异常也遵循这个原则,就是指定模块名,而不是使用主模块:
ex.Handle(MongodbServerConfiguration.ModuleName, "MongodbServerMaintainceCenter", "列", "创建索引出错", new ExtraInfo { DisplayItems = new Dictionary<string, string> { {"数据库名", databaseName }, {"表名", collectionName }, {"列名" , indexColumnDescription.ColumnName } } });
这里,还注意到description这个参数,我们往往可以把它设置为对异常的描述,在这里可以把一些参数信息写在描述中,也可以使用ExtraInfo的DisplayItems。在上面那个异常中我们使用了ExtraInfo,因为KeyValue更清晰,再来看看另外一个例子:
ex.Handle(AppInfoCenterService.ModuleName, ServiceName, "Report", string.Format("调用汇报状态回调方法出错,类型:{0}", configuration.TypeFullName));
异常的描述是开发人员自己提供的,往往比原始的Message更容易看出问题,比如NullReferenceException的异常消息往往对我们没用,只有靠我们自己:
第三个服务是状态服务,在使用上需要注意的是,由于状态服务其实是一个后台线程,请以静态方式全局为一种状态定义一个状态服务,比如:
public class Global : System.Web.HttpApplication { private static IStateService testStateService; public static int stateValue = 100; protected void Application_Start(object sender, EventArgs e) { Adhesive.Common.AdhesiveFramework.Start(); testStateService = AppInfoCenterService.StateService; testStateService.Init(new StateServiceConfiguration("Adhesive.Test.WebApp.TestState", () => { return new List<BaseInfo> { new TestState { StateValue = stateValue, } }; })); } ...
光是代码方式定义了状态服务还不够,还需要在配置服务中根据类型完整名进行相应的定义:
要注意两点:
1、类型完整名要和代码中写的保持一致
2、状态服务属于信息中心模块,信息中心模块是私有的不是全局的配置,请看图中的路径
第四个服务是性能计算服务,使用方式如下:
protected void Performance_Click(object sender, EventArgs e) { AppInfoCenterService.PerformanceService.StartPerformanceMeasure("性能测试1"); for (int i = 0; i < 5; i++) { MethodA(); MethodB(); } } private void MethodA() { Thread.Sleep(int.Parse(SleepTime.Text)); AppInfoCenterService.PerformanceService.SetPerformanceMeasurePoint("性能测试1", "MethodA"); } private void MethodB() { for (int i = 0; i < int.Parse(CpuTime.Text); i++) { var d = 10000000; Math.Cos(d); Math.Sin(d); Math.Log10(d); Math.Sqrt(d); } AppInfoCenterService.PerformanceService.SetPerformanceMeasurePoint("性能测试1", "MethodB"); }
这里需要注意几个问题:
1、StartPerformanceMeasure应该在要统计的代码一开始写
2、SetPerformanceMeasurePoint应该在要统计的方法的最后写
3、对于同一个性能统计,SetPerformanceMeasurePoint和StartPerformanceMeasure中的性能测试名需要保持一致
总之一定,Adhesive的信息中心模块只是提供了一个信息收集的渠道,如果希望自己的程序可以尽快排查定位问题,那么需要遵循几个实践原则:
1、不吞吃所有异常,都调用Handle方法来处理异常,Handle不意味万能,在必要的时候throw出异常
2、合理利用各种日志服务来记录各种日志,合理区分各种LogLevel,一旦日志过多,我们可以根据LogLevel来分别开关,比如:
在这里我们通过定义一个新的日志策略,来关闭报警服务模块Debug日志的远程记录功能
并且,我们也可以调整日志记录的详细程序,比如我们希望报警模块的Info级别的日志包含信息策略为None,而不是主模块默认的Simple:
条件中限制了LogLevel是Info而ModuleName是报警服务模块:
3、尽量通过模块、分类来区分各种日志和异常
4、尽量在日志的消息、异常的描述中体现多一点信息,而不是简单的一些“出错”字样,但要注意方法的重载,不要把分类写到描述,把描述写到分类
5、合理利用ExtraInfo来提供数据的筛选功能,或提交参数信息
6、针对信息中心模块自己记录的网站未处理异常、应用程序域未处理异常设置基于统计的报警策略
7、可以针对信息中心模块自己记录的网站请求状态等状态设置基于状态的报警策略
8、合理利用信息中心模块的代码性能统计服务来排查性能问题
9、对于需要索引的项,比如模块名、大类、小类,以及ExtraInfo中的一些过滤字段,在数据库中是做索引的,因此这些字段应该是枚举数量控制在一定范围,比如100之内的,如果它们的变化范围达到几千甚至几万,那么就不适合了