Prima usavo le facciate di logging come Common.Logging (anche per nascondere la mia libreria CuttingEdge.Logging ), ma oggigiorno utilizzo il pattern Dependency Injection e questo mi permette di nascondere i logger dietro la mia (semplice) astrazione che aderisce a entrambe le dipendenze Principio di inversione e principio di segregazione dell'interfaccia(ISP) perché ha un membro e perché l'interfaccia è definita dalla mia applicazione; non una libreria esterna. Riducendo al minimo la conoscenza che le parti principali della tua applicazione hanno sull'esistenza di librerie esterne, meglio è; anche se non hai intenzione di sostituire mai la tua libreria di log. La forte dipendenza dalla libreria esterna rende più difficile testare il codice e complica la tua applicazione con un'API che non è mai stata progettata specificamente per la tua applicazione.
Ecco come appare spesso l'astrazione nelle mie applicazioni:
public interface ILogger
{
void Log(LogEntry entry);
}
public enum LoggingEventType { Debug, Information, Warning, Error, Fatal };
// Immutable DTO that contains the log information.
public class LogEntry
{
public readonly LoggingEventType Severity;
public readonly string Message;
public readonly Exception Exception;
public LogEntry(LoggingEventType severity, string message, Exception exception = null)
{
if (message == null) throw new ArgumentNullException("message");
if (message == string.Empty) throw new ArgumentException("empty", "message");
this.Severity = severity;
this.Message = message;
this.Exception = exception;
}
}
Facoltativamente, questa astrazione può essere estesa con alcuni semplici metodi di estensione (consentendo all'interfaccia di rimanere stretta e continuare ad aderire all'ISP). Questo rende il codice per i consumatori di questa interfaccia molto più semplice:
public static class LoggerExtensions
{
public static void Log(this ILogger logger, string message) {
logger.Log(new LogEntry(LoggingEventType.Information, message));
}
public static void Log(this ILogger logger, Exception exception) {
logger.Log(new LogEntry(LoggingEventType.Error, exception.Message, exception));
}
// More methods here.
}
Dal momento che l'interfaccia contiene un solo metodo, è possibile creare facilmente ILogger
un'implementazione che proxy per log4net , a Serilog , Microsoft.Extensions.Logging , NLog o qualsiasi altra libreria di registrazione e configurare il contenitore DI iniettare in classi che hanno una ILogger
loro costruttore.
Si noti che avere metodi di estensione statici su un'interfaccia con un singolo metodo è abbastanza diverso dall'avere un'interfaccia con molti membri. I metodi di estensione sono solo metodi di supporto che creano un LogEntry
messaggio e lo passano attraverso l'unico metodo ILogger
sull'interfaccia. I metodi di estensione diventano parte del codice del consumatore; non fa parte dell'astrazione. Ciò non solo consente ai metodi di estensione di evolversi senza la necessità di modificare l'astrazione, i metodi di estensione e ilLogEntry
vengono sempre eseguiti quando viene utilizzata l'astrazione del logger, anche quando quel logger viene stubbed / deriso. Ciò fornisce maggiore certezza sulla correttezza delle chiamate al logger durante l'esecuzione in una suite di test. L'interfaccia a un membro rende anche i test molto più semplici; Avere un'astrazione con molti membri rende difficile creare implementazioni (come mock, adattatori e decoratori).
Quando si esegue questa operazione, non c'è quasi mai bisogno di un'astrazione statica che le facciate di registrazione (o qualsiasi altra libreria) potrebbero offrire.