单例,日志记录和全局设置 - 实现好还是坏?

时间:2009-04-28 07:14:56

标签: .net singleton scope settings global

我有一个日志类,需要从应用程序的几乎所有位置调用。

然而,它需要在应用程序的开头设置“写入哪条路径”,“日志级别”以及是否“启用”。

我不想每次都给这个参数或者将Logging类作为参数传递给我的应用程序中的每个对象,所以我使用单例模式进行日志记录。

最近我从紧密耦合的课程中遭受了很多痛苦,我不想再犯同样的错误但是在考虑这个之后听起来这是唯一的好解决方案。

更新:

我并不真正关心记录我关心的解决类似设计问题,我对另一个全局设置对象有同样的困境,需要从这么多类中使用。但是将它注入到它们的每一个中只会产生可怕的开销和不太可读的代码。

您对此实施有何看法?当您遇到类似的设计决策时,您会怎么做?

P.S。请不要建议像“使用Log4X库”等

9 个答案:

答案 0 :(得分:4)

首先 - 您可以将日志编写器编写为跟踪侦听器,并使用方法中的Trace.Write等吗?

你真的需要一个实例吗?这将是有用的,例如,如果你想将它抽象为TextWriter或类似的 - 但如果它将是一个独立的单例,那么这些方法不能直接使用静态方法,即Log.Write(...) (而不是传入日志实例)?

重新解决一般问题 - 它取决于正在进行日志记录的类型。对于“manager”(etc)类,您可以考虑使用依赖注入(Unity,StructureMap等)来自动执行此操作。不过,我通常不会使用注射DTO。

答案 1 :(得分:3)

即使你不想要“使用Log4X”的建议(虽然你没有完全说为什么你想重新发明轮子),看看做出的设计决定似乎是明智的各种日志库。

根据我的经验,紧密耦合的问题在应用于日志记录时并不相关 - 特别是,我很少想测试我的应用程序的日志记录方面,我不介意它是否在单元期间记录到控制台测试

简而言之,“正常”模式:

private static readonly Logger log = LogManager.GetLogger(...);

(使用适当的名称更改等)在静态方法的使用方面在美学上没有吸引力,但在实践中效果很好。至少,那是我的经历。

答案 2 :(得分:2)

你可以在这里使用单身人士。您将在应用程序中的每个类与记录器类之间建立紧密耦合,但如果每个类中确实需要记录器类和全局设置类,则可以接受。

答案 3 :(得分:1)

我个人在这种情况下使用静态类。该类具有静态配置字段(用于手动实验)以及一些使用相应.config文件部分中的configurtion填充它们的函数。

这实际上非常接近于您可以“注入”新配置的情况。要将配置更改为新模型,我只需更改保存“活动”配置部分的.config文件字段。

这很容易使用,易于维护,并且每个人都理解它......我没有看到它的任何特殊缺点......

答案 4 :(得分:0)

这是ASP.Net吗?如果是这样,您可以使用Global.asax中的错误事件。

对于许多依赖项,您是否考虑过使用依赖注入框架?

<强>更新

我不确定性能影响以及相关性能对您的应用的影响,但此框架看起来很有趣:PostSharpA blog post about it

您也可以利用Conditional attribute

如果你使用PostSharp,我会对它的运作方式感兴趣。

答案 5 :(得分:0)

记录和设置实际上是以两种不同的方式处理的,所以如果我理解正确,你的实际问题与处理程序集之间的全局设置更相关。

关于日志记录,事情非常清楚 - 使用全局Singleton是常见的,尽管它将您的库紧密地耦合到日志库。使用Trace侦听器是一个更好的解决方案恕我直言。

但是在谈论应用程序设置时,你当然应该避免使它们成为全球性的。将所有与应用程序相关的设置仅保留在一个位置(应该保留的位置),但不能静态地提供给其他库。因此,将适当的设置传递给其他程序集必须是调用者的责任,反之亦然。

答案 6 :(得分:0)

您可以调查的一件事是逐个功能。据称,遵循这种技术可以解决导致类之间高度耦合的一些问题。更具体地说,这意味着应用程序中的每个功能中只有一个类,负责与配置提供程序进行通信(这可能是配置/设置/安装功能本身的一部分)。耦合程度仍处于高位,但由于它的定义很明确,因此应该是可管理的。

答案 7 :(得分:0)

类似的东西:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using log4net;
using log4net.Config;
using log4net.Appender;
using System.Reflection;
using System.IO;
using System.Globalization;
using log4net.Core;
using System.Web;

namespace GenApp.Utils
{
  ///<summary> Wrapper around log4net with dynamically adjustable verbosity</summary>
  public class Logger
  {

    private static Logger inst = new Logger ();
    public static Logger Inst ()
    {
      inst.ConfigureLogging ();
      return inst;
    }


    public enum DebugLevel : int
    {
      Fatal_Msgs = 0,
      Fatal_Error_Msgs = 1,
      Fatal_Error_Warn_Msgs = 2,
      Fatal_Error_Warn_Info_Msgs = 3,
      Fatal_Error_Warn_Info_Debug_Msgs = 4
    }

