Как мне спроектировать ведение журнала в моем приложении?

Итак, я провел много исследований по этому поводу и не нашел ответов, где бы я сказал «да, ЭТО». Я надеюсь, что эрудированная толпа StackOverflow сможет мне помочь.

Я столкнулся с этой проблемой в нескольких разных сценариях. Скажем, у меня есть приложение C#, и есть важные вещи, которые я хочу регистрировать.

public class MyClass
{
    ... 

    public void ImportantMethod()
    {
        DoInterestingThing();

        var result = SomethingElseImportant();
        if (result == null)
        {
            logger.Log("I wasn't expecting that. No biggie.");
            return;
        }

        MoreInterestingStuff(); 
}

Меня интересует откуда взять logger.

Как я вижу, у меня есть несколько вариантов.

  1. Внедрите его в MyClass в конструкторе.
  2. Получите его, используя глобально доступный локатор сервисов.
  3. Используйте декораторы методов и АОП, чтобы ведение журнала выполнялось за меня.

Ни один из этих вариантов не кажется отличным. # 3 выглядит невозможным, поскольку я веду регистрацию прямо в середине своей бизнес-логики, а не просто выполняю простую трассировку вызовов моих методов, входных параметров и/или исключений. # 2, хотя просто кажется, что было бы очень сложно провести модульное тестирование. Конечно, я бы хотел протестировать все. № 1, хотя он и работал бы нормально, загромождает всю мою бизнес-логику объектами протоколирования, которые не имеют ничего общего с самими бизнес-объектами.

Любые альтернативные идеи или мысли по одному из вариантов выше? Большое спасибо!

РЕДАКТИРОВАТЬ: просто для ясности, я уже знаю, как делать DI (я использую Unity), и я уже знаю хорошую структуру ведения журнала (я использую log4net). Просто интересно, как использовать ведение журнала в архитектурном смысле в приложении самым разумным образом.


* ИЗМЕНИТЬ *

Я отметил ответ Марка Симана как решение. Я просмотрел свое приложение и обнаружил, что большинство моих вызовов ведения журнала делают то же самое, что и декоратор. А именно, запишите запись в метод, любые выброшенные исключения и возвращаемые значения выхода.

В некоторых случаях мне по-прежнему нужно было вести журнал непосредственно внутри метода. Примером может быть то, что я хочу быстро потерпеть неудачу в методе, который ничего не возвращает, но не throw Исключение. В этих случаях у меня есть синглтон, который содержит ссылку LogProvider, которая, в свою очередь, извлечет именованный экземпляр журнала. Код выглядит примерно так:

private ILog logger = LogProviderFactory.Instance.GetLogger(typeof(Foo));

LogProviderFactory имеет метод SetProvider, который позволяет вам заменить синглтон. Итак, в модульном тестировании я могу сделать:

// LogProviderFactory.Instance now is our mock
LogProviderFactory.SetProvider(MockLogProvider);

Декоратор ведения журнала использует тот же LogProvider, что и синглтон (который он получает путем внедрения), поэтому ведение журнала унифицировано во всей системе.

Так что на самом деле конечным решением был в основном вариант № 3 и гибрид № 2 (где это шаблон локатора службы, но служба «внедряется» в локатор).

АОП

Что касается «аспектно-ориентированного программирования», то я был немного разочарован ограничениями языка. Надеемся, что в будущих выпусках к АОП будут относиться как к первоклассному гражданину.

