记录器包装器的最佳实践

时间:2011-04-13 09:06:47

标签: c# .net logging nlog

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

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

7 个答案:

答案 0 :(得分:192)

我以前使用Common.Logging等日志记录外观(甚至隐藏我自己的CuttingEdge.Logging库),但是现在我使用Dependency Injection pattern这可以让我隐藏自己的记录器(简单)抽象,它遵循Dependency Inversion PrincipleInterface Segregation Principle(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;
    }
}

可选地,可以使用一些简单的扩展方法扩展这种抽象(允许接口保持狭窄并保持与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.
}

由于界面只包含一个方法,因此您可以轻松创建proxies to log4netto SerilogMicrosoft.Extensions.Logging,NLog或任何其他日志记录库的ILogger实施,并进行配置你的DI容器将它注入到构造函数中有ILogger的类中。

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

当你这样做时,几乎不需要记录外墙(或任何其他库)可能提供的静态抽象。

答案 1 :(得分:9)

我使用了来自https://github.com/uhaciogullari/NLog.Interface的小型界面包装+适配器available via NuGet

PM> Install-Package NLog.Interface 

答案 2 :(得分:4)

通常我更喜欢创建像

这样的界面
public interface ILogger
{
 void LogInformation(string msg);
 void LogError(string error);
}

在运行时我注入了一个从这个接口实现的具体类。

答案 3 :(得分:4)

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

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

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

答案 4 :(得分:2)

您可以使用Castle Logging ServicesSimple Logging Façade,而不是编写自己的外观。

两者都包含NLog和Log4net的适配器。

答案 5 :(得分:1)

自2015年以来,如果您正在构建.NET核心应用程序,也可以使用.NET Core Logging

NLog挂钩的包是:

答案 6 :(得分:1)

到目前为止,最好的选择是使用Microsoft.Extensions.Logging软件包(as pointed out by Julian)。大多数日志记录框架都可以与此一起使用。

按照Steven's answer的定义,您自己的界面在简单情况下是可以的,但是却遗漏了一些我认为很重要的事情:

  • 结构化的日志记录和解构对象(Serilog和NLog中的@表示法)
  • 延迟的字符串构造/格式化:因为它需要一个字符串,所以它必须在调用时评估/格式化所有内容,即使最后由于该事件低于阈值也不会记录该事件(性能成本,请参阅上一点)< / li>
  • 出于性能原因,您可能想要像one two three one two three 这样的条件检查

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