我希望在我的应用程序中使用nlogger,也许将来我需要更改日志记录系统。 因此,我想使用一个日志门面。
您知道有关如何编写这些内容的现有示例的任何建议吗? 或者只需给我提供一些最佳实践的链接。
(Note: I have retained the HTML tags as requested and provided a concise translation.)
我希望在我的应用程序中使用nlogger,也许将来我需要更改日志记录系统。 因此,我想使用一个日志门面。
您知道有关如何编写这些内容的现有示例的任何建议吗? 或者只需给我提供一些最佳实践的链接。
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)。这使得此接口的消费者的代码更加简单:
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
构造函数的类中。还可以轻松创建一个写入控制台的实现,或者用于单元测试的虚拟实现,如下面的清单所示: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接口上唯一的方法传递。这些扩展方法本身不包含任何自我Volatile Behavior,因此不会影响可测试性。如果您愿意,您可以轻松地对它们进行测试,并且它们成为使用者代码的一部分;而不是抽象的一部分。
这不仅允许扩展方法在不需要更改抽象的情况下发展,而且当存根/模式化记录器时,始终执行扩展方法和LogEntry构造函数。这在运行测试套件时对于调用记录器的正确性给予了更大的保障。我曾经多次以这种方式自掘坟墓,在我的单元测试中,对使用的第三方记录器抽象的调用成功了,但在生产中执行时仍然失败了。
单成员接口也使得测试更容易;拥有许多成员的抽象使得创建实现(如模拟、适配器和装饰器)变得困难。
这样做几乎没有任何需要使用日志门面(或任何其他库)提供的静态抽象的需要。
即使有了这个ILogger
设计,仍然建议以这样的方式设计您的应用程序,只有少数类需要依赖于您的ILogger
抽象。这个答案更详细地讨论了这个问题。
IsEnabled(LogLevel)
,出于性能原因您可能希望这样做我使用了来自https://github.com/uhaciogullari/NLog.Interface的小接口包装器+适配器,该软件包也可以通过NuGet获得:
PM> Install-Package NLog.Interface
public interface ILogger
{
void LogInformation(string msg);
void LogError(string error);
}
LogEntry
的依赖,因此也依赖于LoggingEventType
。ILogger
实现必须处理这些LoggingEventTypes
,可能通过case/switch
,这是一种代码异味。为什么要隐藏LoggingEventTypes
的依赖性?实现必须无论如何处理日志级别,因此最好明确说明实现应该做什么,而不是将其隐藏在具有通用参数的单个方法后面。 - DharmaTurtleICommand
接口,它有一个 Handle
方法,需要传入一个 object
对象。实现类必须通过 case/switch
来处理所有可能的类型,以满足接口的约定。这并不理想。不要隐藏那些必须被处理的依赖项。而是应该有一个明确表述期望的接口:“我希望所有记录器都能处理警告、错误、致命等级的日志信息”。与其期望所有的记录器都能够“包含”警告、错误、致命等级的日志信息,还不如声明期望的接口更为优选。 - DharmaTurtleLoggingEventType
应该被称为 LoggingEventLevel
,因为类型是类,在面向对象编程中应该这样编码。对我来说,不使用接口方法与不使用相应的 enum
值之间没有区别。相反,使用 ErrorLoggger : ILogger
、InformationLogger : ILogger
,其中每个记录器都定义了自己的级别。然后 DI 需要注入所需的记录器,可能通过一个键(枚举)进行,但是这个键不再是接口的一部分。(现在你遵循了 SOLID 原则)。 - Wouter与其编写自己的门面,您可以使用Castle Logging Services或Simple Logging Façade。
两者都包含适配器以支持NLog和Log4net。