2009-11-17 15 views
11

Sto creando un'app Composite WPF (Prism) con diversi progetti (Shell, moduli e così via). Mi sto preparando a implementare la registrazione, utilizzando Log4Net. Sembra che ci siano due modi per impostare la registrazione:Miglior approccio di registrazione per l'app composita?

  • Lascia che il progetto Shell esegua tutto il log effettivo. Ottiene il riferimento a Log4Net e altri progetti generano eventi compositi per far sapere alla Shell che è necessario registrare qualcosa. Questi progetti attivano gli eventi solo per i livelli in cui la registrazione è attivata nel file app.config della shell (DEBUG, ERROR, ecc.), In modo da non degradare le prestazioni.

  • Assegnare a ciascun progetto, inclusi i moduli, un riferimento a Log4Net e lasciare che il progetto esegua il proprio log in un file di registro comune, invece di inviare messaggi a Shell per la registrazione.

Qual è l'approccio migliore? O c'è un altro approccio che dovrei considerare? Grazie per l'aiuto.

risposta

15

L'approccio più semplice per accedere a Prism è quello di ignorare la proprietà LoggerFacade nel proprio Bootstrapper. Sovrascrivendo lo LoggerFacade, è possibile passare un'istanza di qualsiasi Logger che si desidera con qualsiasi configurazione necessaria, purché il logger implementa l'interfaccia ILoggerFacade.

ho trovato quanto segue per funzionare abbastanza bene per la registrazione (sto usando il blocco di registrazione Enterprise Libary, ma applicando qualcosa di simile per Log4Net dovrebbe essere semplice):

Creare un Boostrapper nella shell:

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

Creare un adattatore di registrazione nel progetto delle infrastrutture, vale a dire:

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

Il MyCustomLoggerAdapt La classe er verrà utilizzata per sovrascrivere la proprietà 'LoggerFacade' in Bootstrapper. Dovrebbe avere un contstructor predefinito che aggiorna tutto.

Nota: ignorando la proprietà LoggerFacade in Bootstrapper, si fornisce un meccanismo di registrazione per consentire a Prism di utilizzare i propri messaggi interni.È possibile utilizzare questo registratore per tutta l'applicazione o estendere il registratore per un logger più completo. (Vedi 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 } 
} 

Il MyCustomLoggerAdapterExtendedAdapter è dervied dal MyCustomLoggerAdapter e in grado di fornire costruttori addizionali per un registratore di più a tutti gli effetti.

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

} 

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

} 

Infine, per utilizzare logger, tutto quello che dovete fare è aggiungere l'interfaccia appropriata per il costruttore di classe e l'UnityContainer inietterà il logger per 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 

} 

Non penso che sia necessario un modulo separato per la politica di registrazione. Aggiungendo le politiche di registrazione al modulo dell'infrastruttura, tutti gli altri moduli otterranno i riferimenti richiesti (supponendo che si aggiunga il modulo infrastruttura come riferimento agli altri moduli). E aggiungendo il logger al Boostrapper, è possibile lasciare che UnityContainer inietti la politica di registrazione secondo necessità.

C'è un simple example of uisng Log4Net nel progetto contrib di CompositeWPF su CodePlex.

di HTH

+0

Mi manca la registrazione gerarchica di log4net in questa soluzione. Qualcosa che penso manchi anche nel prisma. –

0

Avere configurazioni di logger separate per ogni modulo potrebbe trasformarsi in problemi durante l'implementazione. Ricordare che un utente esperto o un amministratore può modificare completamente la destinazione della registrazione, reindirizzamento a un database o ad un servizio di registrazione aggregato del repository centrale (come quello my company). Se tutti i moduli separati hanno configurazioni separate, il power user/admin deve ripetere la configurazione per ciascun modulo (in ciascun file .config, o nella sezione di ciascun modulo nell'app.config principale), e ripetere ogni volta un cambiamento di posizione/si verifica la formattazione. Inoltre, dato che gli appendici vengono aggiunti in fase di runtime dalla configurazione e potrebbero esserci degli appenders di cui non si sa nulla al momento, qualcuno potrebbe utilizzare un appender che blocca il file e provocare conflitti tra i moduli dell'app. Hsving una singola configurazione di log4.net semplifica l'amministrazione.

I singoli moduli possono ancora essere configurati come per le esigenze di ciascuno, separatamente (ad esempio INFO per livello DB, ERRORE per livello UI). Ogni modulo otterrebbe il registratore chiedendo il proprio tipo: LogManager.GetLogger(typeof(MyModule); ma solo la Shell configurerà il registratore (ad esempio chiama XmlConfigurator.Configure), utilizzando il proprio app.config.

+1

Anche se ogni progetto ha il proprio riferimento a Log4Net, non avrebbero file di configurazione separati - sarebbe ingestibile. Laddove ogni progetto in una soluzione esegue la propria registrazione (con il proprio riferimento Log4Net), sembra che practive utilizzi un singolo file di configurazione nell'app principale. Vedi http://nirajrules.wordpress.com/2008/06/14/blogging-best-practicies/ –

+0

Questa domanda è ancora aperta. Ho appreso che Prism esegue il proprio log e che può essere interfacciato con un logger come Log4Net. Ma non ho ancora trovato una buona pratica per la strategia di registrazione. –

+0

Ho finalmente trovato la mia risposta - vedi sotto. Non cambierò la risposta 'corretta' dalla risposta di Metro Smurf. Fornisce un buon esempio di interfacciamento di Log4Net con Prism. Ma la mia risposta qui di seguito risolve davvero il problema principale di come ottenere il logger che si imposta. –

3

ho finalmente tornato a questo, e si scopre che la risposta è davvero molto semplice. Nel progetto Shell, configurare Log4Net come un logger personalizzato. The Prism Documentation (febbraio 2009) spiega come farlo a p. 287). Il progetto Shell è l'unico progetto che necessita di un riferimento a Log4Net. Per accedere al logger (supponendo che tutti i moduli siano passati un riferimento al contenitore IOC Prism), basta risolvere ILoggerFacade nel contenitore IOC, che ti darà un riferimento al tuo logger personalizzato. Passa un messaggio a questo registratore nel modo normale.

Quindi, non c'è alcun bisogno di ritornare alla shell e non è necessario che i moduli abbiano riferimenti a Log4Net. Santo sgombro, amo i contenitori del CIO!

+0

Anche io li amo! :) – Attilah

2

Il problema con LoggerFacade, suggerito sopra, è che le parti non prism della tua app non lo saprebbero. Logger IMHO deve essere più di basso livello e più universalmente accessibile rispetto al framework Composite.

Il mio suggerimento è, perché non basarsi semplicemente sullo standard Debug/Trace e implementare il proprio TraceListener. In questo modo funzionerà bene per entrambe le parti Prism/nonPrism. Con questo puoi raggiungere il livello di flessibilità desiderato.

+0

Amen. Sto lavorando a un'app in cui gli sviluppatori hanno fatto riferimento alla facciata di 'ILogger' in un codice di livello molto basso. Mi dà una brutta sensazione. – Gusdor

+1

sì, ma log4net è un logger gerarchico, di gran lunga superiore alla semplice registrazione di traccia di debug. È difficile scavare grandi registrazioni, log4net è configurabile e scalabile anche per le routine a basso livello. A questo punto non vedo perché anche i moduli di basso livello non debbano avere una dipendenza da log4net. È una preoccupazione trasversale. Se è veramente necessario creare un assembly di logger di interfaccia e avere una depency, utilizzare una libreria impl –

Problemi correlati