Configurer régulièrement des journaux d'intervalle et d'impression
Après avoir reçu une demande, le journal est imprimé régulièrement via LOG4J. La description des exigences est la suivante: Le journal doit pouvoir être imprimé régulièrement et l'intervalle de temps peut être apparié. En parlant de timing, tout d'abord, je pense à la classe DailyrollingFileAppender. Divers timings. Selon DatePattern, vous pouvez vous référer à la classe SimpledateFormat. Certains paramètres de synchronisation courants sont les suivants:
Grâce à l'observation, j'ai constaté qu'il n'y a pas de format de date similaire à N minutes, j'ai donc écrit une classe personnalisée basée sur la classe DailyrollingFileAppender. Le processus est le suivant:
1) Copiez le code source de la classe DailyrollingFileAppender et renommez-le MinuterollingAPPender. Afin de le configurer dans log4j.xml, ajoutez l'intervalle d'élément de configuration et ajoutez les méthodes d'ensemble et d'obtention;
Int intervaltime privé = 10;
2) Étant donné que la classe DailyrollingFileAppender utilise la classe RollingCalendar pour calculer le temps d'intervalle suivant et doit passer l'intervalle de paramètre, la classe RollingCalendar est modifiée en classe interne; Étant donné que sa méthode consiste à calculer l'heure de la prochaine action de roulement basée sur DatePattern, aucun autre mode de temps n'est nécessaire pour le moment, la méthode de modification est la suivante:
Date publique getNextCheckDate (date maintenant) {this.setTime (maintenant); this.set (calendar.second, 0); this.set (calendar.millisecond, 0); this.add (calendar.minute, intervaltime); return getTime (); }3) Lorsque le mode temps est égalé en fonction des minutes, le mode de temps doit être désactivé. Changez-le en finale statique et supprimez les paramètres de DatePattern dans la méthode GET, SET et le constructeur MinuterollingAPPender dans la réponse.
chaîne statique privée datepattern = "'.'yyyy-mm-dd-hh-mm'.log'";
De même, ComposeCheckPeriod (), qui dessert plusieurs DatePatterns, peut également être supprimé; La transformation est terminée et la catégorie de produit finie est la suivante:
package net.csdn.blog; Importer java.io.file; Importer java.io.ioException; Importer java.io.InterrupteDioException; import java.text.simpledateFormat; import java.util.calendar; Importer java.util.date; import java.util.gregoriancalendar; import org.apache.log4j.fileAPpender; import org.apache.log4j.layout; import org.apache.log4j.helpers.loglog; import org.apache.log4j.spi.loggingEvent; / ** * Timeable by Minute Appender * * @author coder_xia * * / classe publique MinuterollingAPPender étend FileAPpender {/ ** * Le modèle de date. Par défaut, le modèle est défini sur "'.'yyyy-mm-dd" * signifiant le roulement quotidien. * / private static string datepattern = "'.'yyyy-mm-dd-hh-mm'.log'"; / ** * Temps d'intervalle, unité: minutes * / private int intervaltime = 10; / ** * Le fichier journal sera renommé à la valeur de la variable planifiéefileName * lorsque l'intervalle suivant sera entré. Par exemple, si la période de roulement * est d'une heure, le fichier journal sera renommé à la valeur de * "ScheduledFileName" au début de l'heure suivante. * * Le temps de précision lorsqu'un renversement se produit en fonction de l'activité de journalisation. * / chaîne privée ScheduledFileName; / ** * La prochaine fois que nous estimons qu'un renversement devrait se produire. * / private long nextcheck = System.currenttimemillis () - 1; Date maintenant = new Date (); SDF SimpledateFormat; RollingCalendar RC = new RollingCalendar (); / ** * Le constructeur par défaut ne fait rien. * / public minuterollingapprender () {} / ** * Instancier un <code> minuterollingapprender </code> et ouvrir le fichier * conçu par <code> filename </code>. Le nom de fichier ouvert deviendra la destination * ouput de cet appender. * / public MinuterollingAPPender (mise en page de mise en page, nom de fichier de chaîne) lève ioException {super (disposition, nom de fichier, true); activateOptions (); } / ** * @return The Intervaltime * / public int getIntervaltime () {return intervaltime; } / ** * @param intervaltime * L'intervalle pour définir * / public void setIntervaltime (int intervaltime) {this.intervaltime = intervaltime; } @Override public void acticateOptions () {super.activateOptions (); if (filename! = null) {now.settime (System.Currenttimemillis ()); sdf = new SimpledateFormat (datePattern); Fichier fichier = nouveau fichier (nom de fichier); ScheduledFileName = filename + sdf.format (new Date (file.lastmodified ())); } else {LogLog .Error ("Les options de fichier ou datePattern ne sont pas définies pour l'appender [" + name + "]."); }} / ** * Retrapez le fichier actuel dans un nouveau fichier. * / void Rollover () lève ioException {String DADFILENAME = FILENAME + SDF.Format (maintenant); // Il est trop tôt pour rouler car nous sommes toujours dans les limites de l'intervalle de courant. Le roulement se produira une fois que le // intervalle suivant sera atteint. if (scheduledFileName.equals (daDFileName)) {return; } // Fermez le fichier actuel et renommez-le à DaDFileName this.closeFile (); File cible = nouveau fichier (scheduledFileName); if (cible.exists ()) {Target.Delete (); } Fichier file = nouveau fichier (nom de fichier); booléen result = file.renameto (cible); if (result) {logLog.debug (filename + "->" + scheduledFileName); } else {loglog.error ("n'a pas renommé [" + filename + "] à [" + scheduledFileName + "]."); } Essayez {// Cela fermera également le fichier. C'est OK car plusieurs opérations de fermeture sont sûres. this.setFile (nom de fichier, true, this.bufferedio, this.buffeSize); } catch (ioException e) {errorHandler.Error ("setFile (" + filename + ", true) a échoué."); } ScheduledFileName = DADFILENAME; } / ** * Cette méthode différencie MinuterollingAPPender de sa super classe. * * <p> * Avant de se connecter réellement, cette méthode vérifiera s'il est temps de faire * un roulement. Si c'est le cas, il planifiera le prochain temps de roulement, puis * Rollover. * * / @Override Protected void subappend (loggingEvent Event) {long n = System.currenttimemillis (); if (n> = nextCheck) {now.settime (n); NextCheck = rc.GetNextCheckMillis (maintenant); essayez {rollover (); } catch (ioException ioe) {if (ioe instanceof interrupteIoException) {thread.currentThread (). Interrupt (); } Loglog.error ("rollover () a échoué.", Ioe); }} super.subappend (événement); } / ** * RollingCalendar est une classe d'assistance à MinuterollingAppender. Étant donné un type de périodicité et l'heure actuelle, il calcule le début de l'intervalle suivant *. * * / class RollingCalendar étend GregorianCalendar {private static final long SerialVersionUID = -3560331770601814177l; RollingCalendar () {super (); } public long getNextCheckMillis (date maintenant) {return getNextCheckDate (maintenant) .getTime (); } Public Date getNextCheckDate (Date Now) {this.setTime (maintenant); this.set (calendar.second, 0); this.set (calendar.millisecond, 0); this.add (calendar.minute, intervaltime); return getTime (); }}}Le fichier de configuration de test est le suivant:
<? xml version = "1.0" encoding = "utf-8"?> <! doctype log4j: système de configuration "log4j.dtd"> <log4j: configuration xmlns: log4j = "http://jakarta.apache.org/log4j/"> <apmander = "myfile"> <paramn name = "file" value = "log4jtest.log" /> <param name = "append" value = "true" /> <param name = "interaltime" value = "2" /> <alout> <param name = "ConversionPattern" value = "% p% d (% c:% l) -% M% n" /> </ Layout> </ appender> <root> <priorité ref = "myfile" /> </ root> </ log4j: configuration>
En ce qui concerne l'implémentation de synchronisation, vous pouvez également utiliser l'implémentation du temporisateur fourni par Java, qui élimine le calcul et la comparaison du temps à chaque fois que vous enregistrez le journal. La différence consiste en fait à configurer un thread et à appeler la méthode de roulement. La mise en œuvre est la suivante:
package net.csdn.blog; Importer java.io.file; Importer java.io.ioException; import java.text.simpledateFormat; Importer java.util.date; import java.util.timer; import java.util.timertask; import org.apache.log4j.fileAPpender; import org.apache.log4j.layout; import org.apache.log4j.helpers.loglog; classe publique TimertaskRollingPender étend FileAPpender {/ ** * Le modèle de date. Par défaut, le modèle est défini sur "'.'yyyy-mm-dd" * signifiant le roulement quotidien. * / private static final string datePattern = "'.'yyyy-mm-dd-hh-mm'.log'"; / ** * Temps d'intervalle, unité: minutes * / private int intervaltime = 10; SimpledateFormat sdf = new SimpledateFormat (datePattern); / ** * Le constructeur par défaut ne fait rien. * / public TimertaskRollingAPPender () {} / ** * Instancier un <code> TimertaskRollingAPPender </code> et ouvrez le fichier * conçu par <code> FileName </code>. Le nom de fichier ouvert deviendra la destination * ouput de cet appender. * / public TimerTaskRollingAPPender (Layout Layout, String FileName) lève ioException {super (Layout, FileName, true); activateOptions (); } / ** * @return The Intervaltime * / public int getIntervaltime () {return intervaltime; } / ** * @param intervaltime * L'intervalle pour définir * / public void setIntervaltime (int intervaltime) {this.intervaltime = intervaltime; } @Override public void acticateOptions () {super.activateOptions (); Timer Timer = new Timer (); TIMER.Schedule (new LogTimemertask (), 1000, intervaltime * 60000); } class LogTimertask étend Timertask {@Override public void run () {String DaDFileName = filename + sdf.format (new Date ()); CloseFile (); File Target = nouveau fichier (DADFILENAME); if (cible.exists ()) Target.delete (); Fichier fichier = nouveau fichier (nom de fichier); booléen result = file.renameto (cible); if (result) LogLog.debug (nom de fichier + "->" + DADFILENAME); else LogLog.Error ("Échec de renommer [" + FileName + "] à [" + DADAFILENAME + "]."); essayez {setFile (nom de fichier, true, tampono, tamponize); } catch (ioException e) {errorHandler.Error ("setFile (" + filename + ", true) a échoué."); }}}}}Cependant, il y a deux problèmes avec la mise en œuvre ci-dessus:
1) concurrence
Un endroit où des problèmes de concurrence peuvent se produire après l'appel CloseFile () dans Run (), la méthode Subappend () se produit juste pour écrire le journal. En ce moment, le fichier est fermé et l'erreur suivante sera signalée:
java.io.ioException: Stream fermé à Sun.nio.cs.Streamincoder.enSureOpen (source inconnue) à Sun.nio.cs.Streamenceder.write (source inconnue) sur Sun.nio.Cs.Stremencer.Write (Source inconnue) sur Java.io.wouthewwrewwriter) Source (UNNID ..................La solution est relativement simple. Faites simplement synchronisé la méthode Run () et ajoutez le mot-clé synchronisé; Cependant, l'auteur n'a pas résolu la situation où le journal peut être perdu s'il veut vraiment l'écrire et que la vitesse d'écriture est assez rapide;
L'utilisation de la minuterie pour implémenter est plus simple, mais si les tâches de la minuterie sont exécutées trop longtemps, elles occuperont exclusivement l'objet Timer, ce qui rend les tâches suivantes incapables d'être exécutées à tout moment. La solution est également plus simple. La classe de minuterie de la version du pool de threads ScheduleDexeCutOrService est utilisée pour implémenter ce qui suit:
/ ** * * / package net.csdn.blog; Importer java.io.file; Importer java.io.ioException; import java.text.simpledateFormat; Importer java.util.date; Importer java.util.concurrent.executors; Importer java.util.concurrent.TimeUnit; import org.apache.log4j.fileAPpender; import org.apache.log4j.layout; import org.apache.log4j.helpers.loglog; / ** * @Author coder_xia * <p> * Utilisez ScheduleDexeCutOrService pour implémenter les journaux d'impression de configuration chronométrés * <p> * * / classe publique planifiéexECUTORServiceAppender étend FileAppender {/ ** * le modèle de date. Par défaut, le modèle est défini sur "'.'yyyy-mm-dd" * signifiant le roulement quotidien. * / private static final string datePattern = "'.'yyyy-mm-dd-hh-mm'.log'"; / ** * Temps d'intervalle, unité: minutes * / private int intervaltime = 10; SimpledateFormat sdf = new SimpledateFormat (datePattern); / ** * Le constructeur par défaut ne fait rien. * / public ScheduleDExECUtorServiceAppender () {} / ** * Instancier un <code> ScheduleDexeCutOrServiceAppender </code> et ouvrez le fichier * conçu par <code> nom de fichier </code>. Le nom de fichier ouvert deviendra * la destination ouput de cet appender. * / public ScheduleDExECUTOrServiceAppender (Layout Layout, String FileName) lève ioException {super (Layout, FileName, true); activateOptions (); } / ** * @return The Intervaltime * / public int getIntervaltime () {return intervaltime; } / ** * @param intervaltime * L'intervalle pour définir * / public void setIntervaltime (int intervaltime) {this.intervaltime = intervaltime; } @Override public void acticateOptions () {super.activateOptions (); Exécuteurs.NewSingLetHreadsCheDuleDexeCutor (). ScheduleAtFixeDrate (new LogTimertask (), 1, Intervaltime * 60000, timeunit.milliseconds); } class LogTimertask implémente Runnable {@Override public void run () {String DADFILENAME = FILENAME + SDF.Format (new Date ()); CloseFile (); File Target = nouveau fichier (DADFILENAME); if (cible.exists ()) Target.delete (); Fichier fichier = nouveau fichier (nom de fichier); booléen result = file.renameto (cible); if (result) LogLog.debug (nom de fichier + "->" + DADFILENAME); else LogLog.Error ("Échec de renommer [" + FileName + "] à [" + DADAFILENAME + "]."); essayez {setFile (nom de fichier, true, tampono, tamponize); } catch (ioException e) {errorHandler.Error ("setFile (" + filename + ", true) a échoué."); }}}}}En ce qui concerne la mise en œuvre du timing, c'est presque la fin. La valeur par défaut consiste à générer un nouveau fichier journal en 10 minutes. Vous pouvez le définir vous-même lors de la configuration. Cependant, il y a un danger caché. Si la personne de configuration ne sait pas que l'intervalle de temps est des minutes, si vous pensez que c'est quelques secondes, vous en avez 600 et ouvrez ensuite un débogage pour générer un fichier journal avec G, ce sera certainement une catastrophe. La transformation suivante consiste à combiner la taille maximale du RollingFileAPpender et le nombre maximum de fichiers de sauvegarde à correspondre, puis l'améliorer à nouveau. La prochaine fois, nous continuerons à décrire le processus de transformation.
Ajouter la configuration du nom du module
J'ai mentionné l'implémentation de classe personnalisée de l'impression chronométrée LOG4J, donc je ne parlerai pas de la taille et du nombre spécifié de fichiers de sauvegarde. Je peux l'ajouter du code de copie de classe RollingFileAppender à la classe personnalisée précédente. La seule chose qui doit être résolue est le problème de concurrence, c'est-à-dire que lorsque le fichier de renommée est fermé, une erreur de flux de sortie fermé sera signalée lorsqu'un événement de journal se produira.
Il y a maintenant un tel scénario d'application, et il y en a souvent:
1. Le projet contient plusieurs projets différents;
2. Le même projet contient différents modules.
Pour le premier cas, vous pouvez configurer log4j <catogery = "test">, puis utiliser la méthode suivante lors de la génération de journalisation:
Logger logger = logger.getLogger ("test");Dans le deuxième cas, nous espérons imprimer différents modules dans le même fichier journal, mais nous espérons imprimer le nom du module dans le journal pour localiser le problème lorsqu'il y a un problème. Par conséquent, cet article doit ajouter le nom de module de configuration à la classe appender. Commençons la transformation ci-dessous. Contrairement à l'impression temporelle, nous utilisons la classe RollingFileAppender comme classe de base pour la transformation.
Tout d'abord, ajoutez l'élément de configuration moduleName et ajoutez les méthodes GET et SET;
Puisqu'il est hérité de RollingFileAPpender, vous n'avez qu'à formater les données dans loggingEvent dans subappend (), ajoutez la méthode Formatinfo pour formater les données et le code est omis;
La catégorie de produit finale est la suivante:
package net.csdn.blog; import org.apache.log4j.category; import org.apache.log4j.rollingFileAPpender; import org.apache.log4j.spi.loggingEvent; / ** * @author coder_xia * * / public class moduleAPender étend rollingFileAPpender {private string modulename; / ** * @return the moduleName * / public String getModuLename () {return moduleName; } / ** * @param moduleName * le moduleName pour définir * / public void setModuLename (string moduleName) {this.modulename = modulename; } / ** * Format Imprimer Contenu * * @param Event * Event * @return msg * / private String Formatinfo (Event LoggingEvent) {StringBuilder sb = new StringBuilder (); if (moduleName! = null) {sb.append (modulename) .append ("|"); sb.append (event.getMessage ()); } return sb.toString (); } @Override public void subappend (event loggingEvent) {String msg = formatinfo (événement); super.SubAllAnd (new LoggingEvent (catégorie.class.getName (), event .getLogger (), event.getLevel (), msg, null)); }}