Я хочу использовать в своем приложении nlogger, возможно, в будущем мне нужно будет изменить систему ведения журналов. Поэтому я хочу использовать фасад для вырубки.
Вы знаете какие-нибудь рекомендации по написанию существующих примеров? Или просто дайте мне ссылку на лучшую практику в этой области.
Ответы:
Раньше я использовал фасады журналирования, такие как Common.Logging (даже для того, чтобы скрыть мою собственную библиотеку CuttingEdge.Logging ), но в настоящее время я использую шаблон внедрения зависимостей, и это позволяет мне скрывать логгеры за моей собственной (простой) абстракцией, которая придерживается обоих зависимостей. Принцип инверсии и принцип разделения интерфейсов(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. }
Поскольку интерфейс содержит только один метод, вы можете легко создать
ILogger
реализацию , что прокси для log4net , к Serilog , Microsoft.Extensions.Logging , NLog или любой другой библиотеке лесозаготовительной и настроить DI контейнер , чтобы ввести его в классах , которые имеютILogger
в своем конструктор.Обратите внимание, что наличие статических методов расширения поверх интерфейса с одним методом сильно отличается от наличия интерфейса с множеством членов. Методы расширения - это просто вспомогательные методы, которые создают
LogEntry
сообщение и передают его через единственный методILogger
интерфейса. Методы расширения становятся частью кода потребителя; не часть абстракции. Это не только позволяет методам расширения развиваться без необходимости изменения абстракции, методов расширения иLogEntry
конструктор всегда выполняется, когда используется абстракция регистратора, даже если этот регистратор заглушен / имитируется. Это дает больше уверенности в правильности вызовов регистратора при запуске в комплекте тестов. Однокомпонентный интерфейс также значительно упрощает тестирование; Наличие абстракции с множеством членов затрудняет создание реализаций (таких как макеты, адаптеры и декораторы).Когда вы это делаете, вряд ли когда-нибудь понадобится какая-то статическая абстракция, которую могут предложить фасады журналирования (или любая другая библиотека).
источник
Я использовал небольшую оболочку интерфейса + адаптер из https://github.com/uhaciogullari/NLog.Interface, который также доступен через NuGet :
источник
На данный момент лучше всего использовать пакет Microsoft.Extensions.Logging ( как указал Джулиан ). С этим можно использовать большинство фреймворков журналирования.
Определение собственного интерфейса, как объяснено в ответе Стивена, подходит для простых случаев, но при этом упускается несколько вещей, которые я считаю важными:
IsEnabled(LogLevel)
которым вы можете захотеть, еще раз по соображениям производительностиВы, вероятно, сможете реализовать все это в своей собственной абстракции, но в этот момент вы будете изобретать колесо.
источник
Обычно я предпочитаю создавать интерфейс вроде
public interface ILogger { void LogInformation(string msg); void LogError(string error); }
а во время выполнения я добавляю конкретный класс, который реализуется из этого интерфейса.
источник
LogWarning
иLogCritical
методы , и все их перегрузку. При этом вы нарушите принцип разделения интерфейса . Предпочитайте определятьILogger
интерфейс однимLog
методом.LogEntry
и, следовательно, зависимость отLoggingEventType
. ОднакоILogger
реализацияLoggingEventTypes
, вероятноcase/switch
, должна иметь дело с этим , что является запахом кода . Зачем скрыватьLoggingEventTypes
зависимость? Реализация в любом случае должна обрабатывать уровни ведения журнала , поэтому было бы лучше явно указать, что должна делать реализация, а не скрывать это за одним методом с общим аргументом.ICommand
которого есть,Handle
который принимаетobject
.case/switch
Для выполнения контракта интерфейса реализации должны иметь более возможные типы. Это не идеально. У вас нет абстракции, скрывающей зависимость, которую нужно обрабатывать в любом случае. Вместо этого имейте интерфейс, который ясно заявляет, что ожидается: «Я ожидаю, что все регистраторы будут обрабатывать предупреждения, ошибки, критические ошибки и т. Д.». Это предпочтительнее, чем "Я ожидаю, что все регистраторы будут обрабатывать сообщения, которые включают предупреждения, ошибки, критические ошибки и т. Д."LoggingEventType
их следует называть,LoggingEventLevel
поскольку типы являются классами и должны кодироваться как таковые в ООП. Для меня нет разницы между неиспользованием метода интерфейса и отказом от соответствующегоenum
значения. Вместо этого используйтеErrorLoggger : ILogger
,InformationLogger : ILogger
где каждый регистратор определяет свой уровень. Затем DI необходимо ввести необходимые регистраторы, возможно, через ключ (перечисление), но этот ключ больше не является частью интерфейса. (Теперь вы ТВЕРДЫЙ).Прекрасное решение этой проблемы появилось в виде проекта LibLog .
LibLog - это абстракция журналирования со встроенной поддержкой основных журналов, включая Serilog, NLog, Log4net и Enterprise logger. Он устанавливается через диспетчер пакетов NuGet в целевую библиотеку как файл источника (.cs) вместо ссылки .dll. Такой подход позволяет включить абстракцию журналирования, не заставляя библиотеку принимать внешние зависимости. Это также позволяет автору библиотеки включать ведение журнала, не заставляя приложение-потребитель явно предоставлять средство ведения журнала библиотеке. LibLog использует отражение, чтобы выяснить, какой конкретный регистратор используется, и подключиться к нему без какого-либо явного кода подключения в проектах библиотеки.
Итак, LibLog - отличное решение для ведения журнала в библиотечных проектах. Просто создайте ссылку и настройте конкретный регистратор (Serilog для победы) в своем основном приложении или службе и добавьте LibLog в свои библиотеки!
источник
Вместо того чтобы писать собственный фасад, вы можете использовать Castle Logging Services или Simple Logging Façade .
Оба включают адаптеры для NLog и Log4net.
источник
С 2015 года вы также можете использовать .NET Core Logging, если вы создаете основные приложения .NET.
Пакет для подключения NLog:
источник