记录器包装器最佳实践 [英] Logger wrapper best practice

查看:27
本文介绍了记录器包装器最佳实践的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I want to use a nlogger in my application, maybe in the future I will need to change the logging system. So I want to use a logging facade.

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

Do you know any recommendations for existing examples how to write those ones ? Or just give me link to some best practice in this area.

推荐答案

我曾经使用过记录门面,例如 Common.Logging(甚至隐藏我自己的 CuttingEdge.Logging 库),但现在我使用依赖注入模式.这允许我将记录器隐藏在应用程序定义的抽象背后,该抽象遵守 依赖倒置原则接口隔离原则 (ISP) 因为它有一个成员并且因为接口是由我的应用;不是外部库.

I used to use logging facades such as Common.Logging (even to hide my own CuttingEdge.Logging library), but nowadays I use the Dependency Injection pattern. This allows me to hide loggers behind an application-defined abstraction that adheres to both Dependency Inversion Principle and the Interface Segregation Principle (ISP) because it has one member and because the interface is defined by my application; not an external library.

尽量减少应用程序核心部分对外部库的存在的了解,效果更好;即使您无意替换您的日志库.对外部库的严格依赖使得测试您的代码变得更加困难,并且使用从未专门为您的应用程序设计的 API 使您的应用程序复杂化.

Minimizing the knowledge that the core parts of your application have about the existence of external libraries, the better; even if you have no intention to ever replace your logging library. The hard dependency on the external library makes it more difficult to test your code, and it complicates your application with an API that was never designed specifically for your application.

这就是我的应用程序中的抽象通常的样子:

This is what the abstraction often looks like in my applications:

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

public sealed class ConsoleLogger : ILogger
{
    public void Log(LogEntry entry)
}

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

// Immutable DTO that contains the log information.
public struct LogEntry
{
    public LoggingEventType Severity { get; }
    public string Message { get; }
    public Exception Exception { get; }

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

        this.Severity = severity;
        this.Message = msg;
        this.Exception = ex;
    }
}

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

Optionally, this abstraction can be extended with some simple extension methods (allowing the interface to stay narrow and keep adhering to the ISP). This makes the code for the consumers of this interface much simpler:

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 ex) =>
        logger.Log(new LogEntry(LoggingEventType.Error, ex.Message, ex));

    // More methods here.
}

因为接口只包含一个方法,所以很容易创建一个 ILogger 实现,代理log4net转至 Serilog, Microsoft.Extensions.Logging、NLog或任何其他日志库并配置您的 DI 容器以将其注入在其构造函数中具有 ILogger 的类中.创建写入控制台的实现或可用于单元测试的虚假实现也很容易,如下面的清单所示:

Because the interface contains just a single method, it becomes easily to create an ILogger implementation that proxies to log4net, to Serilog, Microsoft.Extensions.Logging, NLog or any other logging library and configure your DI container to inject it in classes that have a ILogger in their constructor. It is also easy to create an implementation that writes to the console, or a fake implementation that can be used for unit testing, as shown in the listing below:

public class ConsoleLogger : ILogger
{
    public void Log(LogEntry entry) => Console.WriteLine(
      $"[{entry.Severity}] {DateTime.Now} {entry.Message} {entry.Exception}");
}

public class FakeLogger : List<LogEntry>, ILogger
{
    public void Log(LogEntry entry) => this.Add(entry);
}

在具有单个方法的接口之上具有静态扩展方法与具有具有多个成员的接口完全不同.扩展方法只是创建 LogEntry 消息并将其通过 ILogger 接口上的唯一方法传递的辅助方法.这些扩展方法本身不包含自身的易失性行为,因此不会妨碍可测试性.如果您愿意,您可以轻松地测试它们,它们将成为消费者代码的一部分;不是抽象的一部分.

Having static extension methods on top of an interface with a single method is quite different from having an interface with many members. The extension methods are just helper methods that create a LogEntry message and pass it through the only method on the ILogger interface. These extension methods themselves contain no Volatile Behavior of themselves and, therefore, won't hinder testability. You can easily test them if you wish, and they become part of the consumer's code; not part of the abstraction.

这不仅允许扩展方法在不需要更改抽象的情况下发展,扩展方法和 LogEntry 构造函数总是在使用 logger 抽象时执行,即使该 logger 是存根/嘲笑.这使在测试套件中运行时对记录器调用的正确性更加确定.我已经多次用这种方法自欺欺人了,在我的单元测试期间,我对使用的第三方记录器抽象的调用成功了,但在生产中执行时仍然失败.

Not only does this allow the extension methods to evolve without the need to change the abstraction, the extension methods and the LogEntry constructor are always executed when the logger abstraction is used, even when that logger is stubbed/mocked. This gives more certainty about the correctness of calls to the logger when running in a test suite. I've shot myself in the foot with this many times, where my calls to the used third-party logger abstraction succeeded during my unit test, but still failed when executed in production.

单成员界面也使测试变得更加容易;拥有包含许多成员的抽象使得创建实现(例如模拟、适配器和装饰器)变得困难.

The one-membered interface makes testing much easier as well; Having an abstraction with many members makes it hard to create implementations (such as mocks, adapters, and decorators).

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

When you do this, there is hardly ever any need for some static abstraction that logging facades (or any other library) might offer.

尽管如此,即使采用这种 ILogger 设计,您还是更喜欢以这样的方式设计您的应用程序,即只有少数类需要依赖于您的 ILogger 抽象.这个答案对此进行了详细讨论.

Still, even with this ILogger design, prefer designing your application in such way that only a few classes require a dependency on your ILogger abstraction. This answer talks about this in detail.

这篇关于记录器包装器最佳实践的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