web-dev-qa-db-fra.com

Bonnes pratiques pour l'enveloppe d'enregistreur

Je veux utiliser un nlogger dans mon application, peut-être qu'à l'avenir, je devrai changer le système de journalisation. Je veux donc utiliser une façade forestière.

Connaissez-vous des recommandations pour des exemples existants sur la façon de les écrire? Ou donnez-moi simplement un lien vers certaines des meilleures pratiques dans ce domaine.

86
Night Walker

J'avais l'habitude d'utiliser des façades de journalisation telles que Common.Logging (même pour cacher ma propre CuttingEdge.Logging bibliothèque), mais aujourd'hui j'utilise la 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 Inversion Principle et Interface Segregation Principle (ISP) car il a un membre et parce que l'interface est définie par mon application; pas une bibliothèque externe. Minimiser les connaissances des parties principales de votre application sur l'existence de bibliothèques externes, mieux c'est; même si vous n'avez pas l'intention de remplacer votre bibliothèque de journalisation. La forte dépendance de la bibliothèque externe rend plus difficile le test de votre code 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;
    }
}

Facultativement, 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.
}

Comme l'interface ne contient qu'une seule méthode, vous pouvez facilement créer une implémentation ILogger qui proxy vers log4net , vers Serilog , Microsoft.Extensions .Logging , NLog ou toute autre bibliothèque de journalisation et configurez votre conteneur DI pour l'injecter dans les classes qui ont un ILogger dans leur constructeur.

Notez que le fait d'avoir des méthodes d'extension statiques au-dessus d'une interface avec une seule méthode est très différent d'avoir une interface avec de nombreux membres. Les méthodes d'extension ne sont que des méthodes auxiliaires qui créent un message LogEntry et le transmettent via la seule méthode de l'interface ILogger. 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 avoir besoin de changer l'abstraction, mais les méthodes d'extension et le constructeur LogEntry sont toujours exécutés lorsque l'abstraction de l'enregistreur est utilisée, même lorsque cet enregistreur est tronqué/moqué. 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; Le fait d'avoir une abstraction avec de nombreux membres rend difficile la création d'implémentations (telles que des simulateurs, 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) puissent offrir.

194
Steven

J'ai utilisé le petit wrapper d'interface + adaptateur de https://github.com/uhaciogullari/NLog.Interface qui est également disponible via NuGet :

PM> Install-Package NLog.Interface 
9
Jon Adams

En général, je préfère créer une interface comme

public interface ILogger
{
 void LogInformation(string msg);
 void LogError(string error);
}

et dans le runtime j'injecte une classe concrète qui est implémentée à partir de cette interface.

4
crypted

Une excellente solution à ce problème a émergé sous la forme du projet LibLog .

LibLog est une abstraction de journalisation avec prise en charge intégrée des principaux enregistreurs, notamment Serilog, NLog, Log4net et Enterprise Logger. Il est installé via le gestionnaire de packages NuGet dans une bibliothèque cible en tant que fichier source (.cs) au lieu d'une référence .dll. Cette approche permet à l'abstraction de journalisation d'être incluse sans forcer la bibliothèque à prendre une dépendance externe. Il permet également à un auteur de bibliothèque d'inclure la journalisation sans forcer l'application consommatrice à fournir explicitement un enregistreur à la bibliothèque. LibLog utilise la réflexion pour déterminer quel enregistreur concret est utilisé et s'y connecter sans code de câblage explicite dans le ou les projets de bibliothèque.

Ainsi, LibLog est une excellente solution pour la journalisation dans les projets de bibliothèque. Il vous suffit de référencer et de configurer un enregistreur concret (Serilog pour la victoire) dans votre application ou service principal et d'ajouter LibLog à vos bibliothèques!

4
Rob Davis

Au lieu d'écrire votre propre façade, vous pouvez utiliser Castle Logging Services ou Simple Logging Façade .

Les deux comprennent des adaptateurs pour NLog et Log4net.

2
Jonas Kongslund

Pour l'instant, le meilleur pari est d'utiliser le package Microsoft.Extensions.Logging ( comme l'a souligné Julian ). La plupart des infrastructures de journalisation peuvent être utilisées avec cela.

Définir votre propre interface, comme expliqué dans réponse de Steven est OK pour les cas simples, mais il manque quelques éléments que je considère importants:

  • Journalisation structurée et déstructuration des objets (notation @ dans Serilog et NLog)
  • Construction/formatage de chaîne retardé: comme il prend une chaîne, il doit tout évaluer/formater lorsqu'il est appelé, même si à la fin l'événement ne sera pas enregistré car il est inférieur au seuil (coût de performance, voir point précédent)
  • Vérifications conditionnelles comme IsEnabled(LogLevel) que vous pourriez vouloir, pour des raisons de performances encore une fois

Vous pouvez probablement implémenter tout cela dans votre propre abstraction, mais à ce stade, vous réinventerez la roue.

2
Philippe

Depuis 2015, vous pouvez également utiliser . NET Core Logging si vous créez des applications de base .NET.

Le package auquel NLog doit se connecter est:

1
Julian