Eu costumava usar fachadas de registro, como Common.Logging (até mesmo para ocultar minha própria biblioteca CuttingEdge.Logging ), mas hoje em dia eu uso o padrão de injeção de dependência e isso me permite ocultar registradores atrás de minha própria abstração (simples) que adere a ambos Dependency Princípio de Inversão e o Princípio de Segregação de Interface(ISP) porque tem um membro e porque a interface é definida pela minha aplicação; não é uma biblioteca externa. Minimizar o conhecimento que as partes centrais de seu aplicativo têm sobre a existência de bibliotecas externas, melhor; mesmo se você não tiver a intenção de substituir sua biblioteca de registro. A forte dependência da biblioteca externa torna mais difícil testar seu código e complica seu aplicativo com uma API que nunca foi projetada especificamente para seu aplicativo.
É assim que a abstração costuma se parecer em meus aplicativos:
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;
}
}
Opcionalmente, essa abstração pode ser estendida com alguns métodos de extensão simples (permitindo que a interface permaneça estreita e continue aderindo ao ISP). Isso torna o código para os consumidores desta interface muito mais simples:
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.
}
Uma vez que a interface contém apenas um único método, você pode facilmente criar uma ILogger
implementação que faz proxy para log4net , Serilog , Microsoft.Extensions.Logging , NLog ou qualquer outra biblioteca de registro e configurar seu contêiner de DI para injetá-lo em classes que têm um ILogger
em seus construtor.
Observe que ter métodos de extensão estáticos no topo de uma interface com um único método é bastante diferente de ter uma interface com muitos membros. Os métodos de extensão são apenas métodos auxiliares que criam uma LogEntry
mensagem e a passam pelo único método na ILogger
interface. Os métodos de extensão tornam-se parte do código do consumidor; não faz parte da abstração. Isso não só permite que os métodos de extensão evoluam sem a necessidade de alterar a abstração, os métodos de extensão e oLogEntry
construtores são sempre executados quando a abstração do registrador é usada, mesmo quando esse registrador é fragmentado / simulado. Isso dá mais certeza sobre a exatidão das chamadas para o logger durante a execução em um conjunto de testes. A interface de um membro torna o teste muito mais fácil também; Ter uma abstração com muitos membros torna difícil criar implementações (como mocks, adaptadores e decoradores).
Quando você faz isso, dificilmente há necessidade de alguma abstração estática que as fachadas de registro (ou qualquer outra biblioteca) podem oferecer.