使用策略注入应用程序块、Structure Map 和 log4net 进行自动日志记录






4.71/5 (13投票s)
使用最少的代码干扰记录项目中所有方法的入口和出口。
引言
本文将讨论策略注入应用程序块如何与控制反转容器(如 Structure Map)协同工作,使用第三方日志记录工具(如 log4net)自动记录项目中的所有方法。
只需调用 IoC.Resolve
而不是 ObjectFactory.GetInstance
,并在类上添加 [Tag(Tags.Log)]
标签,项目中的所有方法在进入和退出时都会自动记录日志,日志格式如下:
10/10/10 10:10:10AM - Entering IMyBusinessLogic.GetMyBusinessLogic()
Parameters: [[(Int64) id = '1234']]
10/10/10 10:10:11AM - Leaving IMyBusinessLogic.GetMyBusinessLogic()
Return Value: [(LoggingDemo.Models.MyBusinessObject)
Properties: [[(Int64) Id = '1234'], [(String) Name = 'My Name'],
[(DateTime) Date = '4/21/2010 3:17:28 PM']]]
自动日志记录系统的核心是策略注入应用程序块,它允许我们处理日志记录等横切关注点。通过将策略注入应用程序块与 Structure Map 结合使用,我们可以轻松地记录应用程序中所有方法的进入和退出。日志管理(如开启/关闭日志记录、限制详细程度等)则由 web.config 中的 log4net 配置部分处理。方法访问的详细日志记录对于在生产环境中调试问题非常有价值,因为你可以轻松地在 web.config 中切换一个开关,立即看到哪些方法被调用、何时被调用、调用的顺序以及它们在失败前执行到哪个程度。一旦问题重现,就可以关闭详细日志记录,恢复生产环境的性能。
背景
网上有一些关于如何将策略注入应用程序块与企业库的日志记录块一起使用的文章,但一些工作环境已经在使用 log4net(例如,与 NHibernate 一起使用),并且希望继续使用该日志记录工具,而不是管理两个日志记录工具。本文假设您已经了解如何使用 Structure Map 和 log4net(或者可以自行搜索以获取更多说明),因此这里将只简要讨论它们。
入门
您需要从 Microsoft 下载并安装 Enterprise Library 4.1,以便获得其 Enterprise Library Configuration 编辑器,方便修改 web.config。
本文包含一个快速的 MVC .NET 演示项目,其中包含这些开源/免费库的 DLL:
- Microsoft Enterprise Library 4.1
- log4net 1.2.10.0
- Structure Map 2.5.3.0
在您自己的项目中,请务必添加并引用这些 DLL:
- log4net.dll
- Microsoft.Practices.EnterpriseLibrary.Common.dll
- Microsoft.Practices.EnterpriseLibrary.Data.dll
- Microsoft.Practices.EnterpriseLibrary.PolicyInjection.dll
- Microsoft.Practices.ObjectBuilder2.dll
- Microsoft.Practices.Unity.dll
- Microsoft.Practices.Unity.Interception.dll
- StructureMap.AutoMocking.dll
- StructureMap.dll
此演示使用了一个名为 StructureMapControllerFactory
的自定义控制器工厂,该工厂可以从 Structure Map 中自动获取控制器实例。它在 global.asax 的 Application_Start
方法中被激活。
调用处理器 (Call Handler)
自动日志记录系统的核心是策略注入应用程序块的调用处理器及其在 web.config 中的配置设置。
首先,创建一个实现 ICallHandler
接口的新类。您将在演示源代码的 MethodLoggingCallHandler
类中看到一个示例。您需要为该类添加 [ConfigurationElementType(typeof (CustomCallHandlerData))]
装饰器,以便它在下一步的 Microsoft Enterprise Library Configuration 编辑器中显示。您必须创建一个接受 NameValueCollection
作为参数的构造函数重载,但您不必使用该参数。
接下来,您需要实现该接口所需的 Invoke
方法。这是整个系统中关键的方法,因为此方法会在项目中每个被标记为记录日志的方法上被调用。在 Invoke
方法中,我们实例化第三方日志记录工具(例如 log4net),然后编写方法进入日志,包括方法名和输入参数。
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
ILog log = LogManager.GetLogger("MyLog");
log.Debug("Entering " + input.MethodBase.DeclaringType.FullName +
"." + input.MethodBase.Name + "() Parameters: [" +
GetParameters(input.Inputs) + "]");
然后调用策略正在包装的实际方法。
IMethodReturn result = getNext()(input, getNext);
最后,我们编写方法退出日志,包括返回值。GetObjectPropertiesString
方法获取每个参数的类型、名称和值。对于原始类型,只显示类型和值。对于集合,只写入名称和类型,因为我们不希望记录可能包含数千个对象的集合中的每个对象。
配置策略注入应用程序块
使用 Microsoft Enterprise Library Configuration 编辑器,查看项目的 web.config(截图来自 Enterprise Library 4.1,5.0 及更高版本中的编辑器看起来不同)。
- 右键单击配置文件节点,并将新的策略注入块添加到 web.config。
- 向策略添加新策略。
- 向匹配规则添加新的标签属性匹配规则 (Tag Attribute Matching Rule)。
- 在匹配规则的属性中,将 Match 改为“Log”。
- 注意:仅使用标签属性匹配规则,因为 Enterprise Library 4.1 中存在一个已知 bug,即自定义属性匹配规则会调用两次 Invoke 方法。
- 向处理程序添加新的自定义处理程序。
- 在自定义处理程序的属性中,将 Type 改为您的
MethodLoggingCallHandler
。- 如果列表中没有显示,请按“从文件加载”按钮,加载项目 DLL(从 bin 文件夹),然后您将在列表中看到您的调用处理器。
使用策略注入器包装类实例
所有将实现自动日志记录系统的类都需要被策略注入器包装。这可以通过一个名为 IoC
的 static
类轻松完成。该类接受一个类型参数,使用我们的控制反转容器(例如 Structure Map)获取一个实例,然后返回该实例(已由策略注入器包装)。在实践中,您只需使用 IoC.Resolve
而不是 ObjectFactory.GetInstance
。感谢 David Hayden(来自 codebetter.com)提供的这个简单解决方案。
public static T Resolve<T>()
{
// get from StructureMap
T instance = ObjectFactory.GetInstance<T>();
// wrap using Policy Injection Application Block
return PolicyInjection.Wrap<T>(instance);
}
由于我们也想包装现有实例(例如,在类构造函数中进行依赖注入以及记录类中子方法的调用),我们需要一个接受实例并返回包装实例的重载。
public static T Resolve<T>(T instance)
{
// wrap using Policy Injection Application Block
return PolicyInjection.Wrap<T>(instance);
}
现在,您想被记录日志的所有类实例化都调用 IoC.Resolve
,正如您在 HomeController
的 Index
操作中所见。
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
IMyBusinessLogic demo = IoC.Resolve<IMyBusinessLogic>();
demo.GetMyBusinessLogic(1234);
return View();
}
一个不直观的陷阱
策略注入应用程序块有一个不太直观的功能,我未能完全解决:策略注入应用程序块不会递归记录方法调用。因此,如果您的方法调用另一个方法(即使是同一类中的 public
方法),只有父方法会被记录。为了记录子方法,您不应该直接调用内部方法,而是必须使用一个被 IoC.Resolve
包装过的类实例,并通过包装实例调用您的方法。例如,如果 GetMyBusinessLogic
方法调用了 GetMyOtherBusinessLogic
,并且我们想记录这两个方法,我们就需要创建一个 IMyBusinessLogic
的包装实例,并通过它调用 GetMyOtherBusinessLogic
。换句话说,如果您想记录所有方法,就不能直接在 GetMyBusinessLogic
中调用 GetMyOtherBusinessLogic
,而必须将所有您想记录的方法暴露在接口中,以便 IoC.Resolve
可以用策略注入应用程序块将其包装。演示源代码对此进行了说明。
public MyBusinessObject GetMyBusinessLogic(Int64 id)
{
// need to get a wrapped instance of this class
// so the child method is also logged
IMyBusinessLogic myBusinessLogic = IoC.Resolve<imybusinesslogic>(this);
return myBusinessLogic.GetMyOtherBusinessLogic(id);
}
public MyBusinessObject GetMyOtherBusinessLogic(Int64 id)
{
return new MyBusinessObject {Date = DateTime.Now, Id = id, Name = "My Name"};
}
为您的类或方法打上日志标签
由于魔法字符串很糟糕,我们创建了一个名为 Tags
的 static
类,其中包含我们在策略配置设置中指定的标签名称。现在,我们可以轻松地更改日志名称,而无需修改可能数百个文件。
public static class Tags
{
public const string Log = "Log";
}
现在,对于您想记录的任何方法,只需添加此装饰器:[Tag(Tags.Log)]
。如果您想自动记录一个类中的所有方法,只需将装饰器添加到类本身即可。
试一试
访问演示项目的首页,您将看到 Logs 文件夹中出现一个今天日期的新日志文件。它将列出方法进入/退出日期和时间、使用的线程号以及简短消息。
摘要
通过调用 IoC.Resolve
而不是 ObjectFactory.GetInstance
,并使用 [Tag(Tags.Log)]
标记您的类,您可以自动记录解决方案中所有方法的进入和退出,而无需充斥代码。该解决方案包含方法名、参数和返回值,并且易于未来重构。