复合应用的最佳日志记录方法?

时间:2009-11-17 18:45:19

标签: c# logging log4net prism

我正在创建一个包含多个不同项目(Shell,模块等)的复合WPF(Prism)应用程序。我正准备使用Log4Net实现日志记录。似乎有两种方法来设置日志记录:

  • 让Shell项目完成所有实际的日志记录。它获取对Log4Net的引用,其他项目触发复合事件,让Shell知道它需要记录一些东西。这些项目仅针对在Shell的app.config文件(DEBUG,ERROR等)中打开日志记录的级别触发事件,以免降低性能。

  • 为每个项目(包括模块)提供Log4Net参考,让项目自己记录到公共日志文件,而不是向Shell发送消息以进行记录。

哪种方法比较好?或者,我应该考虑另一种方法吗?谢谢你的帮助。

4 个答案:

答案 0 :(得分:18)

登录Prism的最简单方法是覆盖 Bootstrapper 中的LoggerFacade属性。通过覆盖LoggerFacade,只要记录器实现ILoggerFacade接口,就可以传入任何所需Logger的实例,只要记录器实现-My Project -Shell Module (add a reference to the Infrastructure project) -Bootstrapper.cs 接口。

我发现以下内容非常适合日志记录(我正在使用Enterprise Libary Logging块,但应该为Log4Net应用类似的东西应该是直截了当的):

在您的Shell中创建一个Boostrapper:

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

在您的Infrastructure项目中创建一个日志记录适配器,即:

LoggerFacade

MyCustomLoggerAdapter 类将用于覆盖Bootstrapper中的“LoggerFacade”属性。它应该有一个默认的contstructor,用来告知所有内容。

注意:通过覆盖Bootstrapper中的MyCustomLoggerAdapterExtendedAdapter属性,您将为Prism提供一个日志记录机制,用于记录自己的内部消息。您可以在整个应用程序中使用此记录器,也可以扩展记录器以获得功能更全面的记录器。 (请参阅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 } }

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 );

}

MyCustomLoggerAdapterExtendedAdapter 是从 MyCustomLoggerAdapter 中提取的,可以为更完整的记录器提供额外的构造函数。

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;
        }
    }

}

Bootstrapper

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

}

最后,要使用任一记录器,您需要做的就是为类的构造函数添加适当的接口,UnityContainer将为您注入记录器:

{{1}}

我认为您不需要单独的模块来执行日志记录策略。通过将日志记录策略添加到基础结构模块,所有其他模块将获得所需的引用(假设您将基础结构模块添加为对其他模块的引用)。通过将记录器添加到Boostrapper,您可以让UnityContainer根据需要注入记录策略。

CodePlex上的CompositeWPF contrib项目也有simple example of uisng Log4Net

HTH的

答案 1 :(得分:4)

我终于回到了这个,事实证明答案非常简单。在Shell项目中,将Log4Net配置为自定义记录器。 Prism文档(2009年2月)解释了如何在p处执行此操作。 287)。 Shell项目是唯一需要引用Log4Net的项目。要访问记录器(假设所有模块都传递了对Prism IOC容器的引用),只需在IOC容器中解析ILoggerFacade,它将为您提供对自定义记录器的引用。以正常方式将消息传递给此记录器。

因此,不需要将任何事件返回到Shell,也不需要模块具有Log4Net引用。圣鲭鱼,我喜欢IOC容器!

答案 2 :(得分:3)

上面提到的LoggerFacade问题是,您的应用中的非棱镜部分不会知道它。 Logger恕我直言需要更低级别,更普遍可访问,而不仅仅是在Composite框架内。

我的建议是,为什么不依靠标准Debug/Trace并实施自己的TraceListener。这样它对Prism / nonPrism部​​件都有效。您可以通过此方式实现所需的灵活性。

答案 3 :(得分:1)

为每个模块配置单独的记录器配置可能会在部署时变成问题。请记住,超级用户或管理员可能会完全更改日志记录的目标,重定向到数据库或中央存储库聚合日志记录服务(如my company之一)。如果所有单独的模块都有单独的配置,则高级用户/管理员必须重复每个模块的配置(在每个.config文件中,或在主app.config的每个模块的部分中),并在每次更改位置时重复此操作/格式化发生。此外,鉴于appender是在运行时从配置添加的,并且可能有appender,你现在不知道任何事情,有人可能会使用一个appender来锁定文件并导致app模块之间的冲突。只需一个log4.net配置即可简化管理。

单个模块仍然可以根据每个模块的需要进行单独配置(例如,DBO的INFO,UI层的ERROR)。每个模块都会通过询问自己的类型来获取记录器:LogManager.GetLogger(typeof(MyModule);但是只有Shell会使用自己的app.config配置记录器(例如调用XmlConfigurator.Configure)。