Лучшая практика обертки Logger

Я хочу использовать nlogger в своем приложении, возможно, в будущем мне нужно будет изменить систему регистрации. Поэтому я хочу использовать лесозаготовительный фасад.

Знаете ли вы какие-либо рекомендации для существующих примеров, как написать эти? Или просто дайте мне ссылку на лучшую практику в этой области.

6 ответов

Решение

Раньше я использовал журналирование фасадов, таких как 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://www.nuget.org/packages/Microsoft.Extensions.Logging/ ( как отметил Джулиан). Большинство каркасов может быть использовано с этим.

Определение собственного интерфейса, как объяснено в ответе Стивена, подходит для простых случаев, но в нем пропущено несколько вещей, которые я считаю важными:

  • Структурированное ведение журнала и деструктуризация объектов (обозначение @ в Serilog и NLog)
  • Задержка построения / форматирования строки: поскольку она принимает строку, она должна оценивать / форматировать все при вызове, даже если в конце событие не будет зарегистрировано, потому что оно ниже порогового значения (стоимость производительности, см. Предыдущий пункт)
  • Условные проверки типа IsEnabled(LogLevel) что вы могли бы хотеть, по причинам производительности еще раз

Вероятно, вы можете реализовать все это в своей собственной абстракции, но в этот момент вы будете заново изобретать колесо.

Я использовал небольшой интерфейсный упаковщик + адаптер из https://github.com/uhaciogullari/NLog.Interface который также доступен через NuGet:

PM> Install-Package NLog.Interface 

Отличное решение этой проблемы появилось в форме проекта LibLog.

LibLog - это абстракция регистрации со встроенной поддержкой основных регистраторов, в том числе Serilog, NLog, Log4net и Enterprise logger. Он устанавливается через менеджер пакетов NuGet в целевую библиотеку в виде исходного файла (.cs) вместо ссылки на DLL. Такой подход позволяет включать абстракцию журналирования, не заставляя библиотеку принимать внешнюю зависимость. Это также позволяет автору библиотеки включать ведение журнала, не вынуждая приложение-потребитель явно предоставлять в библиотеку средства ведения журнала. LibLog использует рефлексию, чтобы выяснить, какой конкретный регистратор используется, и подключить его без какого-либо явного кода подключения в библиотечных проектах.

Итак, LibLog - отличное решение для входа в библиотечные проекты. Просто обратитесь и настройте конкретный логгер (Serilog для победы) в вашем основном приложении или сервисе и добавьте LibLog в свои библиотеки!

Вообще я предпочитаю создавать интерфейс как

public interface ILogger
{
 void LogInformation(string msg);
 void LogError(string error);
}

и во время выполнения я внедряю конкретный класс, который реализуется из этого интерфейса.

Вместо того, чтобы писать свой собственный фасад, вы можете использовать Castle Logging Services или Simple Logging Façade.

Оба включают адаптеры для NLog и Log4net.

С 2015 года вы также можете использовать .NET Core Logging, если вы создаете основные приложения.NET.

Пакет для подключения NLog:

Другие вопросы по тегам