web-dev-qa-db-fra.com

À quoi ressemblerait une classe Log4Net Wrapper?

J'ai cherché un cadre de journalisation pour .net (c #) et j'ai décidé d'essayer log4net après avoir lu quelques fils de questions/réponses ici sur stackoverflow. Je vois des gens mentionner encore et encore qu'ils utilisent une classe wrapper pour log4net et je me demande à quoi cela ressemblerait.

J'ai mon code divisé en différents projets (accès aux données/entreprise/webservice/..). À quoi ressemblerait une classe wrapper log4net? La classe wrapper devrait-elle être incluse dans tous les projets? Dois-je le construire en tant que projet séparé tous ensemble?

Le wrapper doit-il être une classe singleton?

56
Xerx

Essentiellement, vous créez une interface puis une implémentation concrète de cette interface qui encapsule directement les classes et les méthodes de Log4net. Des systèmes de journalisation supplémentaires peuvent être encapsulés en créant des classes plus concrètes qui englobent d'autres classes et méthodes de ces systèmes. Enfin, utilisez une fabrique pour créer des instances de vos wrappers en fonction d'un paramètre de configuration ou d'un changement de ligne de code. (Remarque: vous pouvez devenir plus flexible - et complexe - en utilisant un conteneur Inversion of Control tel que StructureMap .)

public interface ILogger
{
    void Debug(object message);
    bool IsDebugEnabled { get; }

    // continue for all methods like Error, Fatal ...
}

public class Log4NetWrapper : ILogger
{
    private readonly log4net.ILog _logger;

    public Log4NetWrapper(Type type)
    {
        _logger = log4net.LogManager.GetLogger(type);
    }

    public void Debug(object message)
    {
        _logger.Debug(message);
    }

    public bool IsDebugEnabled
    {
        get { return _logger.IsDebugEnabled; }
    }

    // complete ILogger interface implementation
}

public static class LogManager
{
    public static ILogger GetLogger(Type type)
    {
        // if configuration file says log4net...
        return new Log4NetWrapper(type);
        // if it says Joe's Logger...
        // return new JoesLoggerWrapper(type);
    }
}

Et un exemple d'utilisation de ce code dans vos classes (déclaré comme un champ statique en lecture seule):

private static readonly ILogger _logger =
    LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

Vous pouvez obtenir le même effet légèrement plus convivial en utilisant:

private static readonly ILogger _logger = 
    LogManager.GetLogger(typeof(YourTypeName));

Le premier exemple est considéré comme plus maintenable.

Vous ne voudriez pas créer un singleton pour gérer toute la journalisation car Log4Net enregistre pour le type appelant; c'est beaucoup plus propre et utile que chaque type utilise son propre enregistreur plutôt que de voir un seul type dans le fichier journal rapportant tous les messages.

