将logger作为单例对象是一个好做法吗?

本文关键字:一个 logger 单例 对象 | 更新日期: 2023-09-27 18:17:28

我习惯将logger传递给构造函数,如:

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

但是这很烦人,所以我用它作为属性有一段时间了

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

这也很烦人-它不是干的,我需要在每节课上重复这个。我可以使用基类,但话又说回来-我使用表单类,所以需要FormBase等。所以我认为,有ILogger暴露的单例的缺点是什么,这样每个人都知道从哪里得到logger:

    Infrastructure.Logger.Info("blabla");

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

将logger作为单例对象是一个好做法吗?

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

这也很烦人-它不是DRY

这是真的。但是对于遍及所有类型的横切关注点,您能做的只有这么多。你必须在任何地方使用记录器,所以你必须在这些类型上有这个属性。

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

单例

独生子女是可怕的<flame-suit-on>

我建议坚持使用属性注入,就像您在第二个示例中所做的那样。这是你不用魔法就能做的最好的因数分解。有一个显式依赖比通过单例隐藏要好。

但是,如果单例模式为您节省了大量的时间,包括您将不得不做的所有重构(水晶球时间!),我想你可能能够接受它们。如果说Singleton有什么用处的话,那就是它了。请记住,如果你曾经想要改变你的想法,成本将是尽可能高的。

如果您这样做,请使用Registry模式查看其他人的答案(请参阅描述),以及那些注册(可重置的)单例工厂而不是单例记录器实例的回答。

还有其他的替代方法可以同样有效而不需要做出太多的妥协,所以你应该先检查一下。

Visual Studio代码片段

您可以使用Visual Studio代码片段来加速重复代码的导入。您可以输入logger tab,代码将神奇地为您出现。

使用AOP来DRY

你可以通过使用面向方面编程(AOP)框架(如PostSharp)来自动生成一些属性注入代码,从而消除一些属性注入代码。

当你完成后,它可能看起来像这样:

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

您还可以使用它们的方法跟踪示例代码来自动跟踪方法入口和退出代码,这可能消除了将一些日志记录器属性一起添加的需要。您可以在类级别或名称空间范围内应用该属性:

[Trace]
public class MyClass
{
    // ...
}
// or
#if DEBUG
[assembly: Trace( AttributeTargetTypes = "MyNamespace.*",
    AttributeTargetTypeAttributes = MulticastAttributes.Public,
    AttributeTargetMemberAttributes = MulticastAttributes.Public )]
#endif

问得好。我相信在大多数项目中logger都是单例的。

我突然想到一些主意:

  • 使用ServiceLocator(或者其他依赖注入容器,如果你已经使用了),它允许你在服务/类之间共享记录器,这样你可以实例化记录器甚至多个不同的记录器,并通过ServiceLocator共享,这显然是一个单例,某种形式的控制反转。这种方法在日志程序的实例化和初始化过程中提供了很大的灵活性。
  • 如果你需要记录器几乎无处不在-实现Object类型的扩展方法,以便每个类都能够调用记录器的方法,如LogInfo(), LogDebug(), LogError()

单例是一个好主意。一个更好的主意是使用Registry模式,它对实例化提供了更多的控制。在我看来,单例模式太接近全局变量了。通过注册中心处理对象的创建或重用,为实例化规则的未来更改提供了空间。

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

Registry.Logger.Info("blabla");

一个普通的单例不是一个好主意。这使得很难更换记录器。我倾向于为我的日志记录器使用过滤器(一些"嘈杂的"类可能只记录警告/错误)。

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

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();
    }
}

这允许我在不改变任何代码的情况下创建FilteringLogFactorySimpleFileLogFactory(因此符合开/闭原则)。

样本扩展

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!");
    }
}

有一本书。net中的依赖注入。根据你的需要,你应该使用拦截

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

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

  1. 您是否有依赖或需要它?-需要它
  2. 是跨部门关注吗?——是的
  3. 你需要它的回答吗?——没有

使用拦截

另一个我个人认为最简单的解决方案是使用静态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

如果你想看看一个很好的解决方案的日志记录,我建议你看看谷歌应用程序引擎与python的日志记录是简单的import logging,然后你可以只是logging.debug("my message")logging.info("my message"),这真的保持它简单,因为它应该。

Java没有一个很好的日志解决方案,应该避免使用log4j,因为它实际上迫使你使用单例,正如这里回答的"可怕",我有一个可怕的经验,试图使日志输出相同的日志语句只有一次,当我怀疑双重日志的原因是我有一个单例的日志对象在两个类加载器在同一个虚拟机(!)

请原谅我没有那么具体到c#,但从我所看到的c#的解决方案看起来类似于Java,我们有log4j,我们也应该使它成为一个单例

这就是为什么我真的很喜欢GAE/python的解决方案,它尽可能简单,你不必担心类加载器,获得双重日志语句或任何设计模式。

我希望这些信息中的一些可以与您相关,我希望您想看看我推荐的日志解决方案,而不是我对由于必须在几个类加载器中实例化时不可能拥有真正的单例而怀疑单例有多少问题。