Лучший подход к ведению журнала для составного приложения? - PullRequest
15 голосов
/ 17 ноября 2009

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

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

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

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

Ответы [ 4 ]

18 голосов
/ 01 декабря 2009

Самый простой подход для входа в 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 внедрять политику журналирования по мере необходимости.

Существует простой пример использования Log4Net в проекте CompositeWPF contrib для CodePlex.

НТН в

4 голосов
/ 31 мая 2011

Проблема с LoggerFacade, предложенная выше, заключается в том, что не призменные части вашего приложения не будут знать об этом. Регистратор ИМХО должен быть более низкого уровня и более универсальным, чем просто в среде Composite.

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

4 голосов
/ 17 декабря 2009

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

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

1 голос
/ 18 ноября 2009

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

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

...