我经常对我必须包含在代码中的日志记录数量感到沮丧,这让我想知道是否有更好的做事方式。
我不知道这是否已经完成或是否有人提出了更好的想法,但我想知道是否有人知道将“记录器”“注入”应用程序以便被动地监视线程并且无需执行以下操作即可安静地记录流程:
public void MyProcess(int a, string b, object c)
{
log(
String.Format(
"Entering process MyProcess with arguments: [a] = [{0}]; [b] = [{1}]; [c] = [{2}]",
a.ToString(),
b,
c.ToString()
);
try
{
int d = DoStuff(a)
log(
String.Format(
"DoStuff({0}) returned value {1}",
a.ToString(),
d.ToString()
)
);
}
catch (Exception ex)
{
log(
String.Format("An exception occurred during process DoStuff({0})\nException:\n{1}",
a.ToString(),
ex.ToString())
)
}
}
如果我可以对记录器说:
,那将是多么美妙Monitor(MyClass.MyMethod)
然后它将监视该方法内部的所有内容,包括传入参数以及方法调用和传递给这些方法的值,发生的异常等。
过去有没有人实现过这样的东西?甚至可以 实施吗?以这种方式登录只是一个白日梦吗?
我喜欢设计能够做到这一点的东西,但我甚至不知道我会从哪里开始。当然,我也不想重新发明轮子,如果它已经完成了,如果有人能指出我正确的方向,那就太好了。
任何建议都会感激不尽......
编辑:我想我会回答一个问题,询问日志中所需的详细程度。通常需要提供可配置级别的日志记录,以便如果配置指定详细日志记录,则记录所有内容,而如果配置了关键日志记录,则仅记录某些信息以及异常。如果配置了致命日志记录,则仅记录导致应用程序死亡的信息。这样的事情是可配置的还是AOP需要3或4个不同的版本,具体取决于日志记录级别的数量?
我经常使用4个级别:致命,严重,信息,详细
答案 0 :(得分:9)
您可以使用PostSharp来记录方法。这正是AOP擅长的事情。您可能希望从Log4PostSharp开始 - 一个专门用于记录的插件。
答案 1 :(得分:4)
这是面向方面编程的经典示例。有关非常好的基于CLR的库,请参阅PostSharp。
答案 2 :(得分:4)
这是教科书之一(不确定哪个教科书中有AoP,但你明白了)AoP的例子 - 记录:你想在方法之前和之后粘贴一些东西。
您可能想要探索AoP路线,PostSharp是最流行的路线之一,还有Microsoft Unity(IoC),Castle。
AoP的一个简单示例是在方法之前和之后添加代码,而不是在实际方法中添加方法调用。正如您使用C#标记了问题一样,您可能只想研究一种扩展方法来记录它,该方法已经在this question中。
我会采取切实可行的方法:你做了多少实际记录?你能不能只使用一种扩展方法,而不是让那些阅读你代码的人眼花缭乱。记录built into the .NET framework已经很好了。
答案 3 :(得分:1)
除了Jon提到的日志记录方法之外,它可能还值得注意VS中用于跟踪程序流的另一个有用的功能,那就是能够使用非破坏断点来简单地输出消息或运行宏时点击(注意你也可以打印变量值)
右键单击断点并选择When Hit ...上下文菜单项。
当然,另一个非常有用的功能是System.Diagnostics中的Trace对象和Trace Listners。
答案 4 :(得分:0)
我最近编写了一个日志记录库,它使用IDisposable接口来包装具有日志记录上下文的区域。基本上,你有一个像这样使用的LogSite一次性对象:
using(var logger = new LogSite("methodName", new object[] { p1, p2, p3 })
{
// code that does stuff goes here
}
LogSite对象为构造函数(如MethodBase)提供了一堆方便的重载,因此您只需使用MethodBase.GetCurrentMethod()并使用反射来获取方法和参数的实际名称(而不是硬编码字符串) 。
它的工作方式是这样的 - 在构造函数中,它使用所有跟踪信息写入日志,以指示它进入块。在Dispose方法中,它写入一个退出条目。
在处理时,它还会检查Marshal.GetExceptionCode()是否为非零值,以查看使用中的代码是抛出异常还是正常退出。它不会给你异常,因此必须在catch处理程序中显式记录,但它确实指示该区域的“通过/失败”。这允许您的日志记录范围比方法更具体,因为您可以在单个方法中拥有大量这些块,并且知道哪个块完全抛出了异常。
此外,由于现在有一个“logger”对象,你的catch处理程序看起来像:
try { ... }
catch (Exception ex)
{
logger.LogException(ex);
}
记录器已经知道方法名称,参数信息以及所有这些,并且具有用于格式化异常信息的内部方法。
进入这个高级对象下面的体系结构,有一个“LogDisposition”的概念,它处理我们之前确定的“通过/失败”,并且有一个“LogEntryType”的概念,它是一个过滤器(用Flags枚举实现) )表示正在传递的日志条目类型(错误,跟踪等)。
实际执行日志记录的事情只是发布者/侦听器模式。发布者接受传入的日志条目,就像多播委托一样,保留LogListener实例的注册表(应在程序开始时设置,或根据需要动态添加),并将日志条目传递给这些实例。
LogListeners依次筛选出他们关心的日志条目类型。因此,如果您不希望方法入口和出口点用于非错误条件,则不必显示在日志中。这可以在运行时控制,以允许用户执行诸如随意打开和关闭详细日志记录之类的操作。由于发布者可以写入各种loglisteners,您可以连接写入文件或写入数据库的内容,或在GUI中显示错误通知......等等。
这是一个非常好的系统,需要相对少量的编码才能获得相对丰富的日志记录。
如果您愿意,我可以给您一个代码示例...您可以通过我的(几乎完全不活跃的)博客(请参阅我的帐户个人资料)与我联系。
希望有所帮助。
答案 5 :(得分:0)
我在所有项目中都使用了开源Apache log4net。这是一个非常简单的实现,具有各种扩展,允许您登录到数据库,zip文件,滚动日志文件,RRS订阅源,telnet客户端等。日志记录基本上就像:
'Will print stack trace after message'
log.err(ex.message,ex)
log.warn("warn")
log.info("info")
log.debug("debug")
当您的应用程序处于运行时,所有记录参数(例如输出格式和日志级别)都会实时读取。