在.NET中是否可能进行被动日志记录?

8
我经常为代码中需要包含的大量日志记录而感到沮丧,这让我想知道是否有更好的方法来完成这些工作。
我不知道是否已经有人这样做过或者有没有更好的想法,但我想知道是否有一种方法可以“注入”一个记录器到应用程序中,以便它被动地监视线程并在发生过程时悄悄地记录,而无需执行像以下这样的操作:
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个不同的版本构建?
我经常使用四个级别:致命、关键、信息、详细。

这可以在运行时进行配置,但是在运行时运行的记录器仍将接收所有信息,然后必须决定要记录什么。如果您不想传递所有信息给记录器造成额外负担,那么唯一的方法就是使用不同的构建版本。 - user65199
6个回答

9

您可以使用PostSharp来记录方法的“周围”操作。这正是AOP擅长的事情。您可能需要从Log4PostSharp开始,这是一个专门用于日志记录的插件。


我以前在使用PostSharp时遇到了问题 - 它与Team Build不兼容。它实际上编辑编译器生成的IL,并且对于在哪里写入编辑后的二进制文件感到困惑。也许现在已经修复了。无论如何,让我的IL被重写让我感到很紧张。 - John Saunders
你对代码合约有相同的顾虑吗,出于兴趣? - Jon Skeet
我不知道,它是否重写IL?它使用的场景是否经过Microsoft的充分测试?我担心PostSharp是唯一或少数几个重写IL代码的工具之一,而Team Build等可能没有考虑到这一点。请问“代码契约”的URL是什么? - John Saunders
是的,它可以重写IL(在某些情况下使用这个工具)。http://research.microsoft.com/contracts - Jon Skeet
@Jon 第3条:不过,只因为来自微软,并不能解决所有问题。请考虑无法使用团队生成构建的服务工厂(codeplex/servicefactory)。在这种情况下,左手(P&P)与右手(Team Build)不是在同一页面上。 - John Saunders
显示剩余4条评论

4

这是面向切面编程的经典示例。请查看基于CLR的PostSharp库,它非常优秀。


4
这是AoP教科书示例之一(不确定哪本教科书中有AoP,但您可以理解),涉及日志记录:在方法之前和之后插入某些内容。
您可能想要探索AoP路线,PostSharp是其中一个受欢迎的工具,还有Microsoft Unity(也是IoC)和Castle。
AoP的一个简单示例是在方法之前和之后添加代码,而不是在实际方法内部添加方法调用。由于您已经使用C#标记了问题,因此您可能只需研究创建扩展方法来记录它,该方法已在this question中。
我会采取实际的方法:您需要记录多少?您是否可以仅使用扩展方法而不是让阅读您的代码的人感到困惑。.NET框架中内置的日志记录已经足够好了。

我曾考虑过只使用扩展方法来解决这个问题,但客户希望根据配置文件定义变量级别的日志记录,因此根据配置文件中指定的日志记录级别,日志的详细程度也会有所不同。这就排除了使用扩展方法的可能性。 - BobTheBuilder
这些都可以在配置文件中定义,包括信息/警告/错误和类别。 - Chris S
无论是PostSharp还是其他什么,你都应该考虑使用Enterprise Library作为你的日志API。你可以配置记录哪些内容、记录到哪里以及以何种格式记录。 - John Saunders
@JS:在你的代码中无需输入大量的“记录这个”或“记录那个”的代码吗? - BobTheBuilder
不行。你需要使用PostSharp或等效工具来生成记录日志的代码。我只是建议生成的代码应该调用Enterprise Library Logging Application Block。 - John Saunders

1
除了Jon提到的日志记录方法之外,在VS中跟踪程序流程的另一个有用功能值得注意,那就是具有非中断断点的能力,当命中时会简单地输出消息或运行宏(请注意,您也可以打印变量值)。
右键单击断点,选择“When Hit...”上下文菜单项。
当然,另一个非常有用的功能是System.Diagnostics中的Trace对象和Trace Listners。

我确实使用过这个功能,在开发阶段非常有用,但是它无法帮助客户独立调试他们在生产环境中的应用,而这通常是需要的场景。 - BobTheBuilder

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()是否为非零值,以查看using内部的代码是抛出异常还是正常退出。它不会给您异常,因此必须在catch处理程序中显式记录异常,但它确实指示该区域的“通过/失败”。这使得您的日志范围比仅限于方法更具体,因为您可以在单个方法中有很多这些块,并且知道哪个块确切地引发了异常。

此外,由于现在有一个“logger”对象可用,因此您的catch处理程序看起来就像:

try { ... } 
catch (Exception ex)
{ 
    logger.LogException(ex);
}

日志记录器已经知道方法名称、参数信息等等,并且具有用于格式化异常信息的内部方法。

在这个高级对象下面进入到架构中,有一个名为“LogDisposition”的概念,它处理了我们之前确定的“通过/未通过”,还有一个名为“LogEntryType”的概念,它是一个筛选器(使用Flags枚举实现),表示正在传递什么样的日志条目(错误、跟踪等)。

实际执行日志记录的是发布者/监听器模式。发布者接收传入的日志条目,就像多播委托一样,保持LogListener实例的注册表(应该在程序开始时设置,或在需要时动态添加),并将日志条目传递给这些实例。

LogListeners反过来会过滤出他们关心的日志条目种类。因此,如果您不想要非错误条件下的方法进入和退出点出现在日志中,它们就不必出现在日志中。这可以在运行时进行控制,以允许用户随意打开和关闭详细日志记录。由于发布者可以写入各种日志侦听器,因此您可以连接到编写文件、写入数据库或在GUI中显示错误通知等的东西。

这是一个相当不错的系统,只需要相对较少的编码,即可实现相对丰富的日志记录。

如果您需要,我可以提供代码示例......您可以通过我的(几乎完全闲置的)博客与我联系(请参阅我的账户资料)。

希望这有所帮助。


0

我在所有的项目中都使用开源Apache log4net。它是一个非常简单的实现,具有各种扩展功能,可以让您记录到数据库、压缩文件、滚动日志文件、RRS提要、Telnet客户端等。记录基本上就像这样简单:

'Will print stack trace after message'
log.err(ex.message,ex)

log.warn("warn")
log.info("info")
log.debug("debug")

在运行时,记录参数(如输出格式和日志级别)以输出的方式都是实时读取的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接