我应该如何在我的应用程序中架构日志?

时间:2011-07-14 23:58:59

标签: architecture logging dependency-injection service-locator cross-cutting-concerns

所以我对此进行了大量的研究,并没有找到任何答案,我说,“是的,那就是”。我希望那些博学多才的StackOverflow人群可以帮助我。

我在几个不同的场景中遇到过这个问题。假设我有一个C#应用程序,并且我想记录重要的事情。

public class MyClass
{
    ... 

    public void ImportantMethod()
    {
        DoInterestingThing();

        var result = SomethingElseImportant();
        if (result == null)
        {
            logger.Log("I wasn't expecting that. No biggie.");
            return;
        }

        MoreInterestingStuff(); 
}

我感兴趣的是我从哪里获得logger

在我看来,我有几个选择。

  1. 将它注入构造函数中的MyClass。
  2. 使用全球可用的服务定位器检索它。
  3. 使用方法装饰器和AOP为我完成日志记录。
  4. 这些似乎都不是很好的选择。 #3看起来是不可能的,因为我正在我的业务逻辑中间进行记录而不只是简单地跟踪我的方法调用,输入参数和/或引发的异常。 #2,虽然简单似乎很难进行单元测试。当然,我想要对所有内容进行单元测试。 #1,虽然它可以正常工作,但是使用与业务对象本身无关的记录对象,使我的所有业务逻辑变得混乱。

    上述其中一个选项的其他想法或想法? 非常感谢!

    编辑:为了清楚,我已经知道如何做DI(我使用Unity),我已经知道一个好的日志框架(我使用log4net)。只是想知道如何以最智能的方式在应用程序中使用体系结构意义上的日志记录。


    *编辑*

    我将Mark Seeman的答案标记为解决方案。我浏览了我的应用程序,发现我的大部分日志记录调用都是装饰者可以做的事情。即,记录方法的条目,抛出任何异常,并退出返回值。

    有些情况下,我仍然需要直接在方法中记录。一个例子是我希望在一个方法中快速失败的方法,该方法不会返回任何内容而不是throw an Exception。在这些情况下,我有一个单例,它包含一个引用LogProvider,它将依次检索一个命名的日志实例。代码看起来类似于:

    private ILog logger = LogProviderFactory.Instance.GetLogger(typeof(Foo));
    

    LogProviderFactory有一个方法SetProvider,允许你换掉单例。所以在单元测试中,我可以这样做:

    // LogProviderFactory.Instance now is our mock
    LogProviderFactory.SetProvider(MockLogProvider);
    

    日志记录装饰器使用与单例(它通过注入获得)相同的LogProvider,因此日志记录在整个系统中统一。

    所以最终的解决方案主要是选项#3和混合#2(它是服务定位器模式,但服务被'注入'到定位器中)。

    AOP

    就“面向方面的编程”而言,我对语言的局限感到有些失望。希望AOP在未来版本中被视为一等公民。

    • 我尝试过PostSharp但是无法正确地在我的机器上运行它。此外,您必须在系统上安装PostSharp才能使用它(而不是仅仅调用解决方案附带的dll或类似的东西),这是一个很大的限制。
    • 我使用了LinFu并且能够让它部分工作。然而,它在少数情况下爆炸了。新的2.0版本几乎没有记录,因此这是一个障碍。
    • 与Unity的接口拦截似乎开箱即用。我很幸运,我想记录的大部分内容都是在实现接口的类中。

3 个答案:

答案 0 :(得分:4)

使用logging Decorator

答案 1 :(得分:2)

两位:

(1) - 预先构建的日志记录框架。

有些人喜欢Log4Net,但我是EntLibs粉丝。这在实际记录方面做得很重。像EntLibs这样的工具可以让你登录到不同类型的日志存储库(数据库,消息队列,滚动文本文件等)。它们还允许您根据类别等登录到不同的实例。它们通常具有高度可配置性。

(2) - 包装日志框架的自定义类。

所以“logger”是你编写的东西,它调用日志框架来进行实际的日志记录。

我喜欢这种方法有几个原因:

  • 当自定义包装器进入单独的程序集时,您将日志记录框架(#1)与应用程序的其余部分分离。
  • 通过编写自己的Logging API,您可以定义适合您需求的方法签名,并且可以对其进行扩展。
  • 如果您在团队中工作,您可以使方法签名非常易于使用,这样就没有理由说使用日志记录太难了。
  • 它使日志记录保持一致。它还可以轻松地对代码搜索“非法”代码进行搜索,这些代码可以轻松写入文件,控制台或事件日志,因为日志记录中不会有任何内容(这些都在框架中)。
  • 通过为每个层编写特定的自定义类,您可以在幕后预先填充大量数据,从而使编写实际应用程序代码的人员的生活更轻松。您可以设置严重性,优先级,默认事件ID,类别等。
  • 它在应用程序复杂性和增长方面表现良好;对于较小的应用程序来说,它可能看起来很苛刻,但如果它随着时间的推移开始在你身上发展,那么你就有足够的空间。

这是我所参与的项目中的信息记录类的示例。它有一堆易于调用的公共方法,以及一个调用框架的私有方法(ConcreteLogInformation)。

public static void LogInformation(string title, string message)

public static void LogInformation(string title, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, int eventId, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, string message, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, string message, int eventId)

public static void LogInformation(string title, string message, int eventId, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, string message, int eventId, string category)

public static void LogInformation(string title, string message, int eventId, Dictionary<string, object> extendedProperties, string category)

private static void ConcreteLogInformation(string title, string message, int eventId, Dictionary<string, object> extendedProperties, string category)

答案 2 :(得分:1)

使用工厂或工厂方法中的请求上下文进行上下文绑定部分Ninject Contetual Binding docs我有一个利用容器为您的类注入适当记录器的示例通过做(在Ninjectese):

Bind<ILog>().ToMethod( context => LogFactory.CreateLog( context.Request.Target.Type ) );

对于追踪类型的东西,Mark的拦截文章描述了最佳方法。

我可以再问一下,在没有赞成的情况下丢弃它们之前,你已经深入阅读了@Mark Seemann引用的文章。