【问题标题】:Why use singleton for application logging?为什么使用单例进行应用程序日志记录?
【发布时间】:2012-03-16 02:36:33
【问题描述】:

我是 reading 关于单例模式的缺点。许多论坛中建议的单例的有效用法是 Logging 应用程序。我想知道为什么这是对模式的有效使用。我们不是在整个应用程序的内存中维护状态信息吗?

为什么不直接使用函数:

class Logger
{
    public static void Log(string message)
    {
         //Append to file

    }
}

【问题讨论】:

    标签: c# singleton


    【解决方案1】:

    回答“为什么不只使用函数”:此代码在多线程日志记录中无法正常工作。如果两个线程尝试写入同一个文件,则会抛出异常。这就是为什么最好使用单例进行日志记录。在这个解决方案中,我们有一个线程安全的单例容器,其他线程将消息(日志)安全地推送到容器中。并且容器(始终是线程安全队列)将消息/日志一一写入文件/db/etc。

    【讨论】:

      【解决方案2】:

      最好声明接口:

      interface ILogger
      {
          public void Log(string message);
      }
      

      然后实现特定类型的logger

      class FileLogger : ILogger
      {
          public void Log(string message)
          {
               //Append to file
          }
      }
      
      class EmptyLogger : ILogger
      {
          public void Log(string message)
          {
               //Do nothing
          }
      }
      

      并在需要的地方注入。您将在测试中注入EmptyLogger。使用单例会使测试变得更加困难,因为您也必须在测试中保存到文件中。如果要测试类是否生成正确的日志条目,可以使用模拟并定义期望。

      关于注入:

      public class ClassThatUsesLogger
      {
          private ILogger Logger { get; set; }
          public ClassThatUsesLogger(ILogger logger) { Logger = logger }
      }
      

      ClassThatUsesLogger 在生产代码中采用 FileLogger:

      classThatUsesLogger = new ClassThatUsesLogger(new FileLogger());
      

      在测试中需要 EmptyLogger:

      classThatUsesLogger = new ClassThatUsesLogger(new EmptyLogger());
      

      您在不同的场景中注入不同的记录器。有更好的方法来处理注射,但你必须做一些阅读。

      编辑

      请记住,您仍然可以在代码中使用单例,正如其他人所建议的那样,但您应该将其用法隐藏在接口后面,以放松类和特定日志实现之间的依赖关系。

      【讨论】:

      • 我不明白注入部分。能详细点吗?
      • 一个需要记录器但不创建记录器的类,在类的构造过程中(或最常见的是通过构造函数)传入的类称为具有记录器注入类。 en.wikipedia.org/wiki/Dependency_injection
      【解决方案3】:

      当您询问有关内存中剩余状态信息的问题时,我不确定您指的是什么,但是支持单例而不是静态进行日志记录的一个原因是单例仍然允许您两者
      (1)抽象程序 (ILogger) 和
      (2) 通过练习依赖注入来遵守依赖倒置原则

      您不能将静态日志记录方法作为依赖项注入(除非您想在任何地方传递 Action<string> 之类的东西),但您可以传递一个单例对象,并且您可以传递不同的实现在编写单元测试时就像NullLogger

      【讨论】:

        【解决方案4】:

        单例记录器实现允许您轻松控制将日志记录刷新到磁盘或数据库的频率。如果您有多个记录器实例,那么它们可能都试图同时写入,这可能会导致冲突或性能问题。单例允许对此进行管理,以便您仅在安静时间刷新到商店,并且所有消息都井井有条。

        【讨论】:

        • 我同意@Jay 和 LukLed 的观点,即您应该将记录器定义为允许反转控制的接口。这将使您的测试更加容易。
        【解决方案5】:

        在大多数情况下不推荐使用 Singleton 设计模式,因为它是一种全局状态,隐藏了依赖关系(使 API 不那么明显)并且难以测试。

        日志记录不是这些情况之一。这是因为日志不会影响代码的执行。也就是说,正如这里所解释的:http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html

        无论是否给定,您的应用程序的行为都不会有任何不同 记录器已启用。这里的信息流向一种方式:从您的 应用到记录器中。

        您可能仍然不想使用单例模式。至少不完全是。这是因为没有理由强制记录器的单个实例。如果您想要两个日志文件,或者两个行为不同且用于不同目的的记录器怎么办?

        因此,您真正想要的 logger 就是让您可以在需要时从任何地方轻松访问它。基本上,日志记录是一种特殊情况,最好的方法是使其全局可访问。

        1. 简单的方法是在您的应用程序中添加一个包含 logger 实例的静态字段:

          public final static LOGGER = new Logger();
          
        2. 或者如果您的记录器是由工厂创建的:

          public final static LOGGER = new LoggerFactory().getLogger("myLogger");
          
        3. 或者,如果您的记录器是由 DI 容器创建的:

          public final static LOGGER = Container.getInstance("myLogger");
          
        4. 您可以通过配置文件使您的记录器实现可配置,您可以在进行测试时将其设置为“mode = test”,以便在这些情况下记录器可以相应地运行,或者不记录,或者登录到控制台。

          public final static LOGGER = new Logger("logConfig.cfg");
          
        5. 您还可以使记录器的行为在运行时可配置。因此,在运行测试时,您可以简单地设置它:LOGGER.setMode("test");

        6. 或者,如果您没有将静态 LOGGER 设置为最终状态,则可以在测试设置中简单地将静态 LOGGER 替换为测试记录器或模拟记录器。

        7. 你可以做的稍微有点花哨的东西接近单例模式,但不完全是:

          public class Logger
          {
              private static Logger default;
              public static getDefault()
              {
                  if(default == null)
                  {
                      throw new RuntimeException("No default logger was specified.");
                  }
                  return default;
              }
          
              public static void setDefault(Logger logger)
              {
                  if(default != null)
                  {
                      throw new RuntimeException("Default logger already specified.");
                  }
                  default = logger;
              }
          
              public Logger()
              {
              }
          }
          
          public static void main(String [] args)
          {
              Logger.setDefault(new Logger());
          }
          
          @Test
          public void myTest()
          {
              Logger.setDefault(new MockedLogger());
          
              // ... test stuff
          }
          

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-04-09
          • 2021-03-22
          • 2021-03-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-01-31
          • 1970-01-01
          相关资源
          最近更新 更多