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

发布于 2024-08-12 06:58:03 字数 386 浏览 2 评论 0原文

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

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

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

哪种方法更好?或者,我应该考虑另一种方法吗?感谢您的帮助。

I am creating a Composite WPF (Prism) app with several different projects (Shell, modules, and so on). I am getting ready to implement logging, using Log4Net. It seems there are two ways to set up the logging:

  • Let the Shell project do all of the actual logging. It gets the reference to Log4Net, and other projects fire composite events to let the Shell know that it needs to log something. Those projects fire the events only for levels where logging is turned on in the Shell's app.config file (DEBUG, ERROR, etc), so as not to degrade performance.

  • Give each project, including modules, a Log4Net reference, and let the project do its own logging to a common log file, instead of sending messages to the Shell for logging.

Which is the better approach? Or, is there another approach that I should consider? Thanks for your help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

罪歌 2024-08-19 06:58:03

登录 Prism 的最简单方法是覆盖 Bootstrapper 中的 LoggerFacade 属性。通过重写 LoggerFacade,您可以传入任何您想要的 Logger 实例以及所需的任何配置,只要该 Logger 实现了 ILoggerFacade 接口即可。

我发现以下内容对于日志记录非常有效(我正在使用 Enterprise Libary Logging 块,但对 Log4Net 应用类似的内容应该很简单):

在 Shell 中创建 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 类将用于覆盖 Bootstrapper 中的“LoggerFacade”属性。它应该有一个默认的构造函数来更新所有内容。

注意:通过覆盖 Bootstrapper 中的 LoggerFacade 属性,您可以为 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 根据需要注入日志记录策略。

CodePlex 上的 CompositeWPF contrib 项目上有一个 uisng Log4Net 的简单示例以及。

HTH 的

The simplest approach to logging in Prism is to override the LoggerFacade property in your Bootstrapper. By overridding the LoggerFacade, you can pass in an instance of any Logger you want with any configuration needed as long as the logger implements the ILoggerFacade interface.

I've found the following to work quite well for logging (I'm using the Enterprise Libary Logging block, but applying something similar for Log4Net should be straight forward):

Create a Boostrapper in your Shell:

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

Create a Logging Adapter in your Infrastructure project, i.e.:

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

The MyCustomLoggerAdapter class will be used to override the 'LoggerFacade' property in the Bootstrapper. It should have a default contstructor that news everything up.

Note: by overriding the LoggerFacade property in the Bootstrapper, you are providing a logging mechanisim for Prism to use to log its own internal messages. You can use this logger throughout your application, or you can extend the logger for a more fully featured logger. (see 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 }
}

The MyCustomLoggerAdapterExtendedAdapter is dervied from the MyCustomLoggerAdapter and can provide additional constructors for a more full-fledged logger.

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

}

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

}

Finally, to use either logger, all you need to do is add the appropriate interface to your class' constructor and the UnityContainer will inject the logger for you:

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

}

I don't think you need a separate module for the logging policy. By adding the logging policies to your infrastructure module, all other modules will get the required references (assuming you add the infrastructure module as a reference to your other modules). And by adding the logger to your Boostrapper, you can let the UnityContainer inject the logging policy as needed.

There is a simple example of uisng Log4Net on the CompositeWPF contrib project on CodePlex as well.

HTH's

梦里人 2024-08-19 06:58:03

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

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

I finally got back to this one, and it turns out the answer is really pretty simple. In the Shell project, configure Log4Net as a custom logger. The Prism Documentation (Feb. 2009) explains how to do that at p. 287). The Shell project is the only project that needs a reference to Log4Net. To access the logger (assuming all modules are passed a reference to the Prism IOC container), simply resolve ILoggerFacade in the IOC container, which will give you a reference to your custom logger. Pass a message to this logger in the normal manner.

So, there is no need for any eventing back to the Shell, and no need for modules to have Log4Net references. Holy mackerel, I love IOC containers!

独自唱情﹋歌 2024-08-19 06:58:03

上面建议的 LoggerFacade 的问题是应用程序的非棱镜部分不会知道它。恕我直言,记录器需要比复合框架内的更低级别和更普遍的可访问性。

我的建议是,为什么不只依赖标准的Debug/Trace并实现您自己的TraceListener。这样,它对于 Prism/nonPrism 部件都适用。您可以通过此实现所需的灵活性。

The problem with LoggerFacade, suggested above, is that the non prism parts of your app wouldn't know about it. Logger IMHO needs to be more low level and more universally accessible than just within the Composite framework.

My suggestion is, why not just rely on standard Debug/Trace and implement your own TraceListener. This way it will work well for both Prism/nonPrism parts. You can achieve desired level of flexibility with this.

故乡的云 2024-08-19 06:58:03

为每个模块设置单独的记录器配置可能会在部署时出现问题。请记住,高级用户或管理员可能会完全更改您的日志记录目标,重定向到数据库或中央存储库聚合日志记录服务(例如 我公司的一个)。如果所有单独的模块都有单独的配置,则高级用户/管理员必须为每个模块重复配置(在每个 .config 文件中,或在主 app.config 中的每个模块部分中),并在每次位置更改时重复此操作/发生格式化。此外,考虑到附加程序是在运行时从配置中添加的,并且可能存在您目前不了解的附加程序,有人可能会使用锁定文件的附加程序并导致应用程序模块之间发生冲突。 Hsving 一个 log4.net 配置简化了管理。

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

Having separate logger configurations for each module might turn into problems at deployment. Remember that a power user or administrator may completely change the target of your logging, redirecting to a database or to a central repository aggregated logging service (like my company's one). If all separate modules have separate configurations, the power user/admin has to repeat the configuration for each module (in each .config file, or in each module's section in the main app.config), and repeat this every time a change in location/formatting occurs. And besides, given that the appenders are added at run time from configuration and there may be appenders you don't know anything about at the moment, someone may use an appender that locks the file and result in conflict between the app modules. Hsving one single log4.net config simplifies administration.

Individual modules can still be configure as for the needs of each one, separately (eg. INFO for DB layer, ERROR for UI layer). Each module would get the logger by asking for its own type: LogManager.GetLogger(typeof(MyModule); but only the Shell will configure the logger (eg. call XmlConfigurator.Configure), using its own app.config.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文