Лучший подход к регистрации для составного приложения?

Я создаю приложение Composite WPF (Prism) с несколькими различными проектами (Shell, модули и т.д.). Я готов выполнить регистрацию, используя Log4Net. Кажется, есть два способа настройки ведения журнала:

  • Пусть проект Shell выполнит все фактические записи. Он получает ссылку на Log4Net, а другие проекты создают комбинированные события, чтобы сообщить Shell, что ему нужно что-то записывать. Эти проекты запускают события только для уровней, где ведение журнала включается в файле приложения app.config оболочки (DEBUG, ERROR и т.д.), Чтобы не ухудшить производительность.

  • Дайте каждому проекту, включая модули, ссылку Log4Net, и пусть проект выполнит собственный журнал в общем файле журнала вместо отправки сообщений в оболочку для ведения журнала.

Какой подход лучше? Или, есть ли другой подход, который я должен рассмотреть? Благодарим за помощь.

Ответ 1

Самый простой способ регистрации в Prism - переопределить свойство LoggerFacade в Bootstrapper. Переопределяя LoggerFacade, вы можете передать экземпляр любого Logger, который вы хотите, с любой необходимой конфигурацией, если регистратор реализует интерфейс ILoggerFacade.

Я нашел следующее для работы достаточно хорошо для ведения журнала (я использую блок ведения журнала Enterprise Libary, но применение чего-то подобного для Log4Net должно быть прямым):

Создайте Boostrapper в своей оболочке:

-My Project
  -Shell Module (add a reference to the Infrastructure project)
    -Bootstrapper.cs

Создайте адаптер ведения журнала в вашем проекте инфраструктуры, то есть:

-My Project
  -Infrastructure Module
    -Adapters
      -Logging
        -MyCustomLoggerAdapter.cs
        -MyCustomLoggerAdapterExtendedAdapter.cs
        -IFormalLogger.cs

Класс MyCustomLoggerAdapter будет использоваться для переопределения свойства LoggerFacade в Bootstrapper. Он должен иметь конструктор по умолчанию, который сообщает все новости.

Примечание: переопределяя свойство LoggerFacade в Bootstrapper, вы предоставляете механизм логирования для Prism, чтобы использовать для регистрации собственных внутренних сообщений. Вы можете использовать этот регистратор во всем приложении, или вы можете расширить регистратор для более полнофункционального регистратора. (см. MyCustomLoggerAdapterExtendedAdapter/IFormalLogger)

public class MyCustomLoggerAdapter : ILoggerFacade
{

    #region ILoggerFacade Members

    /// <summary>
    /// Logs an entry using the Enterprise Library logging. 
    /// For logging a Category.Exception type, it is preferred to use
    /// the EnterpriseLibraryLoggerAdapter.Exception methods."
    /// </summary>
    public void Log( string message, Category category, Priority priority )
    {
        if( category == Category.Exception )
        {
            Exception( new Exception( message ), ExceptionPolicies.Default );
            return;
        }

        Logger.Write( message, category.ToString(), ( int )priority );
    }

    #endregion


    /// <summary>
    /// Logs an entry using the Enterprise Library Logging.
    /// </summary>
    /// <param name="entry">the LogEntry object used to log the 
    /// entry with Enterprise Library.</param>
    public void Log( LogEntry entry )
    {
        Logger.Write( entry );
    }

    // Other methods if needed, i.e., a default Exception logger.
    public void Exception ( Exception ex ) { // do stuff }
}

MyCustomLoggerAdapterExtendedAdapter скрыт от MyCustomLoggerAdapter и может предоставить дополнительные конструкторы для более полноценного регистратора.

public class MyCustomLoggerAdapterExtendedAdapter : MyCustomLoggerAdapter, IFormalLogger
{

    private readonly ILoggingPolicySection _config;
    private LogEntry _infoPolicy;
    private LogEntry _debugPolicy;
    private LogEntry _warnPolicy;
    private LogEntry _errorPolicy;

