Хорошая практика заключается в том, что логгер как одноэлементный?
У меня была привычка передавать регистратор в конструктор, например:
public class OrderService : IOrderService {
public OrderService(ILogger logger) {
}
}
Но это довольно раздражает, поэтому я использовал это свойство в течение некоторого времени:
private ILogger logger = NullLogger.Instance;
public ILogger Logger
{
get { return logger; }
set { logger = value; }
}
Это тоже раздражает - это не сухо, мне нужно повторить это в каждом классе. Я мог бы использовать базовый класс, но опять же - я использую класс Form, поэтому понадобится FormBase и т.д.
Поэтому я думаю, что было бы недостатком синглтона с выставленным ILogger, поэтому кто-то знал бы, где взять регистратор:
Infrastructure.Logger.Info("blabla");
UPDATE: Как правильно заметил Мерлин, я должен упомянуть, что в первом и втором примерах я использую DI.
Ответы
Ответ 1
Это тоже раздражает - это не DRY
Это правда. Но есть только так много, что вы можете сделать для сквозной заботы, которая пронизывает каждый тип, который у вас есть. Вы должны использовать логгер везде, поэтому у вас должно быть свойство на этих типах.
Итак, посмотрим, что мы можем с этим сделать.
Singleton
Синглтоны ужасны <flame-suit-on>
.
Я рекомендую придерживаться инъекции свойств, как вы это сделали со вторым примером. Это лучший факторинг, который вы можете сделать, не прибегая к магии. Лучше иметь явную зависимость, чем скрывать ее с помощью одноэлементного.
Но если синглоты сэкономят вам значительное время, включая все рефакторинги, которые вам когда-либо придется делать (время хрустального шара!), я полагаю, вы могли бы жить с ними. Если когда-либо было использование Singleton, возможно, это и есть. Имейте в виду, что стоимость, если вы когда-либо захотите изменить свой разум, будет примерно такой же высокой, как и она.
Если вы это сделаете, ознакомьтесь с ответами других людей, используя шаблон Registry
(см. описание), и те, кто регистрирует (сбрасывается ) singleton factory, а не одиночный экземпляр журнала.
Существуют и другие альтернативы, которые могут работать так же хорошо, без какого-либо компромисса, поэтому сначала вы должны проверить их.
фрагменты кода Visual Studio
Вы можете использовать фрагменты кода Visual Studio, чтобы ускорить вход этого повторяющегося кода. Вы можете ввести что-то вроде logger
tab, и код будет волшебным образом отображаться для вас.
Использование AOP для DRY off
Вы можете устранить немного этого кода инъекции свойств, используя структуру AOP-ориентированного программирования (AOP), такую как PostSharp, чтобы автоматически генерировать некоторые его.
Это может выглядеть примерно так, когда вы закончите:
[InjectedLogger]
public ILogger Logger { get; set; }
Вы также можете использовать свой пример кода отслеживания методов для автоматического отслеживания ввода и выхода кода метода, что может исключить необходимость добавления некоторых из свойства логгера вместе. Вы можете применить атрибут на уровне класса или в пространстве имен:
[Trace]
public class MyClass
{
// ...
}
// or
#if DEBUG
[assembly: Trace( AttributeTargetTypes = "MyNamespace.*",
AttributeTargetTypeAttributes = MulticastAttributes.Public,
AttributeTargetMemberAttributes = MulticastAttributes.Public )]
#endif
Ответ 2
Я помещаю экземпляр журнала в контейнер для инъекций зависимостей, который затем вводит регистратор в классы, которые нуждаются в нем.
Ответ 3
Хороший вопрос. Я считаю, что в большинстве проектов logger - одноэлементный.
Некоторые идеи просто приходят мне в голову:
- Используйте ServiceLocator (или другой контейнер Зависимость впрыска, если вы уже используете какой-либо), который позволяет вам share logger через службы/классы, таким образом вы можете создать экземпляр регистратора или даже нескольких разных регистраторов и поделиться через ServiceLocator, что, очевидно, будет одноэлементным, своего рода Inversion of Control. Такой подход дает вам большую гибкость в отношении процесса создания экземпляра журнала и процесса инициализации.
- Если вам нужен регистратор почти везде - внедрите методы расширения для типа
Object
, чтобы каждый класс мог вызвать методы журнала, такие как LogInfo()
, LogDebug()
, LogError()
Ответ 4
Синтаксис - хорошая идея. Еще лучшая идея - использовать шаблон Registry, который дает немного больше контроля над созданием экземпляра. По-моему, одноэлементный шаблон слишком близок к глобальным переменным. При создании или повторном использовании объекта обработки реестра есть место для будущих изменений в правилах создания экземпляров.
Сам реестр может быть статическим классом, чтобы дать простой синтаксис для доступа к журналу:
Registry.Logger.Info("blabla");
Ответ 5
Простой синглтон - это не очень хорошая идея. Это затрудняет замену регистратора. Я использую фильтры для своих регистраторов (некоторые "шумные" классы могут регистрировать только предупреждения/ошибки).
Я использую одноэлементный шаблон в сочетании с шаблоном прокси для журнала factory:
public class LogFactory
{
private static LogFactory _instance;
public static void Assign(LogFactory instance)
{
_instance = instance;
}
public static LogFactory Instance
{
get { _instance ?? (_instance = new LogFactory()); }
}
public virtual ILogger GetLogger<T>()
{
return new SystemDebugLogger();
}
}
Это позволяет мне создать FilteringLogFactory
или просто SimpleFileLogFactory
без изменения кода (и, следовательно, соблюдая принцип Open/Closed).
Расширение образца
public class FilteredLogFactory : LogFactory
{
public override ILogger GetLogger<T>()
{
if (typeof(ITextParser).IsAssignableFrom(typeof(T)))
return new FilteredLogger(typeof(T));
return new FileLogger(@"C:\Logs\MyApp.log");
}
}
И использовать новый factory
// and to use the new log factory (somewhere early in the application):
LogFactory.Assign(new FilteredLogFactory());
В вашем классе, который должен регистрироваться:
public class MyUserService : IUserService
{
ILogger _logger = LogFactory.Instance.GetLogger<MyUserService>();
public void SomeMethod()
{
_logger.Debug("Welcome world!");
}
}
Ответ 6
В .NET есть книга "Инъекция зависимостей". Основываясь на том, что вам нужно, вы должны использовать перехват.
В этой книге есть диаграмма, помогающая решить, использовать ли инъекцию конструктора, инъекцию свойств, инъекцию метода, контекст окружения, перехват.
То, как одна из причин использует эту диаграмму:
- У вас есть зависимость или она нужна? - Нужна она
- Является ли это сквозной проблемой? - Да
- Вам нужен ответ? - Нет
Использовать перехват
Ответ 7
Если вы хотите посмотреть на хорошее решение для ведения журнала, я предлагаю вам взглянуть на движок Google с помощью python, где ведение журнала так же просто, как import logging
, а затем вы можете просто logging.debug("my message")
или logging.info("my message")
, который действительно сохраняет его как просто, как и должно быть.
У Java не было хорошего решения для регистрации, т.е. log4j следует избегать, поскольку он практически заставляет вас использовать синглтоны, которые, как сказано здесь, "ужасны", и у меня был ужасный опыт с попыткой сделать вывод журнала одним и тем же журналом выражение только один раз, когда я подозреваю, что причиной двойного ведения журнала было то, что у меня есть один Singleton объекта ведения журнала в двух загрузчиках классов в одной и той же виртуальной машине (!)
Я прошу прощения за то, что вы не так специфичны для С#, но из того, что я видел, решения с С# выглядят похожими на Java, где у нас был log4j, и мы также должны сделать его одиночным.
Вот почему мне действительно понравилось решение с GAE/python, это так просто, как может быть, и вам не нужно беспокоиться о загрузчики классов, получение отчета о двойном протоколировании или любой шаблон дизайна вообще.
Я надеюсь, что некоторые из этих сведений могут иметь отношение к вам, и я надеюсь, что вы хотите взглянуть на мое решение для регистрации, которое я рекомендую, а не на то, что я запугиваю вопрос о том, насколько сильно Синглтон подозревается из-за невозможности иметь реальный синглтон, когда он должен запускаться в нескольких загрузчиках классов.
Ответ 8
Другим решением, которое я лично считаю самым простым, является использование статического класса Logger. Вы можете вызвать его из любого метода класса без необходимости изменения класса, например. добавьте вложение свойств и т.д. Его довольно простой и простой в использовании.
Logger::initialize ("filename.log", Logger::LEVEL_ERROR); // only need to be called once in your application
Logger::log ("my error message", Logger::LEVEL_ERROR); // to be used in every method where needed
Ответ 9
Учитывая, что в наши дни я использую общий логгер (ILogger для типа T) - ответ нет;)