    public static void Debug ( GenApp.Bo.User objUser, ILog logger, string msg )
    {
      DebugLevel debugLevel = (DebugLevel)objUser.UserSettings.LogLevel;
      string strLogLevel = Logger.GetLogTypeString ( debugLevel );
      inst.SetLogingLevel ( strLogLevel );
      logger.Debug ( msg );

    } //eof method 


    public static void Info ( GenApp.Bo.User objUser, ILog logger, string msg )
    {
      DebugLevel debugLevel = (DebugLevel)objUser.UserSettings.LogLevel;
      string strLogLevel = Logger.GetLogTypeString ( debugLevel );
      inst.SetLogingLevel ( strLogLevel );
      logger.Info ( msg );

    } //eof method 


    public static void Warn ( GenApp.Bo.User objUser, ILog logger, string msg )
    {
      DebugLevel debugLevel = (DebugLevel)objUser.UserSettings.LogLevel;
      string strLogLevel = Logger.GetLogTypeString ( debugLevel );
      inst.SetLogingLevel ( strLogLevel );
      logger.Warn ( msg );

    } //eof method 


    public static void Error ( GenApp.Bo.User objUser, ILog logger, string msg )
    {
      DebugLevel debugLevel = (DebugLevel)objUser.UserSettings.LogLevel;
      string strLogLevel = Logger.GetLogTypeString ( debugLevel );
      inst.SetLogingLevel ( strLogLevel );
      logger.Error ( msg );
    } //eof method 


    public static void Fatal ( GenApp.Bo.User objUser, ILog logger, string msg )
    {
      DebugLevel debugLevel = (DebugLevel)objUser.UserSettings.LogLevel;
      string strLogLevel = Logger.GetLogTypeString ( debugLevel );
      inst.SetLogingLevel ( strLogLevel );
      logger.Fatal ( msg );
    } //eof method 


