J'avais l'habitude d'utiliser des façades de journalisation telles que Common.Logging (même pour masquer ma propre bibliothèque CuttingEdge.Logging ), mais de nos jours, j'utilise le modèle d'injection de dépendance et cela me permet de cacher les enregistreurs derrière ma propre abstraction (simple) qui adhère à la fois à Dependency Principe d'inversion et principe de ségrégation d'interface(FAI) parce qu'il a un membre et parce que l'interface est définie par mon application; pas une bibliothèque externe. Minimiser les connaissances que les parties centrales de votre application ont sur l'existence de bibliothèques externes, mieux c'est; même si vous n'avez jamais l'intention de remplacer votre bibliothèque de journalisation. La forte dépendance à la bibliothèque externe rend le test de votre code plus difficile et complique votre application avec une API qui n'a jamais été conçue spécifiquement pour votre application.
Voici à quoi ressemble souvent l'abstraction dans mes applications:
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;
}
}
En option, cette abstraction peut être étendue avec quelques méthodes d'extension simples (permettant à l'interface de rester étroite et de continuer à adhérer au FAI). Cela rend le code pour les consommateurs de cette interface beaucoup plus simple:
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.
}
Étant donné que l'interface ne contient qu'une seule méthode, vous pouvez facilement créer une ILogger
implémentation qui effectue un proxy vers log4net , Serilog , Microsoft.Extensions.Logging , NLog ou toute autre bibliothèque de journalisation et configurer votre conteneur DI pour l'injecter dans les classes qui ont un ILogger
dans leur constructeur.
Notez qu'avoir des méthodes d'extension statiques au-dessus d'une interface avec une seule méthode est assez différent d'avoir une interface avec de nombreux membres. Les méthodes d'extension ne sont que des méthodes d'assistance qui créent un LogEntry
message et le transmettent à la seule méthode de l' ILogger
interface. Les méthodes d'extension font partie du code du consommateur; ne fait pas partie de l'abstraction. Non seulement cela permet aux méthodes d'extension d'évoluer sans qu'il soit nécessaire de changer l'abstraction, les méthodes d'extension et leLogEntry
constructeur sont toujours exécutés lorsque l'abstraction du journal est utilisée, même lorsque ce logger est stubbed / mocked. Cela donne plus de certitude quant à l'exactitude des appels à l'enregistreur lors de l'exécution dans une suite de tests. L'interface à un membre facilite également les tests; Avoir une abstraction avec de nombreux membres rend difficile la création d'implémentations (telles que des simulacres, des adaptateurs et des décorateurs).
Lorsque vous faites cela, il n'y a presque jamais besoin d'une abstraction statique que les façades de journalisation (ou toute autre bibliothèque) pourraient offrir.