Accueil / Articles PiApplications. / La plate-forme Java / Le langage

Historisation avec log4j

Description du mécanisme d'historisation.

Initialement, le projet log4j était un projet indépendant de la plate-forme Java. Il a depuis était intégré à la plate-forme via le package java.util.logging. L'historisation est un mécanisme qui repose sur une arborescence de journaux. Cette arborescence repose sur le nommage des journaux ou chaque branche est séparée de la précédente par un point. En principe, les journaux sont conçus pour être affectés par package. Il est alors d'usage de leur donner le nom du package au profit duquel est assuré l'historisation. Cela n'est cependant pas une règle et chaque journal peut disposer de son propre nom et se voir affecter à une historisation particulière. Il est même possible de créer des journaux "anonymes" dont les noms ne seront pas stockés dans l'espace de nom des journaux.

L'intérêt de disposer de plusieurs journaux tient au fait qu'il est possible de fixer à chacun d'eux un niveau de filtrage différent. Il est donc possible d'affecter des journaux à tout ou partie du code selon les besoins. La racine du système est un journal statique (objet de classe Logger) toujours accessible via la méthode statique Logger.getLogger.

Les journaux sont gérés via un objet de classe LogManager. Cet objet statique est unique dans la JVM considérée. Au lancement de l'application, deux propriétés système permettent de contrôler la configuration initiale :

  1. java.util.logging.config.class
  2. ava.util.logging.config.file

La propriété java.util.logging.config.class définit une classe chargée de fixer la configuration initiale du système d'historisation.

Lorsque cette propriété n'est pas définie, la propriété java.util.logging.config.file est alors cherchée pour initialiser le système d'historisation.

Lorsqu'aucune de ces deux propriétés n'est définie, le système utilise une configuration par défaut accessible dans l'arborescence de la JVM sous le répertoire lib/logging.properties.

Quelque soit le mécanisme d'initialisation mis en œuvre, il conduit à lire un fichier de propriétés qui précise :

Toutes les propriétés de type "{journal}.level" précisent le niveau de filtrage du journal. Sans précision du nom de journal, la propriété .level fixe alors le niveau de filtrage des journaux racine.

Bien que les journaux reçoivent la plupart du temps des messages déjà mise en forme, certaines méthodes de l'objet Logger acceptent une interface en tant que trace. Cette interface est responsable de l'implémentation du code qui produit le message. Voici un exemple d'utilisation :

class DiagnosisMessages {
  static String systemHealthStatus() {
  // collecte des informations sur la santé du système
  ...
  }
}
...
logger.log(Level.FINER, DiagnosisMessages.systemHealthStatus());

Les journaux peuvent être internationalisés via des objet de classes ResourceBundle lors de leur construction.

Les gestionnaires de trace (objet dérivé de la classe Handler) reçoivent les demandes de journalisation (objet dérivé de la classe LogRecord). Ils sont chargés de la mise en forme de ces traces au moyen d'un formateur (objet héritant de la classe Formatter).

Suppression par programmation du gestionnaire de trace "console".

Même lorsque l'on utilise pas le système de journalisation de la JVM, un journal "racine" par défaut est utilisé et produit des traces sur la console ce qui peut être génant.

Il est toujours possible d'influer sur le fichier lib/logging.properties pour obtenir supprimer ce comportement mais ce n'est vraiment pas une bonne idée car les applications qui s'appuient sur l'historisation par défaut s'en trouveront perturbées (sans nécessairement comprendre pourquoi). On peut alors jouer également sur les propriétés systèmes ava.util.logging.config.class et  ava.util.logging.config.file. mais cela induit une tâche d'administration supplémentaire lors du lancement du programme. Il est plus naturel de résoudre cette difficulté par programmation. Voici un exemple de code qui le permet :

Logger logRoot = Log;
Logger logCurrent = Log;
while (logCurrent != null)
{
  logRoot = logCurrent;
  logCurrent = logCurrent.getParent();
}
Handler[] ahnd = logRoot.getHandlers();
for(Handler hnd : ahnd)
logRoot.removeHandler(hnd);
// Ajout des gestionnaires après ceci...

Optimisation des performances.

Nous avons vu plus haut, que certaines méthodes de requêtes de journalisation acceptent un "fournisseur" comme paramètre (interface fonctionnelle Supplier<T>). Cela permet de remplacer la trace par une fonction chargée de la créer. Dans l'exemple supra, la méthode systemHealthStatus sera exécutée dans tous les cas y compris si le niveau de filtrage actuel est inférieur à FINER (et donc la trace ignorée).

Le JDK8 offre une optimisation très intéressante. L'interface Supplier<T> est une interface fonctionnelle. Or toute interface fonctionnelle peut être remplacée par une fonction anonyme via une expression lambda. L'intérêt ici est que l'expression lambda n'est exécutée qu'au moment de l'utilisation du paramètre. Si le niveau de filtrage est inférieur à FINER, le paramètre ne sera pas utilisé et donc l'expression lambda jamais exécutée.

L'amélioration de performance est donc égale au temps d'exécution de l'expression lambda multipliée par le nombre de requêtes de journalisation filtrées. Cela peut être assez conséquent pour une application sévèrement contrôlée.

(c) PiApplications 2016