Раньше я использовал фасады журналирования, такие как Common.Logging (даже для того, чтобы скрыть мою собственную библиотеку CuttingEdge.Logging ), но в настоящее время я использую шаблон внедрения зависимостей, и это позволяет мне скрывать логгеры за моей собственной (простой) абстракцией, которая придерживается обоих зависимостей. Принцип инверсии и принцип разделения интерфейсов(ISP), потому что у него один член и потому что интерфейс определяется моим приложением; не внешняя библиотека. Сведите к минимуму знания основных частей вашего приложения о существовании внешних библиотек, тем лучше; даже если у вас нет намерения когда-либо заменять вашу библиотеку журналов. Жесткая зависимость от внешней библиотеки затрудняет тестирование вашего кода и усложняет ваше приложение с помощью API, который никогда не был разработан специально для вашего приложения.
Вот как часто выглядит абстракция в моих приложениях:
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 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));
}
}
Поскольку интерфейс содержит только один метод, вы можете легко создать ILogger
реализацию , что прокси для log4net , к Serilog , Microsoft.Extensions.Logging , NLog или любой другой библиотеке лесозаготовительной и настроить DI контейнер , чтобы ввести его в классах , которые имеют ILogger
в своем конструктор.
Обратите внимание, что наличие статических методов расширения поверх интерфейса с одним методом сильно отличается от наличия интерфейса с множеством членов. Методы расширения - это просто вспомогательные методы, которые создают LogEntry
сообщение и передают его через единственный метод ILogger
интерфейса. Методы расширения становятся частью кода потребителя; не часть абстракции. Это не только позволяет методам расширения развиваться без необходимости изменения абстракции, методов расширения иLogEntry
конструктор всегда выполняется, когда используется абстракция регистратора, даже если этот регистратор заглушен / имитируется. Это дает больше уверенности в правильности вызовов регистратора при запуске в комплекте тестов. Однокомпонентный интерфейс также значительно упрощает тестирование; Наличие абстракции с множеством членов затрудняет создание реализаций (таких как макеты, адаптеры и декораторы).
Когда вы это делаете, вряд ли когда-нибудь понадобится какая-то статическая абстракция, которую могут предложить фасады журналирования (или любая другая библиотека).