Documentation:Outils Log Tech
From Gameforge Official Website
Contents |
Introduction
Pourquoi ne pas avoir utilisé un logger deja existant ?
Cas de log4cxx
- Très voir trop buggé
- Support les applications multi-threadées mais avec les bibliothèques d'apache (ce qui nous aurait ajouté une nouvelle dépendance).
Cas de Log4cpp
- Quasiment mort
- Support les applications multi-threadées mais avec les bibliothèques d'omniworks (ce qui nous aurait ajouté une nouvelle dépendance).
Prérequis
- Configuration :
- Elle doit pouvoir se faire au moyen d'un fichier xml ou part une classe contennat l'ensemble des propriété.
- Si la configuration est basée sur un fichier et que si fichier est modifié alors la conf est actualisée
- Général :
- Thread safe
- Modulaire (cad qu'on puisse avoir des logs de niveau debug sur certaines sections pour ne pas avoir de log sur d'autrre section)
- 5 niveaux de log (debug, info, error, fatal, warning)
- Filtrage de log
Dépendance
- Xerces-c pour le parsing du fichier xml de configuration.
- ACE pour la partie martie thread-safe (a base de mutex...)
L'organisation
Des fichiers
Les fichiers :
- source : commun/src/log - headers : commun/include/gameforge/commun/log
Des namespaces
L'ensemble du code sera situé dans le namespace :
gameforge --> commun --> log
Donc en c++ :
namespace gameforge { namespace commun { namespace log { } } }
Les Principales classes et interfaces
Logger
Logger est un singleton [1].
namespace gameforge { namespace commun { namespace log { /** */ class Logger; typedef LoggerPtr boost::shared_ptr<Logger>; class Logger { public: /** */ enum TraceLevel { DEBUG, INFO, WARN, ERROR, FATAL }; /** * Creation & retrieval methods: */ static LoggerPtr getRootLogger( void ); static LoggerPtr getLogger(const std::string& name); /** * */ static void destroy( void ); /** * Printing method: */ void debug(const std::string& message, const char* file = 0, int line = -1); void info(const std::string& message, const char* file = 0, int line = -1); void warn(const std::string& message, const char* file = 0, int line = -1); void error(const std::string& message, const char* file = 0, int line = -1); void fatal(const std::string& message, const char* file = 0, int line = -1); /** * Check if log type is enable */ bool hasDebugLog(void) const; bool hasInfoLog(void) const; bool hasWarnLog(void) const; bool hasErrorLog(void) const; bool hasFatalLog(void) const; /** * */ void updateConfiguration ( void ); protected: /** */ LoggerPtr getNode(LoggerPtr node, const std::string &name); /** */ void print(TraceLevel level, const std::string& message, const char* file , int line ); private: /** * Default constrcutor (to builda a racine node) */ Logger(void); /** * Constructor to build Node with name */ Logger(const std::string &name); /** */ std::list<std::pair<TraceLevel, Output::OutputPtr> > mOutputs; ACE_Thread_Mutex mOutputsMutex; bool mHaveOutput; /** */ std::map<std::string, LoggerPtr> mNodes; ACE_Thread_Mutex mNodesMutex; /** */ std::string mName; /** */ static LoggerPtr mRoot; }; } } }
Configurator
Configurator est aussi un singleton.
namespace gameforge { namespace commun { namespace log { /** */ class Configurator; typedef ConfiguratorPtr boost::shared_ptr<Configurator>; class Configurator { public: /** */ static ConfiguratorPtr getInstance ( void ); /** */ static void destroy( void ); /** */ void fromFile(const std::string& file, unsigned int delay = 0); /** */ void clear( void ); /** */ void addOutput(const std::string &name, OutputPtr output); /** */ void addFilter(const std::string &loggerName, Logger::TraceLevel level, const std::string &nameOutput); /** */ void setStatusLogger(const std::string &loggerName, bool status); /** */ struct LoggerConfiguration { bool enable; std::list<std::pair<Logger::TraceLevel, Output::OutputPtr> > output; }; typedef LoggerConfigurationPtr boost::shared_ptr<LoggerConfiguration>; /** */ const LoggerConfigurationPtr getConfiguration(const std::string & name) const; private: /** */ ACE_Thread_Mutex mMutexCondition; ACE_Condition<ACE_Thread_Mutex> mCondition; /** */ unsigned int mDelay; std::string mFile; /** */ static ConfiguratorPtr mInstance; /** */ std::map<std::string, OutputPtr> mOutputs; /** */ std::map<std::string, LoggerConfigurationPtr> mConfiguration; }; } } }
Output
L'interface
namespace gameforge { namespace commun { namespace log { class Output { public: /** */ virtual ~Output( void ) { } /** */ virtual void setProperties(const std::string& properties, const std::string &value) = 0; /** */ virtual void print(Logger::TraceLevel level, const std::string& ns, const std::string& message, const char* file , int line ) = 0; }; /** */ typedef OutputPtr boost::shared_ptr<Output>; } } }
La Factory
namespace gameforge { namespace commun { namespace log { /** */ class OutputFactory { public: /** */ static OutputPtr create(const std::string& type); private: /** */ OutputFactory(void) { } /** */ ~OutputFactory(void) { } }; } } }
Les implémentations de l'interface
namespace gameforge { namespace commun { namespace log { class StreamOutput { public: /** */ StreamOutput(std::ostream &stream); /** */ virtual ~StreamOutput(void); /** */ virtual void setProperties(const std::string& properties, const std::string &value); /** */ virtual void print(Logger::TraceLevel level, const std::string& name, const std::string& message, const char* file , int line ); }; /** */ class FileOutput { public: /** */ FileOutput(void); /** */ virtual ~FileOutput(void); /** */ virtual void setProperties(const std::string& properties, const std::string &value); /** */ virtual void print(Logger::TraceLevel level, const std::string& name, const std::string& message, const char* file , int line ); }; } } }
Fichier de configuration
La configuration se fera au moyen d'un fichier xml. La syntaxe de ce fichier sera vérifié par un fichier XSD.
Fichier XSD
To do...
Exemples de fichier de configuration
Exemple n°1
<?xml version="1.0"?> <log> <!-- Definition des flux de sortie --> <output id="f" type="file"> <propertie name="Blablabla-%yyyy%mm%dd-%hh%mm%ss-%unique"/> <propertie format="%hh%mm%ss%mmmm-%t"/> <propertie maxsize="200k" /> </output> <output id="c" type="stdout"> <propertie format="%hh%mm%ss%mmmm-%t"/> </output> <output id="e" type="stderr"> <propertie format="%hh%mm%ss%mmmm-%t"/> </output> <!-- Definition des règle de filtrage --> <node name="."> <filter level="debug" output="f" /> <filter level="error" output="e" /> </node> <node name="gameforge.client"> <filter level="debug" output="c" /> </node> <node name="gameforge.server"> <filter level="error" output="f" /> </node> <node name="gameforge.commun.msg"> <filter status="false" /> </node> </log>
Dans cette exemple il y a deux sections :
- Celle ou on définit les flux de sortie
- Les règle de filtrage
Les flux de sortie
<output id="f" type="file"> <propertie name="Blablabla-%yyyy%mm%dd-%hh%mm%ss-%unique"/> <propertie format="%hh%mm%ss%mmmm-%t [%node - %type] %message"/> <propertie maxsize="200k" /> </output>
On définit un flux de sortie qu'on nomme f de vers un fichier.
On lui associe aussi un certain nombre de propriétés :
- le nom du fichier qui sera la concaténation de :
- d'une chaine statique Blablabla-
- de %yyyy%mm%dd- avec :
- %yyyy l'année sous forme de quatre digit (1997, 2006...)
- %mm le mois sous forme de deux digites (01, 07, 11...)
- %dd la journée sous dorme de deux digite (01, 09, 31 ...)
- de %hh%mm%ss- avec :
- %hh l'heure sous forme de deux digites.
- %mm le mois sous forme de deux digites.
- %ss le nombre de seconde sous forme de deux digites.
- de %unique par défaut vaut 1 mais si par exemple le fichier Blablabla-20060430-140304-1 existe deja il prendra 2 comme valeur et si Blablabla-20060430-140304-2 existe deja il prendra la valeur de 3...
- le format de sortie
- %hh l'heure avec deux digites
- %mm les minutes avec deux digites.
- %ss les secondes avec deux digites.
- %mmmmm les mili seconde avec quatres digites.
- %t le numero du thread
- %node le nom du node (".", "gameforge.commun.message", "gameforge.client"...)
- %type le type de message (debug, error, info...)
- %message le message
- la taille maximum des fichiers (ici 200 koctets)
<output id="c" type="stdout"> <propertie format="%hh%mm%ss%mmmm-%t"/> </output> <output id="e" type="stderr"> <propertie format="%hh%mm%ss%mmmm-%t"/> </output>
On définit deux flux de sortie, l'une vers la sortout standard et l'autre vers la sortie d'erreur.
Les règles de filtrage
<node name="."> <filter level="debug" output="f" /> <filter level="error" output="e" /> </node>
C'est la règle de filtrage par défault, toutes les autres vont "dériver" d'elles. Cette règle dit :
- que tous messages de niveau debug ou supérieur vont être envoyés vers le flux f.
- que tous messages de niveau error ou supérieur vont être envoyés vers le flux e.
<node name="gameforge.client"> <filter level="debug" output="c" /> </node>
Cet règle de filtrage "dérive" de :
- "gameforge" (qui n'existe pas)
- "." qui existe.
donc par défaut elle a deux règles de filtrage si elle n'avait eu aucune règle spécifique. Mais dans ce cas là, elle a une règle que tous les messages de niveau debug ou supérièur vont être envoyés vers le flux c.
<node name="gameforge.commun.msg"> <filter status="false" /> </node>
Cet règle de filtrage "dérive" de :
- "gameforge.commun" (qui n'existe pas)
- "gameforge" (qui n'existe pas)
- "." qui existe.
donc par défaut elle a deux règles de filtrage si elle n'avait eu aucune règle spécifique. Mais dans ce cas là, il y a une règle que tous les log soient ignorés.