    private LogEntry InfoLog
    {
        get
        {
            if( _infoPolicy == null )
            {
                LogEntry log = GetLogEntryByPolicyName( LogPolicies.Info );
                _infoPolicy = log;
            }
            return _infoPolicy;
        }
    }

    // removed backing code for brevity
    private LogEntry DebugLog... WarnLog... ErrorLog


    // ILoggingPolicySection is passed via constructor injection in the bootstrapper
    // and is used to configure various logging policies.
    public MyCustomLoggerAdapterExtendedAdapter ( ILoggingPolicySection loggingPolicySection )
    {
        _config = loggingPolicySection;
    }


    #region IFormalLogger Members

    /// <summary>
    /// Info: informational statements concerning program state, 
    /// representing program events or behavior tracking.
    /// </summary>
    /// <param name="message"></param>
    public void Info( string message )
    {
        InfoLog.Message = message;
        InfoLog.ExtendedProperties.Clear();
        base.Log( InfoLog );
    }

    /// <summary>
    /// Debug: fine-grained statements concerning program state, 
    /// typically used for debugging.
    /// </summary>
    /// <param name="message"></param>
    public void Debug( string message )
    {
        DebugLog.Message = message;
        DebugLog.ExtendedProperties.Clear();
        base.Log( DebugLog );
    }

    /// <summary>
    /// Warn: statements that describe potentially harmful 
    /// events or states in the program.
    /// </summary>
    /// <param name="message"></param>
    public void Warn( string message )
    {
        WarnLog.Message = message;
        WarnLog.ExtendedProperties.Clear();
        base.Log( WarnLog );
    }

    /// <summary>
    /// Error: statements that describe non-fatal errors in the application; 
    /// sometimes used for handled exceptions. For more defined Exception
    /// logging, use the Exception method in this class.
    /// </summary>
    /// <param name="message"></param>
    public void Error( string message )
    {
        ErrorLog.Message = message;
        ErrorLog.ExtendedProperties.Clear();
        base.Log( ErrorLog );
    }

    /// <summary>
    /// Logs an Exception using the Default EntLib Exception policy
    /// as defined in the Exceptions.config file.
    /// </summary>
    /// <param name="ex"></param>
    public void Exception( Exception ex )
    {
        base.Exception( ex, ExceptionPolicies.Default );
    }

    #endregion


    /// <summary>
    /// Creates a LogEntry object based on the policy name as 
    /// defined in the logging config file.
    /// </summary>
    /// <param name="policyName">name of the policy to get.</param>
    /// <returns>a new LogEntry object.</returns>
    private LogEntry GetLogEntryByPolicyName( string policyName )
    {
        if( !_config.Policies.Contains( policyName ) )
        {
            throw new ArgumentException( string.Format(
              "The policy '{0}' does not exist in the LoggingPoliciesCollection", 
              policyName ) );
        }

        ILoggingPolicyElement policy = _config.Policies[policyName];

        var log = new LogEntry();
        log.Categories.Add( policy.Category );
        log.Title = policy.Title;
        log.EventId = policy.EventId;
        log.Severity = policy.Severity;
        log.Priority = ( int )policy.Priority;
        log.ExtendedProperties.Clear();

        return log;
    }

}


public interface IFormalLogger
{

    void Info( string message );

    void Debug( string message );

    void Warn( string message );

    void Error( string message );

    void Exception( Exception ex );

}

В Bootstrapper:

public class MyProjectBootstrapper : UnityBootstrapper
{

  protected override void ConfigureContainer()
  {
    // ... arbitrary stuff

    // create constructor injection for the MyCustomLoggerAdapterExtendedAdapter
      var logPolicyConfigSection = ConfigurationManager.GetSection( LogPolicies.CorporateLoggingConfiguration );
      var injectedLogPolicy = new InjectionConstructor( logPolicyConfigSection as LoggingPolicySection );

      // register the MyCustomLoggerAdapterExtendedAdapter
      Container.RegisterType<IFormalLogger, MyCustomLoggerAdapterExtendedAdapter>(
              new ContainerControlledLifetimeManager(), injectedLogPolicy );

  }