    /// <summary>
    /// Activates debug level 
    /// </summary>
    /// <sourceurl>http://geekswithblogs.net/rakker/archive/2007/08/22/114900.aspx</sourceurl>
    private void SetLogingLevel ( string strLogLevel )
    {

      this.ConfigureLogging ();
      string strChecker = "WARN_INFO_DEBUG_ERROR_FATAL";

      if (String.IsNullOrEmpty ( strLogLevel ) == true || strChecker.Contains ( strLogLevel ) == false)
        throw new ArgumentOutOfRangeException ( " The strLogLevel should be set to WARN , INFO , DEBUG ," );



      log4net.Repository.ILoggerRepository[] repositories = log4net.LogManager.GetAllRepositories ();

      //Configure all loggers to be at the debug level.
      foreach (log4net.Repository.ILoggerRepository repository in repositories)
      {
        repository.Threshold = repository.LevelMap[strLogLevel];
        log4net.Repository.Hierarchy.Hierarchy hier = (log4net.Repository.Hierarchy.Hierarchy)repository;
        log4net.Core.ILogger[] loggers = hier.GetCurrentLoggers ();
        foreach (log4net.Core.ILogger logger in loggers)
        {
          ( (log4net.Repository.Hierarchy.Logger)logger ).Level = hier.LevelMap[strLogLevel];
        }
      }

      //Configure the root logger.
      log4net.Repository.Hierarchy.Hierarchy h = (log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.GetRepository ();
      log4net.Repository.Hierarchy.Logger rootLogger = h.Root;
      rootLogger.Level = h.LevelMap[strLogLevel];
    }

    ///<summary>
    ///0 -- prints only FATAL messages 
    ///1 -- prints FATAL and ERROR messages 
    ///2 -- prints FATAL , ERROR and WARN messages 
    ///3 -- prints FATAL  , ERROR , WARN and INFO messages 
    ///4 -- prints FATAL  , ERROR , WARN , INFO and DEBUG messages 
    ///</summary>
    private static string GetLogTypeString ( DebugLevel debugLevel )
    {

      string srtLogLevel = String.Empty;
      switch (debugLevel)
      {
        case DebugLevel.Fatal_Msgs:
          srtLogLevel = "FATAL";
          break;
        case DebugLevel.Fatal_Error_Msgs:
          srtLogLevel = "ERROR";
          break;
        case DebugLevel.Fatal_Error_Warn_Msgs:
          srtLogLevel = "WARN";
          break;
        case DebugLevel.Fatal_Error_Warn_Info_Msgs:
          srtLogLevel = "INFO";
          break;
        case DebugLevel.Fatal_Error_Warn_Info_Debug_Msgs:
          srtLogLevel = "DEBUG";
          break;
        default:
          srtLogLevel = "FATAL";
          break;
      } //eof switch
      return srtLogLevel;

    } //eof GetLogTypeString


    /// <summary>
    /// The path where the configuration is read from.
    /// This value is set upon a call to ConfigureLogging().
    /// </summary>
    private string configurationFilePath;
    public void ConfigureLogging ()
    {
      lock (this)
      {
        bool configured = false;


        #region ConfigureByThePathOfTheEntryAssembly
        // Tells the logging system the correct path.
        Assembly a = Assembly.GetEntryAssembly ();

        if (a != null && a.Location != null)
        {
          string path = a.Location + ".config";

          if (File.Exists ( path ))
          {
            log4net.Config.DOMConfigurator.Configure (
              new FileInfo ( path ) );
            configurationFilePath = path;
            configured = true;
          }
          else
          {
            path = FindConfigInPath ( Path.GetDirectoryName ( a.Location ) );
            if (File.Exists ( path ))
            {
              log4net.Config.DOMConfigurator.Configure (
                new FileInfo ( path ) );
              configurationFilePath = path;
              configured = true;
            }
          }
        }
        #endregion ConfigureByThePathOfTheEntryAssembly


        #region ConfigureByWeb.config
        // Also, try web.config.
        if (!configured)
        {
          if (HttpContext.Current != null &&
            HttpContext.Current.Server != null &&
            HttpContext.Current.Request != null)
          {
            string path = HttpContext.Current.Server.MapPath (
              HttpContext.Current.Request.ApplicationPath );

            path = path.TrimEnd ( '\\' ) + "\\Web.config";

            if (File.Exists ( path ))
            {
              log4net.Config.DOMConfigurator.Configure (
                new FileInfo ( path ) );
              configurationFilePath = path;
              configured = true;
            }
          }
        }
        #endregion ConfigureByWeb.config


        #region ConfigureByThePathOfTheExecutingAssembly
        if (!configured)
        {
          // Tells the logging system the correct path.
          a = Assembly.GetExecutingAssembly ();

          if (a != null && a.Location != null)
          {
            string path = a.Location + ".config";

            if (File.Exists ( path ))
            {
              log4net.Config.DOMConfigurator.Configure (
                new FileInfo ( path ) );
              configurationFilePath = path;
              configured = true;
            }
            else
            {
              path = FindConfigInPath ( Path.GetDirectoryName ( a.Location ) );
              if (File.Exists ( path ))
              {
                log4net.Config.DOMConfigurator.Configure (
                  new FileInfo ( path ) );
                configurationFilePath = path;
                configured = true;
              }
            }
          }
        }
        #endregion ConfigureByThePathOfTheExecutingAssembly


        #region ConfigureByThePathOfTheCallingAssembly
        if (!configured)
        {
          // Tells the logging system the correct path.
          a = Assembly.GetCallingAssembly ();

          if (a != null && a.Location != null)
          {
            string path = a.Location + ".config";

            if (File.Exists ( path ))
            {
              log4net.Config.DOMConfigurator.Configure (
                new FileInfo ( path ) );
              configurationFilePath = path;
              configured = true;
            }
            else
            {
              path = FindConfigInPath ( Path.GetDirectoryName ( a.Location ) );
              if (File.Exists ( path ))
              {
                log4net.Config.DOMConfigurator.Configure (
                  new FileInfo ( path ) );
                configurationFilePath = path;
                configured = true;
              }
            }
          }
        }
        #endregion ConfigureByThePathOfTheCallingAssembly


        #region ConfigureByThePathOfTheLibIsStored
        if (!configured)
        {
          // Look in the path where this library is stored.
          a = Assembly.GetAssembly ( typeof ( Logger ) );

          if (a != null && a.Location != null)
          {
            string path = FindConfigInPath ( Path.GetDirectoryName ( a.Location ) );
            if (File.Exists ( path ))
            {
              log4net.Config.DOMConfigurator.Configure (
                new FileInfo ( path ) );
              configurationFilePath = path;
              configured = true;
            }
          }
        }
        #endregion ConfigureByThePathOfTheLibIsStored



      } //eof lock   
    } //eof method 



    /// <summary>
    /// Searches for a configuration file in the given path.
    /// </summary>
    private string FindConfigInPath (
      string path )
    {
      string[] files = Directory.GetFiles ( path );

      if (files != null && files.Length > 0)
      {
        foreach (string file in files)
        {
          if (Path.GetExtension ( file ).Trim ( '.' ).ToLower (
            CultureInfo.CurrentCulture ) == "config")
          {
            return file;
          }
        }
      }

      // Not found.
      return string.Empty;
    } //eof method 



    /// <summary>
    /// Remove dynamically appenders
    /// </summary>
    /// <param name="appenderName"></param>
    /// <param name="threshold"></param>
    public static void SetThreshold ( string appenderName, Level threshold )
    {
      foreach (AppenderSkeleton appender in LogManager.GetRepository ().GetAppenders ())
      {
        if (appender.Name == appenderName)
        {
          appender.Threshold = threshold;
          appender.ActivateOptions ();

          break;
        }
      }
    } //eof method 



  } //eof class 


} //eof namespace 

答案 8 :(得分:0)

如果您始终使用相同的来源,则可以使用Singleton模式。

但是如果您在不同的源上记录信息,例如在文件或事件日志中,则为不同的配置创建不同的日志记录类实例。