如何将 NLog 包装在适配器中 Class

How to wrap NLog in an Adapter Class

阅读 Steven's answer here 后,我开始考虑关闭日志系统以及它可以成为什么样的 PITA。界面的简单性是我最喜欢的,它是原始的,没有其他项目可以引入,除了您编写的用于生成事件的代码之外,什么都没有。我已经修改了原来的代码,这样我就可以在LogEntry的上下文参数中传入class名称:

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

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

public class LogEntry
{
    public readonly LoggingEventType Severity;
    public readonly string Message;
    public readonly Exception Exception;
    public readonly Type Context;

    public LogEntry(LoggingEventType severity, 
                    string message, 
                    Exception exception = null, 
                    Type context = 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;
        this.Context = context;
    }
}

问题 #1:传入 Type/context 参数有什么问题吗?

这个 post 也阐明了编写基于 Log4net 的适配器,并且是我的 NLog 适配器的基础,尽管我不使用 ILogger 的构造函数注入。

class NLogAdapter : ILogger
{
    public void Log(LogEntry entry)
    {
        NLog.Logger log;
        if (entry.Context != null)
        {
            log = NLog.LogManager.GetLogger(entry.Context.GetType().Namespace);
        }
        else
        {
            log = NLog.LogManager.GetLogger("DefaultLogger");
        }
        switch (entry.Severity)
        {
            case LoggingEventType.Debug:
                log.Debug(entry.Exception, entry.Message);
                break;
            case LoggingEventType.Information:
                log.Info(entry.Exception, entry.Message);
                break;
            case LoggingEventType.Warning:
                log.Warn(entry.Exception, entry.Message);
                break;
            case LoggingEventType.Error:
                log.Error(entry.Exception, entry.Message);
                break;
            case LoggingEventType.Fatal:
                log.Fatal(entry.Exception, entry.Message);
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(entry));
        }
    }
}

问题 #2:我不太确定每次调用都使用日志管理器,这是获取 NLog Logger 实例的最正确方法吗?您还有其他建议可以给我吗?

注意:此适配器可能是 DI 容器中的单例,也可能 used/made 成静态 class。

谢谢, 斯蒂芬

I'm a bit unsure about the use of the log manager for every call, is this the most correct way to get an instance of a NLog Logger? Are there any other recommendations that you might give me?

一个更典型的设计(和性能)设计是创建一个通用实现,并注入一个封闭的通用单例实现,其通用参数等于它被注入的class,因为可以在下面看到answer:

class NLogAdapter<T> : ILogger
{
    private static readonly NLog.Logger log =
        NLog.LogManager.GetLogger(typeof(T).FullName);
}

这不仅使您不必在每次调用时解析 NLog 记录器,还使您不必将上下文传递给 LogEntry。

将它注入消费者,看起来像这样:

new ProductController(new NLogAdapter<ProductController>())

new HomeController(new NLogAdapter<HomeController>())

如果您使用的是 DI 容器,这取决于您使用的容器,以及必须如何配置。以 Simple Injector 为例,只需按如下方式进行上下文注册:

container.RegisterConditional(typeof(ILogger),
    c => typeof(NLogAdapter<>).MakeGenericType(c.Consumer.ImplementationType),
    Lifestyle.Singleton,
    c => true); 

当logging adapter是单例时,意味着开销被降到了最低。

Question #1: Does anything seem wrong about passing in the Type/context param?

在使您的记录器实现具有上下文时,不需要像在记录期间那样传递上下文信息。

重要警告:不要使 ILogger 抽象成为通用的(如 Microsoft did 在他们的日志库中),这只会使消费者和他们的测试复杂化。那将是一个严重的设计缺陷。