  • Я попробовал PostSharp, но не смог правильно запустить его на своей машине. Кроме того, было большим ограничением, что вам нужно было установить PostSharp в вашей системе, чтобы использовать его (в отличие от простого вызова dll, который поставляется с решением или чем-то подобным).
  • Я использовал LinFu и смог частично заставить его работать. Однако в нескольких случаях он взрывался. Новая версия 2.0 почти не документирована, так что это было препятствием.
  • Однако перехват интерфейса с Unity, кажется, работает хорошо из коробки. Мне повезло, что большинство вещей, которые я хотел регистрировать, находились в классах, реализующих интерфейсы.

comment
Всегда внимательно смотрите, нужно ли вам что-то регистрировать, или просто быстро потерпеть неудачу, вызвав исключение. Быстрая помощь чаще оказывается выходом, чем вы ожидаете.   -  person Steven    schedule 15.07.2011
comment
Хороший вопрос Стивен! В моем последнем приложении мне нужно было внести некоторые записи в журнал, которые не обязательно были связаны с исключениями/ошибками. Такие вещи, как полученное новое сообщение или добавленный пользователь. Не отладочные заявления как таковые, а информационный материал о выполнении приложения для ведения учета.   -  person JonH    schedule 15.07.2011


Ответы (3)


Используйте логирование Декоратор.

person Mark Seemann    schedule 15.07.2011
comment
В принципе, это кажется хорошей идеей. Однако на практике у меня может быть 20 различных объектов или служб, для которых необходимо ведение журнала. В этом случае я бы фактически удвоил это число. LoggingArticleManager, LoggingOrderService? Не кажется очень эффективным. - person JonH; 15.07.2011
comment
@Onisemus: Вот тут и начинается перехват. - person Steven; 15.07.2011
comment
+1 @Onisemus: я не верю, что вы правильно прочитали связанные статьи, и предлагаю перечитать, чтобы получить точку перехвата - person Ruben Bartelink; 15.07.2011
comment
Ах, хорошо, извините, я не заметил, что там были две разные ссылки! После прочтения это имеет смысл. Похоже, что перехват = АОП (во всяком случае, для наших целей). АОП отлично справляется с тем, что позволяет мне вести журнал до вызова метода и после вызова метода. Но как насчет середины, как показывает мой пример? Разве это не было бы невозможно с декоратором ведения журнала? - person JonH; 15.07.2011
comment
Если вам нужно войти в середине метода, метод делает слишком много :) - person Mark Seemann; 15.07.2011
comment
@MarSeemann, хорошая мысль :). В целом я с вами полностью согласен. Действительно ли нет никаких разумных ситуаций, в которых мы хотели бы войти в середине метода? Если есть... как лучше это сделать? - person JonH; 16.07.2011
comment
Если есть, то единственным способом сделать это является внедрение зависимости от ведения журнала, но я не думаю, что существует слишком много случаев, когда вы должны вести журнал изнутри метода. Но никогда не говори никогда :) - person Mark Seemann; 16.07.2011
comment
Регистрация внутри метода вполне разумна, это просто зависит от того, каковы тогда сценарий и обоснование. - person Adrian K; 16.07.2011

Два бита:

(1) – готовая структура ведения журналов.

Некоторым нравится Log4Net, но я фанат EntLibs. Это делает тяжелую работу с точки зрения фактического ведения журнала. Такие инструменты, как EntLibs, позволят вам регистрировать различные типы репозиториев журналов (база данных, очередь сообщений, скользящий текстовый файл и т. д.). Они также позволят вам войти в разные экземпляры на основе категорий и так далее. Обычно они легко настраиваются.

(2) — настраиваемые классы, включающие структуру ведения журналов.

Таким образом, «логгер» — это то, что вы пишете, и он вызывает структуру ведения журнала для фактического ведения журнала.

Мне нравится этот подход по нескольким причинам:

  • Вы отделяете структуру ведения журналов (№ 1) от остальной части вашего приложения, поскольку пользовательские оболочки помещаются в отдельную сборку.
  • Написав свой собственный Logging API, вы можете определить сигнатуры методов, которые соответствуют вашим потребностям, и вы можете расширить их.
  • Если вы работаете в команде, вы можете сделать сигнатуры методов очень простыми в использовании, чтобы ни у кого не было оснований говорить, что использование ведения журнала было слишком сложным.
  • Это обеспечивает согласованность ведения журнала. Это также упрощает поиск кода для «незаконного» кода, который записывает в файлы, консоль или журнал событий, так как в вашем журнале не будет ничего (все это находится в структуре).
  • Написав специальные пользовательские классы для каждого уровня, вы можете предварительно заполнить множество данных за кулисами, упрощая жизнь тем, кто пишет фактический код приложения. Вы можете установить серьезность, приоритет, идентификаторы событий по умолчанию, категории и многое другое.
  • Он хорошо масштабируется с точки зрения сложности и роста приложений; это может показаться тяжелым для небольших приложений, но у вас будет достаточно места, если со временем он начнет расти.