Étant donné que votre implémentation doit être assez réutilisable (d'autres projets de votre organisation), vous pouvez en faire sa propre Assemblée ou, idéalement, l'inclure avec votre propre assemblage de cadre/utilitaire personnel/organisation. Ne déclarez pas à nouveau les classes séparément dans chacun de vos assemblys métier/données/interface utilisateur, ce n'est pas maintenable.

51
cfeduke

En supposant que vous alliez avec quelque chose comme réponse de cfeduke ci-dessus , vous pouvez également ajouter une surcharge à votre LogManager comme ceci:

public static ILogger GetLogger()
{
    var stack = new StackTrace();
    var frame = stack.GetFrame(1);
    return new Log4NetWrapper(frame.GetMethod().DeclaringType);
}

De cette façon, dans votre code, vous pouvez maintenant simplement utiliser:

private static readonly ILogger _logger = LogManager.GetLogger();

au lieu de l'un ou l'autre de ceux-ci:

private static readonly ILogger _logger =
    LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly ILogger _logger = 
    LogManager.GetLogger(typeof(YourTypeName));

Ce qui équivaut effectivement à la première alternative (c'est-à-dire celle qui utilise MethodBase.GetCurrentMethod().DeclaringType), seulement un peu plus simple.

24
Alconja

Quels avantages prévoyez-vous de sortir de l'écriture d'un wrapper pour log4net. Je vous recommande de vous familiariser avec les classes log4net avant d'écrire un wrapper autour d'elles. cfeduke a raison dans sa réponse sur la façon d'écrire ledit wrapper, mais à moins que vous n'ayez besoin d'ajouter des fonctionnalités réelles à son exemple, un wrapper ne réussirait qu'à ralentir le processus de journalisation et à ajouter de la complexité pour les futurs responsables. Cela est particulièrement vrai lorsque les outils de refactorisation disponibles dans .Net rendent ces modifications très faciles.

5
White Vans Inc

J'ai réussi à isoler la dépendance log4net en un seul projet. Si vous avez l'intention de faire de même, voici à quoi ressemble ma classe wrapper:

using System;

namespace Framework.Logging
{
    public class Logger
    {
        private readonly log4net.ILog _log;

        public Logger()
        {
            _log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        }

        public Logger(string name)
        {
            _log = log4net.LogManager.GetLogger(name);
        }

        public Logger(Type type)
        {
            _log = log4net.LogManager.GetLogger(type);
        }

        public void Debug(object message, Exception ex = null)
        {
            if (_log.IsDebugEnabled)
            {
                if (ex == null)
                {
                    _log.Debug(message);
                }
                else
                {
                    _log.Debug(message, ex);
                }
            }
        }

        public void Info(object message, Exception ex = null)
        {
            if (_log.IsInfoEnabled)
            {
                if (ex == null)
                {
                    _log.Info(message);
                }
                else
                {
                    _log.Info(message, ex);
                }
            }
        }

        public void Warn(object message, Exception ex = null)
        {
            if (_log.IsWarnEnabled)
            {
                if (ex == null)
                {
                    _log.Warn(message);
                }
                else
                {
                    _log.Warn(message, ex);
                }
            }
        }

        public void Error(object message, Exception ex = null)
        {
            if (_log.IsErrorEnabled)
            {
                if (ex == null)
                {
                    _log.Error(message);
                }
                else
                {
                    _log.Error(message, ex);
                }
            }
        }

        public void Fatal(object message, Exception ex = null)
        {
            if (_log.IsFatalEnabled)
            {
                if (ex == null)
                {
                    _log.Fatal(message);
                }
                else
                {
                    _log.Fatal(message, ex);
                }
            }
        }
    }
}

Et n'oubliez pas d'ajouter ceci dans le AssemblyInfo.cs du projet d'interfaçage (m'a pris quelques bonnes heures pour le trouver)

[Assembly: log4net.Config.XmlConfigurator(Watch = true, ConfigFile = "log4net.config")]

Et mettez votre xml de configuration log4net dans log4net.config fichier, définissez-le comme Content, Copy Always

1
Jeson Martajaya

Il existe des frameworks comme Prism Library for WPF qui promeuvent l'utilisation d'un façade pour le framework de journalisation de votre choix.

Ceci est un exemple qui utilise log4net :

using System;
using log4net;
using log4net.Core;
using Prism.Logging;

public class Log4NetLoggerFacade : ILoggerFacade
{
    private static readonly ILog Log4NetLog = LogManager.GetLogger(typeof (Log4NetLoggerFacade));

    public void Log(string message, Category category, Priority priority)
    {
        switch (category)
        {
            case Category.Debug:
                Log4NetLog.Logger.Log(typeof(Log4NetLoggerFacade), Level.Debug, message, null);
                break;
            case Category.Exception:
                Log4NetLog.Logger.Log(typeof(Log4NetLoggerFacade), Level.Error, message, null);
                break;
            case Category.Info:
                Log4NetLog.Logger.Log(typeof(Log4NetLoggerFacade), Level.Info, message, null);
                break;
            case Category.Warn:
                Log4NetLog.Logger.Log(typeof(Log4NetLoggerFacade), Level.Warn, message, null);
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(category), category, null);
        }
    }
}

Notez qu'en spécifiant callerStackBoundaryDeclaringType vous pouvez toujours obtenir le nom de classe de l'appelant émettant la demande de journalisation. Tout ce que vous avez à faire est d'inclure %C %M dans votre modèle de conversion:

<layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date [%thread] %-5level %C.%M - %message%newline" />
</layout>

Cependant, comme l'avertit documentation , la génération des informations sur la classe de l'appelant est lente, elle doit donc être utilisée à bon escient.

1
Leonardo

Je crois comprendre qu'une classe wrapper pour log4net serait une classe statique qui s'occupe de l'initialisation de l'objet de journalisation à partir de app.config/web.config ou par code (par exemple, intégration avec NUnit).

0
devio

une utilisation possible pour un wrapper log4net pourrait être une classe qui obtient la classe et la méthode appelantes par réflexion pour avoir une idée de l'endroit où votre entrée de journalisation s'est produite. au moins je l'utilise fréquemment.

0

Alconja, j'aime votre idée d'utiliser le stacktrace pour revenir à la méthode d'appel. Je pensais encapsuler davantage les appels, non seulement pour récupérer l'objet enregistreur, mais pour effectuer réellement la journalisation. Ce que je veux, c'est une classe statique qui gère la journalisation, en faisant abstraction de l'implémentation spécifique utilisée. C'est à dire.

LoggingService.LogError("my error message");

De cette façon, je n'ai besoin que de changer les éléments internes de la classe statique, si je décide plus tard d'utiliser un autre système de journalisation.

J'ai donc utilisé votre idée pour obtenir l'objet appelant en utilisant la trace de pile:

public static class LoggingService
{
    private static ILog GetLogger()
    {    
        var stack = new StackTrace();    
        var frame = stack.GetFrame(2);    
        return log4net.LogManager.GetLogger(frame.GetMethod().DeclaringType);
    }

    public static void LogError(string message)
    {
        ILog logger = GetLogger();
        if (logger.IsErrorEnabled)
            logger.Error(message);
    }
    ...
}

Quelqu'un voit-il un problème avec cette approche?

0
Jeppe Andreasen