    private readonly MyCustomLoggerAdapter _logger = new MyCustomLoggerAdapter();
    protected override ILoggerFacade LoggerFacade
    {
        get
        {
            return _logger;
        }
    }

}

Наконец, для использования любого регистратора все, что вам нужно сделать, это добавить соответствующий интерфейс к конструктору класса, а UnityContainer будет вводить вам регистратор:

public partial class Shell : Window, IShellView
{
    private readonly IFormalLogger _logger;
    private readonly ILoggerFacade _loggerFacade;

    public Shell( IFormalLogger logger, ILoggerFacade loggerFacade )
    {
        _logger = logger;
        _loggerFacade = loggerFacade

        _logger.Debug( "Shell: Instantiating the .ctor." );
        _loggerFacade.Log( "My Message", Category.Debug, Priority.None );

        InitializeComponent();
    }


    #region IShellView Members

    public void ShowView()
    {
        _logger.Debug( "Shell: Showing the Shell (ShowView)." );
         _loggerFacade.Log( "Shell: Showing the Shell (ShowView).", Category.Debug, Priority.None );
       this.Show();
    }

    #endregion

}

Я не думаю, что вам нужен отдельный модуль для политики ведения журнала. Добавив политики регистрации в свой инфраструктурный модуль, все остальные модули получат требуемые ссылки (если вы добавите модуль инфраструктуры в качестве ссылки на другие модули). И добавив регистратор в Boostrapper, вы можете позволить UnityContainer при необходимости ввести политику ведения журнала.

В проекте CompositeWPF contrib на CodePlex есть простой пример uisng Log4Net.

НТН-х

Ответ 2

Наконец я вернулся к этому, и получается, что ответ действительно довольно прост. В проекте Shell настройте Log4Net как пользовательский регистратор. Документация Prism (февраль 2009 г.) объясняет, как это сделать на стр. 287). Проект Shell - это единственный проект, который нуждается в ссылке на Log4Net. Чтобы получить доступ к регистратору (при условии, что всем модулям передана ссылка на контейнер Prism IOC), просто разрешите ILoggerFacade в контейнере IOC, что даст вам ссылку на ваш собственный регистратор. Передайте сообщение этому регистратору обычным способом.

Таким образом, нет необходимости в каких-либо событиях в Shell, и нет необходимости в том, чтобы модули имели ссылки Log4Net. Святая скумбрия, я люблю контейнеры МОК!

Ответ 3

Проблема с LoggerFacade, предложенная выше, заключается в том, что части без призмы вашего приложения не будут знать об этом. Logger IMHO должен быть более низким и более универсальным, чем только в рамках Composite.

Мое предложение состоит в том, почему бы не просто положиться на стандартный Debug/Trace и реализовать свой собственный TraceListener. Таким образом, он будет хорошо работать для частей Prism/nonPrism. Вы можете достичь желаемого уровня гибкости с этим.

Ответ 4

Наличие отдельных конфигураций регистратора для каждого модуля может стать проблемой при развертывании. Помните, что могущественный пользователь или администратор может полностью изменить цель вашего ведения журнала, перенаправлять на базу данных или на службу агрегированной регистрации в центральном репозитории (например, моя компания один). Если все отдельные модули имеют отдельные конфигурации, пользователь/администратор власти должен повторить конфигурацию для каждого модуля (в каждом файле .config или в каждом разделе модуля в главном app.config) и повторять это каждый раз при изменении местоположения/форматирование. Кроме того, учитывая, что добавление добавляется во время выполнения из конфигурации, и могут быть добавленные вами приложения, о которых вы сейчас ничего не знаете, кто-то может использовать приложение, которое блокирует файл и приводит к конфликту между модулями приложения. Hsving одна конфигурация log4.net упрощает администрирование.

Отдельные модули могут быть настроены отдельно для каждого из них отдельно (например, INFO для уровня DB, ERROR для слоя пользовательского интерфейса). Каждый модуль получит регистратор, запросив его собственный тип: LogManager.GetLogger(typeof(MyModule);, но только Shell будет настраивать регистратор (например, вызвать XmlConfigurator.Configure), используя собственный app.config.