Вот пример класса информационного ведения журнала в проекте, над которым я работал. У него есть множество легко вызываемых публичных методов и один приватный метод, который вызывает фреймворк (ConcreteLogInformation).

public static void LogInformation(string title, string message)

public static void LogInformation(string title, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, int eventId, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, string message, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, string message, int eventId)

public static void LogInformation(string title, string message, int eventId, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, string message, int eventId, string category)

public static void LogInformation(string title, string message, int eventId, Dictionary<string, object> extendedProperties, string category)

private static void ConcreteLogInformation(string title, string message, int eventId, Dictionary<string, object> extendedProperties, string category)
person Adrian K    schedule 15.07.2011
comment
Я согласен со всем, что вы говорите, кроме последней части, где вы используете статические методы. Использование статических методов значительно усложняет тестирование. Интерфейс ILogger лучше внедрить в конструктор типов, которым нужен логгер. Подход, который хорошо сработал для меня, состоит в том, чтобы иметь один метод Log(LogEntry) для этого интерфейса и иметь набор методов расширения (таких как Log(string) и Log(Exception)), которые вызывают метод интерфейса Log(LogEntry). (еще +1 за ваш ответ). - person Steven; 15.07.2011
comment
@Стивен - да, звучит круто. Я должен признать, что у меня никогда не было достаточно мотивации, чтобы использовать DI для ведения журнала (если ваш DI дает сбой - как вы регистрируете?), но это все же хорошая идея для рассмотрения. - person Adrian K; 15.07.2011
comment
Что именно вы имеете в виду, если ваш DI терпит неудачу? - person Steven; 15.07.2011
comment
Использование DI - инверсия зависимостей (фреймворк/подсистема). Допустим, вы выполняете новое развертывание, оно основано на конфигурации, конфигурация неверна — ваше ведение журнала не будет работать и может затруднить диагностику проблемы. Другое дело, что вы могли бы рассматривать использование DI как усложнение общей системы ведения журнала - что касается подсистем ведения журнала, я думаю, что принцип KISS (Keep It Simple, Stupid) хорош - предполагая, что сохранение простоты = надежность. - person Adrian K; 15.07.2011
comment
Не должно быть возможности иметь такую ​​конфигурацию промаха в вашем контейнере DI. Модульные тесты должны гарантировать правильность конфигурации (эти тесты обычно очень просты). Неправильно настроенная система ведения журнала, конечно, все еще возможна, но это не относится к DI. Использование DI — это, конечно, дополнительный уровень абстракции, но мой опыт показывает, что это значительно упрощает разработку. На самом деле, если ваше приложение будет зависеть от интерфейса ILogger вместо конкретной подсистемы ведения журналов, вряд ли можно считать перепроектированием, и я, безусловно, рассмотрю этот KISS. - person Steven; 15.07.2011
comment
Спасибо за ответ. Если я понял суть того, что вы говорите, вы рекомендуете использовать статические методы в статическом классе где-нибудь для входа в приложение? Это похоже на вариант № 2 на самом деле. В обоих случаях мы получаем доступ к функциям ведения журнала статически, что делает очень сложным (или невозможным, в зависимости от того, как вы его настроили) модульное тестирование. Вы можете использовать обычный DI, но я объяснил свое нежелание делать это в описании. - person JonH; 15.07.2011
comment
Не уверен, что вижу различие, которое делает статические вызовы классов ведения журнала такими плохими. Вы можете протестировать регистратор независимо от другой логики. Если это все еще проблема для вас, вы можете каким-то образом дополнить DI, есть несколько способов сделать это. - person Adrian K; 16.07.2011

