将记录器作为单身人员是一种好习惯吗?

时间:2011-12-12 10:11:03

标签: c# .net logging dependency-injection singleton

我有把logger传递给构造函数的习惯,比如:

public class OrderService : IOrderService {
     public OrderService(ILogger logger) {
     }
}

但这非常令人讨厌,所以我已经将它作为一个属性使用了一段时间:

private ILogger logger = NullLogger.Instance;
public ILogger Logger
{
    get { return logger; }
    set { logger = value; }
}

这也很烦人 - 它不干,我需要在每个班级重复这个。我可以使用基类,但是再次 - 我正在使用Form类,所以需要FormBase等。 所以我认为,暴露ILogger的单身人士会有什么不利因素,所以我们知道在哪里得到记录器:

    Infrastructure.Logger.Info("blabla");

更新:正如Merlyn正确注意到的那样,我应该提一下,在第一和第二个例子中,我正在使用DI。

8 个答案:

答案 0 :(得分:37)

我在我的依赖注入容器中放入一个logger实例,然后将记录器注入需要一个的类中。

答案 1 :(得分:33)

  

这也很烦人 - 它不是DRY

这是真的。但是,只有你可以做的事情才能实现贯穿各种类型的贯穿各领域的关注。您必须在任何地方使用记录器,因此您必须拥有这些类型的属性。

让我们看看我们能做些什么。

的Singleton

单身人士很可怕<flame-suit-on>

我建议您在完成第二个示例时坚持使用属性注入。这是你无需借助魔法就可以做到的最好的因素。拥有显式依赖关系比通过单例隐藏它更好。

但是如果单身人士为你节省了大量时间,包括你将不得不做的所有重构(水晶球时间!),我想你可能能够和他们一起生活。如果有一个单身人士的用途,这可能是它。请记住,如果希望改变主意,那么费用就会高得多。

如果你这样做,请使用the Registry pattern查看其他人的答案(参见说明),以及注册(可重置的)单件工厂而不是单件记录器实例的人。

还有其他替代方案可以在没有太多妥协的情况下正常工作,因此您应该首先检查它们。

Visual Studio代码段

您可以使用Visual Studio code snippets来加速重复代码的输入。您可以键入logger tab 之类的内容,代码将会神奇地为您显示。

使用AOP干掉

您可以使用an Aspect Oriented Programming (AOP) framework like PostSharp自动生成部分属性注入代码,从而消除一些属性注入代码。

完成后可能看起来像这样:

[InjectedLogger]
public ILogger Logger { get; set; }

您还可以使用their method tracing sample code自动跟踪方法入口和出口代码,这可能无需一起添加一些记录器属性。您可以在类级别或名称空间范围内应用该属性:

[Trace]
public class MyClass
{
    // ...
}

// or

#if DEBUG
[assembly: Trace( AttributeTargetTypes = "MyNamespace.*",
    AttributeTargetTypeAttributes = MulticastAttributes.Public,
    AttributeTargetMemberAttributes = MulticastAttributes.Public )]
#endif

答案 2 :(得分:23)

好问题。我相信大多数项目记录器都是单身人士。

我想到了一些想法:

  • 使用ServiceLocator(或其他Dependency Injection容器,如果您已经使用任何容器),它允许您跨服务/类共享记录器,这样您就可以实例化记录器甚至多个不同的记录器和通过ServiceLocator共享,这显然是一个单例,某种Inversion of Control。这种方法为记录器实例化和初始化过程提供了很大的灵活性。
  • 如果您几乎无处不在需要记录器 - 为Object类型实现扩展方法,以便每个类都可以调用logger的方法,如LogInfo()LogDebug()LogError()

答案 3 :(得分:14)

单身是一个好主意。更好的想法是使用Registry模式,这样可以更好地控制实例化。在我看来,单例模式太接近全局变量。通过注册表处理对象创建或重用,将来可以对实例化规则进行更改。

注册表本身可以是一个静态类,提供访问日志的简单语法:

Registry.Logger.Info("blabla");

答案 4 :(得分:9)

普通的单身人士不是个好主意。这使得更换记录器变得困难。我倾向于为我的记录器使用过滤器(一些“嘈杂”类可能只记录警告/错误)。

我使用单例模式与记录器工厂的代理模式相结合:

public class LogFactory
{
    private static LogFactory _instance;

    public static void Assign(LogFactory instance)
    {
        _instance = instance;
    }

    public static LogFactory Instance
    {
        get { _instance ?? (_instance = new LogFactory()); }
    }

    public virtual ILogger GetLogger<T>()
    {
        return new SystemDebugLogger();
    }
}

这允许我创建FilteringLogFactory或仅SimpleFileLogFactory而不更改任何代码(因此遵守开放/封闭原则)。

示例扩展程序

public class FilteredLogFactory : LogFactory
{
    public override ILogger GetLogger<T>()
    {
        if (typeof(ITextParser).IsAssignableFrom(typeof(T)))
            return new FilteredLogger(typeof(T));

        return new FileLogger(@"C:\Logs\MyApp.log");
    }
}

并使用新工厂

// and to use the new log factory (somewhere early in the application):
LogFactory.Assign(new FilteredLogFactory());

在您的课程中应记录:

public class MyUserService : IUserService
{
    ILogger _logger = LogFactory.Instance.GetLogger<MyUserService>();

    public void SomeMethod()
    {
        _logger.Debug("Welcome world!");
    }
}

答案 5 :(得分:3)

.NET中有一本书Dependency Injection。根据您的需要,您应该使用拦截。

在本书中,有一个图表有助于决定是使用构造函数注入,属性注入,方法注入,环境上下文,拦截。

这就是使用此图表的一个原因:

  1. 你有依赖还是需要它? - 需要它
  2. 这是跨领域的关注吗? - 是的
  3. 你需要答案吗? - 没有
  4. 使用拦截

答案 6 :(得分:0)

如果您想查看一个很好的日志记录解决方案,我建议您使用python查看google app引擎,其中日志记录就像import logging一样简单,然后您就可以logging.debug("my message")或{{1}这真的让它变得如此简单。

Java没有一个很好的日志记录解决方案,即应该避免使用log4j,因为它实际上迫使你使用单圈,这里的回答“非常糟糕”而且我在尝试使日志记录输出相同的日志时遇到了糟糕的经历当我怀疑双重记录的原因是我在同一个虚拟机中的两个类加载器中有一个Singleton of logging对象时,只声明一次(!)

请原谅我没有那么特别针对C#,但是从我看到的C#解决方案看起来与我们有log4j的Java类似,我们也应该让它成为单例。

这就是为什么我真的很喜欢solution with GAE / python,它尽可能简单,你不必担心类加载器,获得双重日志声明或任何设计模式就此而言。

我希望这些信息可以与您相关,我希望您能看一下我推荐的I日志解决方案,而不是因为我无法解决由于无法获得当它必须在几个类加载器中实现时,真正的单例。

答案 7 :(得分:0)

我个人认为最简单的另一个解决方案是使用静态Logger类。您可以从任何类方法调用它,而无需更改类,例如添加属性注入等。它非常简单易用。

Logger::initialize ("filename.log", Logger::LEVEL_ERROR); // only need to be called once in your application

Logger::log ("my error message", Logger::LEVEL_ERROR); // to be used in every method where needed