Синглтон, логирование и глобальные настройки - хорошая или плохая реализация? - PullRequest
8 голосов
/ 28 апреля 2009

У меня есть класс журналирования, который требует, чтобы его вызывали практически из любой точки приложения.

Однако в начале приложения требуется настройка с указанием «какой путь записи», «уровень журнала» и, если он «включен» или нет.

Я не хочу указывать эти параметры каждый раз или передавать класс Logging в качестве параметра для каждого отдельного объекта в моем приложении, поэтому я использую одноэлементный шаблон для регистрации.

В последнее время я сильно пострадал от тесно связанных классов. Я не хочу повторять ту же ошибку снова, но подумав об этом, похоже, что это единственное хорошее решение.

ОБНОВЛЕНИЕ:

Меня не волнует регистрация того, что меня волнует, решая подобные проблемы проектирования , у меня та же дилемма с другим объектом глобальных настроек, который требуется использовать из очень многих классов. Но внедрение его в каждый из них приводит к ужасным накладным расходам и менее читаемому коду.

Что вы думаете об этой реализации и что вы делаете, когда сталкиваетесь с подобными проектными решениями?

P.S. Пожалуйста, не предлагайте что-то вроде «использовать библиотеку Log4X» и т. Д.

Ответы [ 9 ]

4 голосов
/ 28 апреля 2009

Во-первых - не могли бы вы написать средство записи журнала в качестве прослушивателя трассировки и использовать Trace.Write и т. Д. Из методов?

Тебе нужен экземпляр здесь? Это было бы полезно, например, если вы хотите абстрагировать его как TextWriter или аналогичный - но если это будет автономный синглтон, могут ли методы не использовать статические методы напрямую, т.е. в экземпляре журнала)?

По поводу общей проблемы - это зависит от типов, которые делают ведение журнала. Для классов «менеджер» (и т. Д.) Вы можете рассмотреть возможность использования внедрения зависимостей (Unity, StructureMap и т. Д.) Для автоматизации этого. Однако я бы не стал использовать инъекции с DTO.

3 голосов
/ 28 апреля 2009

Даже если вам не нужны предложения «использовать Log4X» (хотя вы точно не говорите почему вы хотите заново изобрести колесо), было бы разумно взглянуть на дизайнерские решения, принятые различные библиотеки журналов.

По моему опыту, проблемы тесной связи не так актуальны, когда применяются к ведению журналов - в частности, я редко хочу тестировать сторону журналирования моего приложения, и я не против, если он регистрируется на консоли во время модуля тестирование.

Короче говоря, "нормальный" шаблон:

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

(с соответствующими изменениями имени и т. Д.) Эстетически непривлекательно при использовании статических методов, но на практике работает довольно хорошо. По крайней мере, это был мой опыт.

2 голосов
/ 28 апреля 2009

Возможно, вы можете использовать синглтон здесь. Вы будете иметь тесную связь между каждым классом в приложении и классом регистратора, но если класс регистратора и класс глобальных настроек действительно необходимы в каждом классе, это может быть приемлемым.

1 голос
/ 28 апреля 2009

Я лично использую статический класс в таком случае. Класс имеет статические поля конфигурации (для ручного эксперимента), а также некоторые функции для их заполнения с использованием конфигурации из соответствующего раздела файла .config.

По сути, это очень близко к тому, что было бы у вас с DI, поскольку вы можете «внедрить» новый конфиг. Чтобы изменить конфигурацию на новую модель, я просто изменяю поле файла .config, в котором содержится «активный» раздел конфигурации.

Это легко использовать, легко поддерживать, и все это понимают ... Я не вижу в этом особого недостатка ...

0 голосов
/ 12 сентября 2012

Если вы всегда пишете в один и тот же источник, вы можете использовать шаблон Singleton.

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

0 голосов
/ 20 мая 2009

Нечто подобное:

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 
0 голосов
/ 28 апреля 2009

Одна вещь, которую вы можете исследовать, - это пакетная функция. Утверждается, что следование этой методике устраняет некоторые проблемы, которые приводят к высокой связи между классами. Точнее говоря, это будет означать, что в каждой функции в вашем приложении будет только один класс, отвечающий за общение с поставщиком конфигурации (который вполне может быть частью самой функции конфигурации / настройки / установки). Уровень связи все еще находится на стороне высокого уровня, но, поскольку он четко определен, он должен быть управляемым.

0 голосов
/ 28 апреля 2009

Ведение журнала и настройки фактически обрабатываются двумя различными способами, поэтому, если я правильно понял, ваш фактический вопрос был больше связан с обработкой глобальных настроек между сборками.

Что касается ведения журналов, все довольно ясно - использование глобального Singleton для этого является обычным явлением, хотя оно тесно связывает ваши библиотеки с библиотекой журналов. Использование отслеживающих слушателей является еще лучшим решением IMHO.

Но, говоря о настройках приложения, вы, безусловно, должны избегать превращения их в глобальные. Храните все параметры, относящиеся к приложению, только в одном месте (те, которые должны быть сохранены), но статически недоступны для других библиотек. Поэтому передача соответствующих настроек другим сборкам должна быть обязанностью вызывающего абонента, а не наоборот.

0 голосов
/ 28 апреля 2009

Это ASP.Net? Если это так, вы можете использовать Событие ошибки в Global.asax.

Для ваших многочисленных зависимостей вы рассматривали возможность использования структуры внедрения зависимостей?

Обновление

Я не уверен, что это влияет на производительность и насколько она важна для вашего приложения, но эта среда выглядит интересной: PostSharp , Пост в блоге об этом .

Вы также можете использовать атрибут Условное .

Если вы используете PostSharp, мне было бы интересно, как это работает.

...