Bien logger ses mods



  • Prérequis

    Avoir des connaissances de base en Java (variables, conditions, fonctions et classes)
    Connaître la structure globale d'un mod (classe principale, fonctions)

    Introduction

    Nous allons, dans ce tutoriel, apprendre comment bien Logger ses mods. Tout d'abord, que signifie "Logger" ?

    • Logger est simplement un terme de programmation venant de l'anglais "To log", c'est-à-dire, enregistrer. Cela permet au programmeur de débugger son programme plus efficacement. Les logs sont enregistrés dans un fichier texte et permettent aux utilisateurs du mod de transmettre au programmeur les erreurs survenues lors de l'utilisation du mod.
    • Débugger : fait de déceler et corriger les bugs ou erreurs de son programme

    La parenthèse de culture informatique close, nous pouvons commencer par créer notre Logger.

    La classe principale du Logger

    Créez tout d'abord une classe. De mon côté, je vais la nommer LoggerManager.

    public class LoggerManager{
    }
    

    Jusque là, pas de problème. Nous allons ajouter un constructeur et quelques variables qui vont nous permettre de mettre en route notre Logger.

    public class LoggerManager {
    
        public static LoggerManager instance;
        private Calendar calendar = Calendar.getInstance();
        private Logger logger;
    
        public LoggerManager(Logger logger, Path logFile){//Premier argument : le logger, deuxième argument : le fichier log.txt
            //On instancie notre logger
            this.logger = logger;
    
            //On crée associe notre Handler au logger
            this.logger.addHandler(new LoggerHandler(logFile));
    
            //On met l'instance de la classe dans elle même (oui c'est curieux, mais ça marche)
            instance = this;
        }
    }
    

    Les Handler sont des objets qui se chargent de publier les messages du Logger. Vous devriez avoir une erreur, c'est normal, il nous faut créer notre propre Handler. Mais avant cela, finissons la structure de notre LoggerManager. Un petit accesseur (à insérer à la suite de notre constructeur, dans la classe) qui va permettre d'accéder au Logger depuis une autre classe :

        public Logger getLogger() {
            return this.logger;
        }
    }
    
    

    Une fonction utile qui va permettre d'insérer la date et l'heure dans notre fichier log.txt :

        public String getDate() {
            return (calendar.get(Calendar.DAY_OF_MONTH) + "/" + calendar.get(Calendar.MONTH) + "/" + calendar.get(Calendar.YEAR)
        + "-" + calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE) + ":" + calendar.get(Calendar.SECOND));
        }
    

    Voilà, notre LoggerManager est terminé. Nous allons maintenat pouvoir passer à notre Handler (enfin 😛 ).

    La classe du Handler

    Le Handler donc ^^. Je vous propose de créer une classe interne (une classe dans une classe (oui, c'est tordu, et oui on peut faire ça à l'infini)). Écrivez tout d'abord ceci dans la classe LoggerManager, au même niveau que la fonction de la date, juste en dessous) :

    private class LoggerHandler extends Handler {
    
    }
    

    Vous allez avoir une erreur, faites les importations nécessaires (Ctrl + Shift + O). Ensuite, placez les méthodes et un constructeur à l'intérieur de cette nouvelle classe :

    public LoggerHandler(Path logFile) {
    
    }
    
    @Override
    public void publish(LogRecord record){
    
    }
    
    @Override
    public void flush(){}
    
    @Override
    public void close() throws SecurityException{
    
    }
    

    Ces méthodes sont automatiquement appelées par notre Logger dés qu'un message est écrit. Elles permettent de modifier le message et d'effectuer les tâches que l'on veut dessus.

    • publish() est appelée lors de la publication d'un message via le Logger
    • flush() est appelée lors de la "purge" du Logger. Cela permet en fait de vider certaines ressources pour libérer de la mémoire et sauvegarder certaines informations ("vider le cache" sur Firefox par exemple). Nous ne nous en servirons pas.
    • close() est appelée lors de la fermeture du Logger. Nous allons nous en servir pour fermer le fichier log.txt

    Ajoutons une variable de classe qui deviendra le flux d'écriture de notre fichier log.txt.

    private BufferedWriter writer = null;
    

    Dans notre constructeur, nous allons initialiser la variable et créer le fichier s'il n'existe pas ou l'écraser, le cas échéant :

    public LoggerHandler(Path logFile){
        try{
            if(Files.exists(logFile)){
                Files.delete(logFile);//On écrase manuellement les fichiers
                Files.createFile(logFile);
            }
            else{
                Files.createFile(logFile);
            }
            //On initialise le flux d'écriture en UTF-8
            this.writer = Files.newBufferedWriter(logFile, StandardCharsets.UTF_8);
        }catch(Exception e){
            System.err.println("[GRAVE] Erreur lors de la création/délétion du fichier log.");
        }
    }
    

    Ensuite nous allons demander à notre Handler d'écrire le message posté dans un fichier :

    try{
        //On écrit le message dans le fichier log
        writer.write(record.getMessage() + "<" + getDate() + "> [" + record.getLevel().getLocalizedName() + "] " + record.getMessage() + "\n");
    }catch(Exception e){
        //En cas de problème, on marque l'erreur
        System.err.println("[GRAVE] Erreur lors de l'écriture du fichier log.");
        e.printStackTrace();
    }
    

    N'oublions pas de fermer le fichier à la fin, pour éviter les erreurs lors de la lecture du fichier :

    @Override
    public void close() throws SecurityException{
        System.out.println("<" + getDate() + "> [Infos] Fermeture des ressources…");
        try{
            writer.flush();
            writer.close();
        }catch(Exception e){}
        System.out.println("<" + getDate() + "> [Infos] OK !");
    }
    

    Note : Cette méthode est appelée lors de la femeture du programme, il peut être utile de fermer les ressources d'autres classes en utilisant celle-ci ! 🙂

    Nous avons terminé notre Logger. Voici la classe complète au cas où je vous aurai perdu en cours de route :
    LoggerManager.java :

    
    import java.io.BufferedWriter;
    import java.nio.charset.StandardCharsets;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.util.Calendar;
    import java.util.logging.Handler;
    import java.util.logging.LogRecord;
    import java.util.logging.Logger;
    
    public class LoggerManager {
    
        public static LoggerManager instance;
        private Calendar calendar = Calendar.getInstance();
        private Logger logger;
    
        public LoggerManager(Logger logger, Path logFile){
            //On instancie notre logger
            this.logger = logger;
    
            //On crée associe notre Handler au logger
            this.logger.addHandler(new LoggerHandler(logFile));
    
            //On met l'instance de la classe dans elle même (oui c'est curieux, mais ça marche)
            instance = this;
        }
    
        public Logger getLogger(){
            return this.logger;
        }
    
        public String getDate(){
            return (calendar.get(Calendar.DAY_OF_MONTH) + "/" + calendar.get(Calendar.MONTH) + "/" + calendar.get(Calendar.YEAR)
            + "-" + calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE) + ":" + calendar.get(Calendar.SECOND));
        }
    
        private class LoggerHandler extends Handler{
    
            private BufferedWriter writer = null;
    
            public LoggerHandler(Path logFile){
                try{
                    if(Files.exists(logFile)){
                    Files.delete(logFile);
                    Files.createFile(logFile);
                }
                else{
                    Files.createFile(logFile);
                }
                this.writer = Files.newBufferedWriter(logFile, StandardCharsets.UTF_8);
                }catch(Exception e){
                    System.err.println("[GRAVE] Erreur lors de la création/délétion du fichier log.");
                }
            }
    
            @Override
            public void publish(LogRecord record){
                try{
                    //On écrit le message dans le fichier log
                    writer.write(record.getMessage("<" + getDate() + "> [" + record.getLevel().getLocalizedName() + "] " + record.getMessage()) + "\n");
                }catch(Exception e){
                    //En cas de problème, on marque l'erreur
                    System.err.println("[GRAVE] Erreur lors de l'écriture du fichier log.");
                    e.printStackTrace();
                }
            }
    
            @Override
            public void flush(){}
    
            /**
            * Lors de la fermeture du `Logger`, on ferme le fichier.
            */
            @Override
            public void close() throws SecurityException{
                System.out.println("<" + getDate() + "> [Infos] Fermeture des ressources…");
                try{
                    writer.flush();
                    writer.close();
                }catch(Exception e){}
                System.out.println("<" + getDate() + "> [Infos] OK !");
            }
        }
    }
    

    Grâce à cette classe, vous pouvez récupérer le logger depuis toutes les autres classes !

    Comme je suis généreux, je vous montre comment utiliser cette classe ! 😛

    Comment utiliser notre nouvelle classe

    Tout d'abord, il faut initialiser le Logger. Pour ce faire, placez ce code au tout début de votre PreInit(), dans votre classe principale.

    new LoggerManager(event.getModLog(), Paths.get("modid.txt"));
    

    Remplacez le "modid" par votre modid mais laissez le ".txt".
    Maintenant, pour écrire quelque chose dans la console, vous devrez faire :

    Logger logger = LoggerManager.instance.getLogger();
    logger.info("Je suis un message d'information");
    logger.warning("Je suis un message d'avertissement");
    logger.severe("Je suis un message d'erreur");
    

    Quand utiliser les fonctions de Logger ?

    Quand vous voulez, mais surtout lorsque votre mod s'initialise, c'est là que votre mod est susceptible de planter ! Par exemple pour un fichier de configuration (exemple non fonctionnel) :

    logger.info("Initialisation du fichier de configuration…");
    if(Files.exists(cfg)){
        Registry.loadConfig();
        logger.info("Fait !");
    }
    else{
        logger.warning("Fichier de configuration inexistant, création en cours...");
        try{
            Files.createFile(cfg);
            if(Files.exists(cfg){
                Registry.saveDefaultConfig();
                logger.info("Fait !");
            }
            catch(Exception e){
                logger.severe("Impossible de créer le fichier de configuration '" + path.toString() + "' ! Raison : " + e.getCause());
                e.printStackTrace();
            }
        }
    }
    

    Vous pouvez constater que c'est très utile !

    J'espère que ce tutoriel vous a aidé, n'hésitez pas à poster vos questions et vos problèmes sur le topic ! Sur ce,
    Happy Modding ! 😄


  • Moddeurs confirmés Rédacteurs Administrateurs

    Tutoriel validé, il est beaucoup mieux fait et beaucoup plus utile que le premier, bravo !



  • @'robin4002':

    Tutoriel validé, il est beaucoup mieux fait et beaucoup plus utile que le premier, bravo !

    Merci ^^ D'autant plus que j'ai utilisé le logger du PlusOuMoins x)


  • Moddeurs confirmés Rédacteurs Modérateurs Administrateurs

    new LoggerManager(event.getModLog(), Paths.get("modid.txt"));
    

    Pourquoi ne peut-on pas changé le .txt en .log, par exemple?



  • @'Superloup10':

    new LoggerManager(event.getModLog(), Paths.get("modid.txt"));
    

    Pourquoi ne peut-on pas changé le .txt en .log, par exemple?

    On peut mais c'est mieux d'utiliser un format reconnu par 80% des OS et qui pourra être ouvert facilement par les joueurs.


  • Moddeurs confirmés Rédacteurs Modérateurs Administrateurs

    Bah, les fichiers de config en .cfg ne sont pas ouvert par tout les OS, non plus.



  • @'Superloup10':

    Bah, les fichiers de config en .cfg ne sont pas ouvert par tout les OS, non plus.

    Ouais mais ça c'est pas moi qui les ai faits 😛


  • Moddeurs confirmés Rédacteurs Modérateurs Administrateurs

    Une question, je suis entrain de coder le fichier de log et j'ai une erreur sur cette ligne:

    writer.write(record.getMessage("<" + getDate() + "> [" + record.getLevel().getLocalizedName() + "] " + record.getMessage()) + "\n");
    

    J'en ai déduis que cette ligne ne fonctionne que pour les mods Minecraft, non?

    Donc que suis-je sensé mettre à la place?


  • Moddeurs confirmés

    @Superloup10 : Tu as quoi comme erreur ? Normalement ça ne dépend pas de Minecraft.

    @EclipseOnFire :
    Pourquoi as-tu utiliser un BufferedWriter et pas un PrintWriter ?
    Pourquoi le constructeur de ton loggerManager est en public ? Alors qu’apparemment de la manière dont tu l'a écrit, c'est un singleton ? Si tu fais des accès concurrentiels vers ton loggers, il va crash et faire planter tout le mod.

    Comment logger dans plusieurs fichiers ? (avec ta méthode on ne peut pas :P)

    TOUS les OS gèrent les .log et les .cfg.

    Je trouve qu'il y a plus simple que ce que tu fais par moment.
    Je vais tenter de proposé un tutoriel similaire au tiens, mais avec ma vision des choses quand j'aurais le temps 😉

    En ce moment je suis pas mal occupé ^^ En dehors de mon taff, je suis entrain de modifier une application pour qu'elle respecte le pattern-design MVC et c'est pas gagné xD Puis il faut aussi que je m'occupe de cette histoire de balise de traduction pour le forum 😉

    En réalité je suis incapable de faire des tutos pour forge, car je n'ai pas encore vraiment mis les pieds dans l'API.
    Je crée juste une gamme d'outils pour les moddeurs minecraft qui se veut indépendante de toute API.
    Mais logger l’exécution de son code ne relève pas de l'API forge 😉


  • Moddeurs confirmés Rédacteurs Modérateurs Administrateurs

    Eclipse me dit:
    The method getMessage() in the type LogRecord is not applicable for the arguments (String)



  • Une fonction get ne prend pas d'arguments, sauf peut-être un identifiant ( Numérique ) normalement.


  • Moddeurs confirmés

    getMessage() ne prend pas de paramètre.
    Je ne comprend pas ce que tu veux faire exactement. Quel message veux-tu stocker ?
    pourquoi tu n'utilise pas LoggerManager.instance.getLogger().info("ton_message_d'information") ?
    C'est très crade comme notation mais je fais ce que je peux avec ce qui est marqué dans le tuto ^^

    Non Gugu42, sinon on aurait "The method getMessage(int) in the type LogRecord is not applicable for the arguments (String)"



  • Je parle en général, non pas dans ce cas précis 😉

    Parfois, des méthodes get prennent un int, mais en général, pas ce cas précis 😛


  • Moddeurs confirmés

    Autant pour moi, j'ai mal compris désolé 🙂


  • Moddeurs confirmés Rédacteurs Modérateurs Administrateurs

    Je me doutais que getMessage ne prends pas de paramètre donc j'en déduis que le tuto comporte une erreur à ce niveau là.


  • Moddeurs confirmés

    Oui, j'ai testé, le tutoriel comporte bien une erreur 😉
    @EclipseOnFire voulait surement voir si on suivait 😛

    EDIT : http://www.minecraftforgefrance.fr/showthread.php?tid=496&pid=5358#pid5358
    Voila ce que j'ai fais, les sources sont sur github donc si tu veux jeter un oeil @EclipseOnFire, jette un oeil sur ma classe LoggerManager 😉
    C'est pas commenté, mais normalement il n'y a rien de compliquer a comprendre sauf peut-être la manière dont j’implémente le coté bufferisé de ma classe TerminalOutput 😉 Mais bon ça m'évite de lancer le vidage du tampon dans le thread graphique et de gérer les accès concurentiels au contenu du Terminal ^^



  • Oui je viens de voir où est l'erreur. C'est bon c'est corrigé, j'ai dû faire une faute de frappe lorsque j"ai transcrit mon code sur le forum.

    @Superloup
    Change la ligne en :

    writer.write(record.getMessage() + "<" + getDate() + "> [" + record.getLevel().getLocalizedName() + "] " + record.getMessage() + "\n");
    

    Merci de m'avoir signalé le bug, c'est corrigé !

    @Blackout

    Pourquoi as-tu utiliser un BufferedWriter et pas un PrintWriter ?

    Heu parce que là où j'ai développé ce Logger, j'avais besoin d'un BufferedWriter. En plus il me semble avoir lu que les Buffered sont plus rapides et qu'ils sont adaptés à Java NIO2 donc bon…

    Pourquoi le constructeur de ton loggerManager est en public ? Alors qu’apparemment de la manière dont tu l'a écrit, c'est un singleton ? Si tu fais des accès concurrentiels vers ton loggers, il va crash et faire planter tout le mod.

    Heu… Tu peux me montrer un exemple s'il te plait ? Ce sera plus clair pour moi ! 😛

    Comment logger dans plusieurs fichiers ? (avec ta méthode on ne peut pas)

    Rien n'empêche d'avoir plusieurs fichiers de configuration, il suffit juste de créer une nouvelle instance de LoggerManager avec un autre fichier et avec le même Logger. Il suffira ensuite de conserver un seul Logger qui loggera dans tous les autres fichiers, celui qui sera en instance quoi… En plus j'ai voulu faire un Logger simple, pas trop complexe.

    TOUS les OS gèrent les .log et les .cfg.

    Oui tu as raison, mais je ne parle pas de l'encodage ni du format, mais de l'extension. Sous 90% des ordis tu double clic sur un .txt et ça s'ouvre. Essaye sur un .cfg ou un .log, il faudra définir un programme par défaut. Voilà pourquoi. Sinon tu peux le modifier ya pas de problème ^^.

    Au niveau des petites erreurs tu voulais sûrement parler de la suppression du fichier alors que je pourrais simplement mettre le writer en mode "write" ?
    J'ai choisi ça car certains OS ne donnent pas directement la permission au programme d'écrire le fichier. Donc, en créant/supprimant le fichier dés le début, on s'assure d'avoir les permissions. S'il n'y en a pas, ça bugge à cet endroit là, et pas au niveau de l'écriture du fichier.


  • Moddeurs confirmés

    PrintWriter sert a écrire des chaînes de caractères, BufferedWriter sert a écrire des octects de manière générale.
    Ici on n'écrit que des caractères imprimables. Les 2 sont toute aussi performantes, si ce n'est la signification de ton code.
    Quand tu lis le code de quelqu'un et que tu vois un PrintWriter, tu sais qu'il ne s'agit que de chaînes de caractères imprimables 😉

    Pour les accès concurrentiels, renseignes toi sur le multithreading en java 😉

    Pour les extension, il faut que les gens apprennent à s'en servir 😉 tu ne codes pas pour des grand-mères de 90 ans qui ne se mettront jamais a l'informatique. un fichier de log c'est un .log, et indique le contenu du fichier qui généralement n'est pas créé par l'utilisateur, mais de manière automatique. Dans ce cas la, tu peux modifier tout tes fichiers plain-text en .txt, même tes pages php et html 😉 Enfin bref, ça n'a que peu d'importance 😉

    le gros probleme de ta version c'est que si l'écriture échoue, tu ne loggeras rien du tout !



  • @'Blackout':

    PrintWriter sert a écrire des chaînes de caractères, BufferedWriter sert a écrire des octects de manière générale.
    Ici on n'écrit que des caractères imprimables. Les 2 sont toute aussi performantes, si ce n'est la signification de ton code.
    Quand tu lis le code de quelqu'un et que tu vois un PrintWriter, tu sais qu'il ne s'agit que de chaînes de caractères imprimables 😉

    Pour les accès concurrentiels, renseignes toi sur le multithreading en java 😉

    Pour les extension, il faut que les gens apprennent à s'en servir 😉 tu ne codes pas pour des grand-mères de 90 ans qui ne se mettront jamais a l'informatique. un fichier de log c'est un .log, et indique le contenu du fichier qui généralement n'est pas créé par l'utilisateur, mais de manière automatique. Dans ce cas la, tu peux modifier tout tes fichiers plain-text en .txt, même tes pages php et html 😉 Enfin bref, ça n'a que peu d'importance 😉

    le gros probleme de ta version c'est que si l'écriture échoue, tu ne loggeras rien du tout !

    Ah d'accord ^^. Bah c'est sûr que c'est un peu mieux oui 😛

    Ah oui je vois… Je pensais pas que ça pouvait se faire pour un mod. Il me suffirait donc de déclarer l'écriture de fichier "synchronized" ?
    Seulement les entités Minecraft ne devraient-t-elles pas être synchronized puisque les entités sont actualisées à chaque tick en plus d'être modifiées par les mods et les events Forge ?

    Oui je suis d'accord, mais je dis pas qu'on peut pas le changer hein...

    Si l'écriture échoue, je ne logge rien dans mon fichier log, c'est sûr, mais je logge quand même dans la console qui est transcrite dans les logs de Forge et dans la console du launcher (s'il est resté ouvert pendant que MC s'exécute).


  • Moddeurs confirmés Modérateurs

    question: une faut faire ce pour chaque mod? ou on peut créer un mod pour tout notre modpack?


Log in to reply