记录器包装器的最佳实践

Nig*_*ker 87 .net c# logging nlog

我想在我的应用程序中使用nlogger,将来可能需要更改日志记录系统.所以我想使用日志门面.

您是否知道对现有示例的任何建议如何编写这些示例?或者只是给我链接到这个领域的一些最佳实践.

Ste*_*ven 196

我以前使用日志记录外观,例如Common.Logging(甚至隐藏我自己的CuttingEdge.Logging库),但是现在我使用依赖注入模式,这允许我隐藏我自己(简单)抽象的记录器,这些抽象遵循两个依赖反转原理接口隔离原则(ISP),因为它有一个成员,因为接口是由我的应用程序定义的; 不是外部图书馆.尽量减少应用程序核心部分对外部库存在的了解,这样做会更好; 即使您无意更换日志库.对外部库的硬依赖性使得测试代码变得更加困难,并且使用从未专门为您的应用程序设计的API使您的应用程序复杂化.

这就是抽象在我的应用程序中经常出现的情况:

public interface ILogger
{
    void Log(LogEntry entry);
}

public enum LoggingEventType { Debug, Information, Warning, Error, Fatal };

// Immutable DTO that contains the log information.
public class LogEntry 
{
    public readonly LoggingEventType Severity;
    public readonly string Message;
    public readonly Exception Exception;

    public LogEntry(LoggingEventType severity, string message, Exception exception = null)
    {
        if (message == null) throw new ArgumentNullException("message");
        if (message == string.Empty) throw new ArgumentException("empty", "message");

        this.Severity = severity;
        this.Message = message;
        this.Exception = exception;
    }
}
Run Code Online (Sandbox Code Playgroud)

可选地,可以使用一些简单的扩展方法来扩展该抽象(允许接口保持狭窄并且保持遵守ISP).这使得此接口的使用者的代码更加简单:

public static class LoggerExtensions
{
    public static void Log(this ILogger logger, string message) {
        logger.Log(new LogEntry(LoggingEventType.Information, message));
    }

    public static void Log(this ILogger logger, Exception exception) {
        logger.Log(new LogEntry(LoggingEventType.Error, exception.Message, exception));
    }

    // More methods here.
}
Run Code Online (Sandbox Code Playgroud)

由于接口只包含一个方法,你可以轻松地创建一个ILogger实现,代理到log4net的,到Serilog,Microsoft.Extensions.Logging,NLOG或任何其他日志库和配置您的DI容器在具有类注入它ILogger在其构造函数.

请注意,在具有单个方法的接口上使用静态扩展方法与具有许多成员的接口完全不同.扩展方法只是辅助方法,它创建LogEntry消息并通过ILogger接口上的唯一方法传递它.扩展方法成为消费者代码的一部分; 不是抽象的一部分.这不仅允许扩展方法在不需要更改抽象的情况下进化,扩展方法和LogEntry构造函数总是在使用记录器抽象时执行,即使该记录器被存根/模拟也是如此.这样可以更加确定在测试套件中运行时对记录器的调用是否正确.单元接口也使测试更容易; 拥有许多成员的抽象使得很难创建实现(例如模拟,适配器和装饰器).

当你这样做时,几乎不需要一些日志外观(或任何其他库)可能提供的静态抽象.

  • @GabrielEspinoza:这完全取决于yu放置扩展方法的命名空间.如果yu将它放在与接口相同的命名空间或项目的根命名空间中,则问题将不存在. (4认同)
  • @Elisabeth好处是你可以通过实现一个单独的函数:"ILogger :: Log",使Facade接口适应任何日志框架.扩展方法确保我们可以访问"便利"API(例如"LogError","LogWarning"等),无论您决定使用哪个框架.尽管使用了C#接口,但这是一种添加常见"基类"功能的迂回方式. (3认同)
  • @ user1829319这只是一个例子.我相信您可以根据您的特定需求提出基于此答案的实施方案. (2认同)
  • 我仍然不明白...将5个Logger方法作为ILogger的扩展而不是ILogger的成员有何优势? (2认同)
  • 我需要再次强调一个为什么这很好的原因.将代码从DotNetFramework上转换为DotNetCore.我这样做的项目,我只需要写一个新的混凝土.那些我没有的地方...... gaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!我很高兴我找到了这条"回来的路". (2认同)

Jon*_*ams 9

我使用了https://github.com/uhaciogullari/NLog.Interface上的小接口包装+适配器,也可以通过NuGet获得:

PM> Install-Package NLog.Interface 
Run Code Online (Sandbox Code Playgroud)

  • v4.0在NLog库中有一个ILogger接口.你不再需要这个库了 (8认同)

Phi*_*ppe 9

到目前为止,最好的办法是使用Microsoft.Extensions.Logging包(正如 Julian 指出的那样)。大多数日志框架都可以与此一起使用。

定义您自己的界面,如Steven 的回答中所述,对于简单的情况是可以的,但它遗漏了一些我认为重要的事情:

  • 结构化日志和解构对象(Serilog 和 NLog 中的 @ 符号)
  • 延迟的字符串构造/格式化:因为它需要一个字符串,所以它必须在调用时评估/格式化所有内容,即使最终由于低于阈值而不会记录该事件(性能成本,请参阅上一点)
  • IsEnabled(LogLevel)出于性能原因再次进行您可能想要的条件检查

您可能可以在自己的抽象中实现所有这些,但那时您将重新发明轮子。


Rob*_*vis 5

这个问题的一个很好的解决方案以LibLog项目的形式出现。

LibLog 是一个日志抽象,内置对主要记录器的支持,包括 Serilog、NLog、Log4net 和企业记录器。它通过 NuGet 包管理器作为源 (.cs) 文件而不是 .dll 引用安装到目标库中。该方法允许包含日志抽象,而无需强制库承担外部依赖项。它还允许库作者在不强制消费应用程序向库显式提供记录器的情况下包括日志记录。LibLog 使用反射来确定正在使用的具体记录器,并在库项目中没有任何显式接线代码的情况下连接到它。

因此,LibLog 是在库项目中记录日志的绝佳解决方案。只需在您的主应用程序或服务中引用和配置一个具体的记录器(Serilog for the win),然后将 LibLog 添加到您的库中!


归档时间:

查看次数:

52916 次

最近记录:

6 年,6 月 前