В разделе Использование контекста запроса в методе Factory или Factory для контекстной привязки ссылки Документы Ninject Contetual Binding У меня есть пример использования вашего контейнера для внедрения соответствующего регистратора для вашего класса, выполнив (на Ninjectese):

Bind<ILog>().ToMethod( context => LogFactory.CreateLog( context.Request.Target.Type ) );

Что касается трассировки типов, то в статье о перехвате Марка описывается лучший подход.

И могу ли я попросить вас еще раз, чтобы вы внимательно прочитали цитируемые статьи @Mark Seemann, прежде чем просто отбрасывать их без голосования.

person Ruben Bartelink    schedule 15.07.2011
comment
Извините, не увидел первую ссылку на статью @Mark Seemann. Однако это не похоже на мой пример. АОП/перехват отлично подходят для трассировки, но метод из примера делает нечто большее. Я вызываю функции ведения журнала в середине своей бизнес-логики — меня интересует больше, чем просто то, что происходит до и после вызова метода. Статья Ninject отражает то, как это делает log4net, и аналогична варианту № 2. Мне это нравится ... но это сложно для модульного тестирования. С реализацией локатора сервисов, как мне указать фиктивный регистратор в моих модульных тестах? - person JonH; 15.07.2011
comment
@Onisemus: Понятия не имею, о каком SL ты говоришь. Если вы на самом деле прочитали это, вы увидите, что есть Constructor Injection интерфейса ILogger, который можно тестировать/мокабельно. Дело в том, что ваш инструмент DI может позаботиться о слабой связи, и вам нужен только один Binding для 20 классов с ведением журнала. - person Ruben Bartelink; 16.07.2011
comment
О, я прочитал это. Было два примера: один — внедрение конструктора, а другой — фабричный метод. Я имел в виду заводской метод. Внедрение конструктора имеет большой смысл, и я понимаю, почему оно так популярно. Просто такие сквозные проблемы, как ведение журнала, загромождают домен. Я не решаюсь его использовать - возможно, я мог бы внедрить ILog в половину классов в домене. - person JonH; 10.08.2011
comment
@Onisemus: внедрение свойств, связанное с одной привязкой, как в моем ответе здесь, может решить эту проблему. Но главное, что делается, заключается в том, что если ваши вещи правильно разделены, вашему коду не нужно делать столько журналов — вы можете внедрить трассировку того, что вызывает то, что вне ваших доменных классов через АОП и / или мини- Возможности АОП вашего контейнера внедрения зависимостей. Затем для остальных вещей, которые должны быть довольно редкими, позвольте CR/R# генерировать ctors, которые выявляют тот факт, что этот доменный класс действительно считает, что у него есть важные вещи для регистрации (в отличие от вещей, которые вы бы замусорили повсюду) - person Ruben Bartelink; 10.08.2011
comment
Это хороший момент — позволить средствам АОП обрабатывать трассировку, а затем вводить журналирование там, где это абсолютно необходимо. Как вы говорите, если важно войти в систему вне трассировки, кажется разумным ввести зависимость ведения журнала в этот момент. Как вы относитесь к внедрению регистратора по сравнению с его получением с помощью какого-либо статического метода (местоположение службы, какой-то гибрид этого) для этих редких случаев? Статические методы могут помочь очистить домен... но опять же, возможно, вы могли бы рассмотреть возможность регистрации достаточно конкретной зависимости, чтобы гарантировать внедрение. (и делает эту зависимость очевидной). - person JonH; 10.08.2011
comment
@Onisemus: я бы поместил иерархию как инъекцию ctor, prop inj, локатор сервисов, синглтоны, другие статические методы / глобальные переменные в платье, глобальные переменные. Но ctor инъекция на голову лучше. Как только вы правильно используете DI-контейнер (не SL!), предполагаемые преимущества синглтонов/статиков быстро исчезают. Но, сказав все это, избавьтесь от всего этого набора текста и по умолчанию используйте внедрение ctor и ищите группы зависимостей, путешествующих стаями, которые представляют недостающие абстракции. - person Ruben Bartelink; 11.08.2011