Developpez.com - Java
X

Choisissez d'abord la catégorieensuite la rubrique :

 

Développons en Java   2.10  
Copyright (C) 1999-2016 Jean-Michel DOUDOUX    (date de publication : 19/03/2016)

[ Précédent ] [ Sommaire ] [ Suivant ] [Télécharger ]      [Accueil ]

 

19. Le logging

 

chapitre 1 9

 

Niveau : niveau 3 Intermédiaire 

 

Le logging consiste à ajouter des traitements dans les applications pour permettre l'émission et le stockage de messages suite à des événements.

Le logging est utile pour tous les types d'applications en permettant par exemple de conserver une trace des exceptions qui sont levées dans l'application et des différents événements anormaux ou normaux liés à l'exécution de l'application.

Le logging permet de gérer des messages émis par une application durant son exécution et de permettre leur exploitation immédiate ou a posteriori. Ces messages sont d'ailleurs très utiles lors de la mise au point d'une application ou lors de son exploitation pour comprendre son fonctionnement ou résoudre une anomalie.

Ce chapitre contient plusieurs sections :

 

19.1. La présentation du logging

Le logging est une activité technique utile et nécessaire dans une application pour :

L'importance du logging croît avec la taille et la complexité de l'application qui l'utilise.

Une API de logging fait généralement intervenir trois composants principaux :

Le logging doit faire partie intégrante des fonctionnalités d'une application. Bien sûr le niveau de gravité des messages n'est pas le même en développement et en production mais le code de l'application doit rester le même. Seule la configuration du logging doit changer dans les différents environnements.

Généralement la configuration peut être externalisée dans un fichier ce qui rend l'utilisation de l'API plus souple et flexible.

La modification de la configuration du logging en cours d'exécution de l'application (soit dynamiquement soit par rechargement de la configuration) est importante pour permettre d'avoir couramment un niveau de log acceptable et, au besoin, un niveau de log plus fin sans devoir relancer l'application.

Les API de logging ont plusieurs inconvénients :

Le logging est particulièrement important dans une application notamment côté serveur mais une utilisation à outrance ou une mauvaise utilisation de cette fonctionnalité peut dégrader les performances générales de l'application.

Les frameworks de logging sont conçus pour limiter la consommation en ressources nécessaires à leur mise en oeuvre mais cette consommation existe tout de même et croît naturellement avec le nombre de messages émis.

L'utilisation d'une API de Logging implique donc une surcharge de consommation de ressources (CPU, mémoire, ...) mais elle se justifie par l'apport des informations fournies en cas de problème sous réserve que ces informations aient été judicieusement choisies.

 

19.1.1. Des recommandations lors de la mise en oeuvre

Voici quelques règles pour une bonne mise en oeuvre du logging :

Pour des traces d'exécution, il est pratique d'émettre un message en début d'une méthode qui affiche les paramètres en entrée et un message à la fin de la méthode avec la valeur de retour

Il est fortement recommandé d'utiliser une API de logging plutôt que d'utiliser la méthode System.out.println() pour plusieurs raisons :

Sur des applications utilisées par plusieurs utilisateurs, par exemple une application web, il peut être très utile de faire figurer dans le message une identité sur le responsable de l'action (par exemple, l'adresse IP d'une requête http).

 

19.1.2. Les différents frameworks

De nombreux frameworks existent pour mettre en oeuvre le logging dont :

Log4j du groupe Apache Jakarta est sûrement l'API la plus répandue et la plus populaire.

Les qualités de Log4j notamment sa simplicité de mise en oeuvre, ses fonctionnalités, sa fiabilité et son évolutivité lui permettent d'être le standard de facto pour le logging.

Depuis la version 1.4 du JDK, Java intègre une API de logging qui est le standard officiel pour le logging. Légèrement moins riche en fonctionnalités que Log4J, elle présente l'avantage d'être fournie dans les API de base.

Afin de faciliter l'utilisation du logging, le groupe Jakarta a développé un wrapper nommé JCL (JakartaCommon Logging) qui permet d'utiliser de façon transparente Log4j ou l'API Logging du JDK en utilisant le tronc commun de ces deux API.

 

19.2. Log4j

Log4j est un projet open source distribué sous la licence Apache Software initialement créé par Ceki Gülcü et maintenu par le groupe Jakarta. Cette API permet aux développeurs d'utiliser et de paramétrer un système de gestion de journaux (logs). Il est possible de fournir les paramètres dans un fichier de configuration ce qui rend sa configuration facile et souple. Log4j est compatible avec le JDK 1.1. et supérieur.


Log4j gère plusieurs niveaux de gravités et les messages peuvent être envoyés dans plusieurs flux : un fichier sur disque, le journal des événements de Windows, une connexion TCP/IP, une base de données, un message JMS, etc ...

Log4j utilise trois composants principaux pour assurer l'envoi de messages selon un certain niveau de gravité et contrôler à l'exécution le format et la ou les cibles de destination des messages :

Ces trois types de composants sont utilisés ensemble pour émettre des messages vers différentes cibles de stockage.

Ceci permet au framework de déterminer les messages qui doivent être loggués, la façon de les formater et vers quelle cible les messages seront envoyés.

La popularité de Log4J est largement liée à sa facilité d'utilisation, ses nombreuses fonctionnalités extensibles et sa fiabilité. Comme le logging n'est jamais une fonctionnalité principale d'une application, Log4j se veut facile à mettre en oeuvre.

Les principales caractéristiques de Log4j sont :

Un autre avantage de log4J est de pouvoir être utilisé avec toutes les versions du JDK depuis la 1.1.

L'externalisation de la configuration de Log4j dans un fichier externe permet de modifier la configuration des traitements de logging sans avoir à modifier le code source de l'application.

La hiérarchie des loggers permet un contrôle très fin de la granularité des messages ce qui réduit le volume de données des logs.

Log4j propose en standard plusieurs destinations de stockage des messages : fichiers, gestion d'événements Windows, Syslog Unix, base de données, email, message JMS, ...

L'API Log4j est regroupée dans plusieurs packages :

Package

Rôle

org.apache.log4j

Contient les principales classes et interfaces

org.apache.log4j.spi

System Programming Interface pour étendre Log4j

org.apache.log4j.chainsaw

Application Swing Chainsaw pour visualiser les logs formatées par un XMLLayout ou émises par un SocketAppender

org.apache.log4j.config

Classes pour la gestion des propriétés des composants

org.apache.log4j.helpers

Utilitaires

org.apache.log4j.jdbc

Classes pour stocker les messages dans une base de données

org.apache.log4j.jmx

Classes pour permettre la configuration de Log4j grâce à JMX

org.apache.log4j.lf5

Application Swing Log Force 5 pour visualiser les logs

org.apache.log4j.net

Classes pour envoyer les messages à travers le réseau (JMS, SMTP, Sockets, ...)

org.apache.log4j.nt

Classes pour envoyer les messages dans le système de gestion des événements de Windows

org.apache.log4j.or

Utilitaires pour formater des objets

org.apache.log4j.performance

Classes de tests des performances

org.apache.log4j.xml

Classes pour permettre la configuration de Log4j avec un fichier XML

org.apache.log4j.varia

Classes diverses


Le site officiel de Log4j est à l'url : http://logging.apache.org/log4j/

Log4j est disponible dans trois versions majeures :

 

19.2.1. Les premiers pas

Cette section fournit des informations et un premier exemple pour la mise en oeuvre de Log4J.

 

19.2.1.1. L'installation

Il faut télécharger le fichier apache-log4j-1.2.xx.zip à l'url http://logging.apache.org/log4j/1.2/download.html

Il suffit ensuite de décompresser l'archive dans un répertoire du système. L'archive contient entre autres les sources, la documentation, des exemples et la bibliothèque log4j-1.2.x.jar.

 

19.2.1.2. Les principes de mise en oeuvre

Pour utiliser Log4j, il suffit d'ajouter le fichier log4j-1.2.x.jar dans le classpath de l'application.

Il faut définir un fichier de configuration : configuration des loggers, définition des appenders, association des appenders aux loggers avec un layout.

Dans le code source des classes, il faut :

 

19.2.1.3. Un exemple de mise en oeuvre

Cette section va mettre en oeuvre Log4j dans un exemple très simple.

Il faut créer un fichier log4j.properties stocké dans le classpath de l'application : ce fichier contient la configuration de Log4j pour l'application.

Exemple :
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d [%-5p] (%F:%M:%L) %m%n

Cette configuration définit le niveau de gravité DEBUG pour le logger racine et lui associe un logger nommé arbitrairement stdout. Par héritage, tous les loggers de l'application vont hériter de cette configuration.

L'appender nommé stdout est de type ConsoleAppender : il envoie les messages sur la console standard.

Un layout personnalisé est associé à l'appender nommé stdout pour formater les messages. Chaque séquence commençant par le caractère % sera remplacée dynamiquement par sa valeur correspondante. Par exemple : %d correspond à la date/heure, %p au niveau de gravité, %m le message, %n une nouvelle ligne, ...

Pour mettre en oeuvre l'API dans le code source, il faut tout d'abord obtenir une instance du logger à utiliser en utilisant la méthode getLogger() de la classe Logger.

Chaque message est émis en utilisant la méthode correspondant au niveau de gravité choisi de la classe Logger.

Exemple :
package com.jmdoudoux.test.log4j;

import org.apache.log4j.Logger;

public class TestLog4j1 {

  private static Logger logger = Logger.getLogger(TestLog4j1.class);
  
  public static void main(String[] args) {
    logger.debug("msg de debogage");
    logger.info("msg d'information");
    logger.warn("msg d'avertissement");
    logger.error("msg d'erreur");
    logger.fatal("msg d'erreur fatale");   
  }
}

L'exécution de cette classe permet d'afficher sur la console les différents messages

Résultat :
2008-06-08 10:16:21,546 [DEBUG] (TestLog4j1.java:main:13) msg de debogage
2008-06-08 10:16:21,546 [INFO ] (TestLog4j1.java:main:14) msg d'information
2008-06-08 10:16:21,546 [WARN ] (TestLog4j1.java:main:15) msg d'avertissement
2008-06-08 10:16:21,546 [ERROR] (TestLog4j1.java:main:16) msg d'erreur
2008-06-08 10:16:21,546 [FATAL] (TestLog4j1.java:main:17) msg d'erreur fatale

Une simple modification du fichier de configuration permet de changer le niveau de gravité des messages pris en compte. Par exemple en remplaçant DEBUG par ERROR

La réexécution de la classe qui n'a pas été modifiée et donc pas recompilée permet d'afficher sur la console uniquement les messages dont la gravité est supérieure ou égale à ERROR.

Résultat :
2008-06-08 10:18:47,530 [ERROR] (TestLog4j1.java:main:13) msg d'erreur
2008-06-08 10:18:47,530 [FATAL] (TestLog4j1.java:main:14) msg d'erreur fatale

 

19.2.2. La gestion des logs avec les versions antérieures à la 1.2

Les versions antérieures à la 1.2 de Log4J utilisaient les classes Category pour gérer les messages et la Priority pour encapsuler les niveaux de gravité.

 

19.2.2.1. Les niveaux de gravités : la classe Priority

Log4j gère des priorités pour permettre à une instance de la classe Category de déterminer si le message sera envoyé dans le log ou non. Il existe cinq priorités qui possèdent un ordre hiérarchique croissant :

La classe org.apache.log4j.Priority encapsule ces priorités.

Chaque Category est associée à une priorité qui peut être changée dynamiquement. La catégorie détermine si un message doit être envoyé dans le log en comparant sa priorité avec la priorité du message. Si celle-ci est supérieure ou égale à la priorité de la Category, alors le message est envoyé vers la cible de destination du log.

La méthode setPropriety() de la classe Category permet de préciser la priorité.

Si aucune priorité n'est donnée à une catégorie, elle "hérite" de la priorité de la première catégorie renseignée trouvée en remontant dans la hiérarchie.

Exemple : soit trois catégories
root associée à la priorité INFO
categorie1 nommée "org" sans priorité particulière
categorie2 nommée "org.moi" associée à la priorité ERROR
categorie3 nommée "org.moi.projet" sans priorité particulière

Une demande d'émission de message avec la priorité DEBUG sur categorie1 n'est pas traitée car la priorité INFO héritée est supérieure à DEBUG.
Une demande avec la priorité WARN sur categorie1 est traitée car la priorité INFO héritée est inférieure à WARN .
Une demande avec la priorité DEBUG sur categorie3 n'est pas traitée car la priorité ERROR héritée est supérieure à DEBUG.
Une demande avec la priorité FATAL sur categorie3 est traitée car la priorité ERROR héritée est inférieure à FATAL.
En fait dans l'exemple, aucune demande avec la priorité DEBUG ne sera traitée.

Au niveau applicatif, il est possible d'interdire le traitement d'une priorité et de celle inférieure en utilisant le code suivant : Category.getDefaultHierarchy().disable(). Il faut fournir la priorité à la méthode disable().

Il est possible d'annuler ce traitement dynamiquement en positionnant la propriété système log4j.disableOverride.

 

19.2.2.2. La classe Category

La classe org.apache.log4j.Category détermine si un message doit être envoyé dans le ou les logs qui lui sont associés.

Chaque Category possède un nom qui est sensible à la casse. Pour créer une instance de la classe Category il faut utiliser la méthode statique getInstance() qui attend en paramètre le nom de la Category. Si une Category existe déjà avec le nom fourni, alors la méthode getInstance() renvoie l'instance existante.

Il est pratique de fournir le nom complet de la classe comme nom de la Category dans laquelle elle est instanciée mais ce n'est pas une obligation. Il est ainsi possible de créer une hiérarchie spécifique différente de celle de l'application, par exemple basée sur des aspects fonctionnels. L'inconvénient d'associer le nom de la classe au nom de la catégorie est qu'il faut instancier un objet Category dans chaque classe : le plus pratique est de déclarer cet objet static.

Exemple :
public class Classe1 {
  static Category category = Category.getInstance(Classe1.class.getName());
  ...
}

La méthode log(Priority, Object) permet de demander l'émission d'un message associé au niveau de gravité fourni en paramètre. Plusieurs méthodes sont des raccourcis qui évitent d'avoir à préciser le niveau de gravité car celui utilisé sera automatiquement celui associé à la méthode (debug(Object), info(Object), warn(Object), error(Object), fatal(Object)).

Toutes ces méthodes possèdent une surcharge qui attend en paramètre supplémentaire un objet de type Throwable. Ces méthodes ajouteront automatiquement au message la pile d'appels (stacktrace) de l'exception.

La demande est traitée en fonction de la hiérarchie de la Category et de la priorité du message.

Pour éviter d'éventuels traitements inutiles de création du message, il est possible d'utiliser la méthode isEnabledFor(Priority) pour savoir si la catégorie prend en compte la priorité ou non.

Exemple :
import org.apache.log4j.*;
 
public class TestIsEnabledFor {

  static Category cat1 = Category.getInstance(TestIsEnabledFor.class.getName());

  public static void main(String[] args) {
    int i=1;
    int[] occurrence={10,20,30};

    BasicConfigurator.configure();

    cat1.setPriority(Priority.WARN) ; 
    cat1.warn("message de test");
    
    if(cat1.isEnabledFor(Priority.INFO)) {
      System.out.println("traitement du message de priorité INFO");  
      cat1.info("La valeur de l'occurrence "+i+" = " + String.valueOf(occurrence[i]));
    }
    if(cat1.isEnabledFor(Priority.WARN)) {
      System.out.println("traitement du message de priorité WARN");  
      cat1.warn("La valeur de l'occurrence "+i+" = " + String.valueOf(occurrence[i]));
    }
  }
}
Résultat :
0 [main] WARN TestIsEnabledFor  - message de test
traitement du message de priorit_ WARN
50 [main] WARN TestIsEnabledFor  - La valeur de l'occurrence 1 = 20

Le nom de la Category permet d'établir une hiérarchie dans les Category : ce nom est composé de mots séparés par un caractère point comme pour les packages. D'ailleurs par simplicité et par convention c'est le nom pleinement qualifié de la classe qui est utilisé.

Il existe toujours une catégorie racine créée par Log4J : pour obtenir une instance de cette Category, il faut utiliser la méthode getRoot() de la classe Category car elle ne possède pas de nom.

La méthode getInstance() de la classe Category renvoie toujours la même instance pour un même nom de catégorie. Si cette instance n'existe pas alors la méthode la créée sinon elle retourne celle existante.

Le message n'est pris en compte que si son niveau de gravité est supérieur ou égal à celui de la catégorie.

Par défaut, une Category hérite du niveau de gravité de sa Category mère selon la hiérarchie des catégories basée sur leurs noms. Ceci est possible car la Category racine à un niveau de gravité par défaut initialisé à DEBUG.

Par exemple, la catégorie com.jmdoudoux.test.log4j hérite des caractéristiques de la catégorie com.jmdoudoux.test.

Il est possible d'associer un niveau de gravité à la Category de façon statique en utilisant la méthode setPriority().

 

19.2.2.3. La hiérarchie dans les catégories

Le nom de la catégorie permet de la placer dans une hiérarchie dont la racine est une catégorie spéciale nommée root qui est créée par défaut sans nom.

La classe Category possède une méthode statique getRoot() pour obtenir la catégorie racine.

La hiérarchie des noms est établie grâce à la notation par point comme pour les packages. D'ailleurs par convention, le nom de la catégorie correspond généralement au nom pleinement qualifié de la classe qui va utiliser la catégorie.

Exemple : soit trois catégories
categorie1 nommée "org"
categorie2 nommée "org.moi"
categorie3 nommée "org.moi.projet"

Categorie3 est fille de categorie2, elle-même fille de categorie1.

Cette relation hiérarchique est importante car la configuration établie pour une catégorie est automatiquement propagée par défaut aux catégories enfants.

L'ordre de la création des catégories de la hiérarchie ne doit pas obligatoirement respecter l'ordre de la hiérarchie. Celle-ci est constituée au fur et à mesure de la création des catégories.

 

19.2.3. La gestion des logs à partir de la version 1.2

Les classes Category et Priority sont déclarées deprecated et sont remplacées respectivement par les classes Logger et Level qui en héritent.

 

19.2.3.1. Les niveaux de gravité : la classe Level

A partir de la version 1.2 de Log4j, la classe Priority ne doit plus être utilisée : il est préférable d'utiliser sa classe fille Level.

Attention la classe Priority n'est pas marquée deprecated car la classe Level en hérite.

La classe org.apache.log4j.Level encapsule donc un niveau de gravité.

Log4j définit plusieurs niveaux de gravité en standard et possédant un ordre hiérarchique :

Deux autres niveaux particuliers sont définis et utilisés dans la configuration :

Il est possible de définir ses propres niveaux de gravité en créant une classe qui hérite de la classe Level.

Le choix du niveau de gravité associé à un message est très important. Voici quelques exemples d'utilisation selon chaque niveau de gravité :

Niveau de gravité

Exemple d'utilisation

TRACE

Entrée et sortie de méthodes

DEBUG

Affichage de valeur de données

INFO

Chargement d'un fichier de configuration, début et fin d'exécution d'un traitement long

WARN

Erreur de login, données invalides

ERROR

Toutes les exceptions capturées qui n'empêchent pas l'application de fonctionner

FATAL

Indisponibilité d'une base de données, toutes les exceptions qui empêchent l'application de fonctionner

 

19.2.3.2. La classe Logger

A partir de la version 1.2 de Log4j, la classe Category ne doit plus être utilisée : il est préférable d'utiliser sa classe fille Logger.

Attention la classe Category n'est pas marquée deprecated car la classe Logger en hérite.

La classe org.apache.log4j.Logger permet donc comme la classe Category de demander l'envoi d'un message dans le système de logs. Un logger compare son niveau de gravité avec celui du message : si ce dernier est supérieur ou égal à celui du logger alors le message est traité.

Un logger est associé à un ou plusieurs appenders : si le message est à traiter, celui-ci est envoyé par le logger à ses appenders.

La classe Logger héritant de la classe Category, elle possède toutes ses méthodes notamment celles permettant l'émission d'un message. L'émission de messages se fait donc en utilisant la méthode log() ou une des méthodes utilisant implicitement un niveau de gravité (debug(), info(), warn(), error(), fatal()).

Exemple : les deux lignes de code sont équivalentes

Exemple :
logger.log(Level.INFO, "mon message");
logger.info("mon message");

Pour obtenir une instance de la classe Logger, il faut utiliser sa méthode statique getLogger(). Cette méthode attend en paramètre le nom du logger.

Exemple :
package com.jmdoudoux.test.log4j;

import org.apache.log4j.Logger;

public class MaClasse {
  private static final Logger logger = Logger.getLogger("com.jmdoudoux.test.log4j.MaClasse");
}

Comme généralement ce nom correspond au nom pleinement qualifié de la classe, une version surchargée de la méthode getLogger() attend en paramètre un objet de type Class pour en extraire le nom.

Exemple :
package com.jmdoudoux.test.log4j;

import org.apache.log4j.Logger;

public class MaClasse {
  private static final Logger logger = Logger.getLogger(MaClasse.class);
}

La méthode getLogger() permet de s'assurer que pour un même nom cela soit toujours la même instance qui est retournée.

Exemple :
package com.jmdoudoux.test.log4j;

import org.apache.log4j.Logger;

public class TestLog4j9 {
  
  public static void main(String[] args) {
    Logger loggerA = Logger.getLogger("com.jmdoudoux.test.log4j");
    Logger loggerB = Logger.getLogger("com.jmdoudoux.test.log4j");
      
    System.out.println("loggerA == loggerB : "+(loggerA==loggerB));
  }
}
Résultat :
loggerA == loggerB : true

Le nom de chaque Logger permet de définir une hiérarchie pour permettre de faciliter leur configuration. Cette hiérarchie sur les noms repose sur l'utilisation du caractère point comme pour les packages. Il est dès lors pratique d'utiliser le nom pleinement qualifié de la classe comme nom de logger pour une classe.

Le nom des logger est sensible à la casse.

La hiérarchie commence toujours par un Logger fournit par Log4j : le RootLogger. Pour obtenir une instance de ce logger racine, il faut utiliser la méthode getRootLogger() de la classe Logger.

Le rootLogger a deux caractéristiques distinctives par rapport aux autres loggers :

Lors de la création de l'instance d'un Logger, la hiérarchie est parcourue pour déterminer le Logger le plus proche de la hiérarchie. A défaut, ce sont les caractéristiques du rootLogger qui sont attribuées au nouveau Logger.

L'ordre de création des loggers n'a pas d'importance : il n'est pas obligatoire de créer les 1oggers dans leur ordre hiérarchique

Chaque Logger et chaque message possèdent un niveau de gravité. Le Logger compare son niveau de gravité avec celui du message : si le niveau de gravité du message est égal ou supérieur au niveau de gravité du Logger, alors le message est traité par le framework sinon il est ignoré.

Exemple : le message ne sera jamais pris en compte

Exemple :
Logger logger = Logger.getLogger("com.jmdoudoux.test.log4j");
logger.setLevel(Level.INFO);
logger.debug("mon message");

Chaque logger est associé à un niveau de gravité soit directement soit indirectement par héritage du niveau de gravité de son père dans la hiérarchie. Si le logger ne possède par de niveau de gravité explicite alors c'est celui de son ancêtre le plus proche dans la hiérarchie des loggers.

Comme le logger racine à un niveau de gravité par défaut, cela implique qu'un logger à toujours un niveau de gravité qui lui est associé.

Si aucun logger ne possède de niveau de gravité explicite dans la hiérarchie alors le niveau du logger racine (rootLogger) est utilisé. Le rootLogger est toujours définit avec un niveau de gravité qui par défaut est debug.

Il est possible de configurer un logger par programmation.

Il est possible d'associer de façon statique un niveau de gravité au logger en utilisant la méthode setLevel(). Il est cependant préférable d'utiliser la configuration dynamique en utilisant un fichier de configuration qui permet de modifier les paramètres sans modifier le code source.

 

19.2.3.3. La migration de Log4j antérieure à 1.2 vers 1.2

La migration de l'utilisation des classes Category vers Logger et Priority vers Level peut généralement être faite grâce à un rechercher/remplacer dans le code source :

Rechercher

Remplacer par

Category.getInstance

Logger.getLogger

Category.getRoot

Logger.getRootLogger

Category

Logger

Priority

Level

 

19.2.4. Les Appender

La cible de destination des messages est encapsulée dans un ou plusieurs objets de type Appender.

L'interface org.apache.log4j.Appender désigne un flux qui représente le log et se charge de l'envoi de messages formatés dans le flux. Le formatage proprement dit est réalisé par un objet de type Layout. Ce layout peut être fourni dans le constructeur adapté ou par la méthode setLayout().

Une Category ou un Logger peuvent avoir plusieurs appenders. Si la Category ou le Logger décident de traiter la demande d'un message, le message est envoyé à chacun des appenders. Pour ajouter manuellement un appender à une Category, il suffit d'utiliser la méthode addAppender() qui attend en paramètre un objet de type Appender.

L'interface Appender est directement implémentée par la classe abstraite AppenderSkeleton.

Cette classe est la classe mère de toutes les classes fournies avec Log4j pour représenter un type de log. Log4J propose plusieurs appenders en standard :

Pour créer un appender par programmation, il suffit d'instancier un objet d'une de ces classes.

Chaque appender possède des paramètres de configuration dédiés.

Comme un Logger peut avoir plusieurs appenders, un même message peut être envoyé vers plusieurs appenders selon la configuration. La méthode addAppender() de la classe Logger permet d'ajouter manuellement un appender au logger.

Comme pour les niveaux de gravité, les appenders d'une catégorie ou d'un logger sont hérités implicitement par défaut de la hiérarchie des loggers.

Il est possible d'inhiber cet héritage pour une partie de la hiérarchie en utilisant la méthode setAdditivity() avec le paramètre false sur l'instance du logger concerné. Ce logger et sa hiérarchie descendante n'hériteront pas des caractéristiques de leur parent.

Exemple :
package com.jmdoudoux.test.log4j;

import java.io.IOException;

import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;
import org.apache.log4j.xml.XMLLayout;

public class TestLog4j10 {
  
  public static void main(
      String[] args) {
    Logger logRoot = Logger.getRootLogger();
    ConsoleAppender ca = new ConsoleAppender();
    ca.setName("console");
    ca.setLayout(new SimpleLayout());
    ca.activateOptions();
    logRoot.addAppender(ca);
    logRoot.setLevel(Level.DEBUG);
    
    logRoot.debug("message 1");
    
    Logger log = Logger.getLogger(TestLog4j10.class);
    
    log.setAdditivity(false);
    try {
      FileAppender fa = new FileAppender(new XMLLayout(), "c:/log.txt");
      fa.setName("FichierLog");
      log.addAppender(fa);
    } catch (IOException e) {
      e.printStackTrace();
    }
    
    log.debug("message 2");
    
    Logger logTest = Logger.getLogger("com.jmdoudoux.test.log4j");
    logTest.debug("message 3");
  }
}
Résultat dans la console :
DEBUG - message 1
DEBUG - message 3
Résultat dans le fichier de log :
<log4j:event logger="com.jmdoudoux.test.log4j.TestLog4j10" timestamp="1231923298709"
      level="DEBUG" thread="main">
<log4j:message><![CDATA[message 2]]></log4j:message>
</log4j:event>

Avant qu'ils ne puissent être utilisés, la plupart des appenders nécessitent un appel à leur méthode activateOptions() lorsqu'ils sont configurés par programmation.

Il est possible de définir son propre appender en définissant une classe qui implémente l'interface Appender ou qui hérite de la classe AppenderSkeleton.

 

19.2.4.1. AsyncAppender

La classe org.apache.log4j.AsyncAppender envoie les messages vers différents appenders de façon périodique et asynchrone. Cet appender utilise son propre thread.

Cet appender n'est configurable que dans un fichier de configuration au format XML.

Un tag fils <appender-ref> permet de préciser un appender vers lequel les messages seront envoyés. L'attribut ref permet de préciser le nom de l'appender concerné.

L'attribut bufferSize permet de préciser le nombre de messages qui seront stockés dans le tampon.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="false"
  xmlns:log4j="http://jakarta.apache.org/log4j/">
  <appender name="console" class="org.apache.log4j.ConsoleAppender">
    <param name="Target" value="System.out" />
    <layout class="org.apache.log4j.SimpleLayout" />
  </appender>
  <appender class="org.apache.log4j.FileAppender" name="file">
    <param name="file" value="c:/monapp.log" />
    <layout class="org.apache.log4j.SimpleLayout" />
  </appender>
  <appender class="org.apache.log4j.AsyncAppender" name="async">
    <param name="bufferSize" value="2" />
    <appender-ref ref="file" />
    <appender-ref ref="console" />
  </appender>
  <root>
    <level value="info" />
    <appender-ref ref="async" />
  </root>
</log4j:configuration>

 

19.2.4.2. JDBCAppender

La classe org.apache.log4j.jdbc.JDBCAppender envoie les messages dans une base de données.

Cet appender possède plusieurs propriétés notamment pour préciser les paramètres de connexion à la base de données.

Nom

Rôle

BufferSize

Nombre de messages stockés dans le tampon avant l'insertion dans la base de données

Driver

Pilote JDBC pour l'accès à la base de données

Url

Url de connexion à la base de données

Password


Mot de passe de connexion

User

Utilisateur de connexion

Sql

Requête SQL pour insérer une occurrence dans la base de données


La propriété Sql permet de définir la requête SQL qui permet l'insertion des informations sur le message dans la base de données. La requête doit utiliser les séquences utilisées par le layout PatternLayout

Exemple :
INSERT INTO log(dthr, niveau, message) VALUES('%d', '%p', '%m');".

Attention : l'utilisation de cet appender fourni par Log4J n'est pas recommandée. Pour plus d'informations consultez la documentation de l'API.

 

19.2.4.3. JMSAppender

La classe org.apache log4j.net.JMSAppender envoie les message vers une destination JMS.

 

19.2.4.4. LF5Appender

La classe org.apache.log4j.lf5.LF5Appender envoie les messages sur une application Swing dédiée.

 

19.2.4.5. NTEventLogAppender

La classe org.apache.log4j.nt.NTEventLogAppender envoie les messages dans le log des événements système sur Windows à partir de Windows NT

 

19.2.4.6. NullAppender

La classe org.apache.log4j.varia.NullAppender ignore les messages qui lui sont envoyés.

La seule propriété d'un NullAppender est :

Nom

Rôle

Threshold

Limiter les messages pris en compte par l'appender à ceux dont le niveau de gravité est supérieur ou égal à celui de threshold. Ceci vient en plus du niveau de gravité associé au logger. Héritée de AppenderSkeleton


 

19.2.4.7. SMTPAppender

La classe org.apache.log4j.net.SMTPAppender envoie les messages par mail.

La classe SMTPAppender possède plusieurs attributs :

Nom

Rôle

BufferSize

Nombre de messages inclus dans un mail

SMTPHost

Nom de la machine qui héberge le serveur SMTP

From

Email de l'émetteur du mail

To

Email du ou des destinataires du mail

Subject

Sujet du mail

Cc

Email du ou des destinataires en copie du mail

Bcc

Email du ou des destinataires en copie cachée du mail

SMTPPassword

Mot de passe

SMTPUsername

Utilisateur


Par défaut, seuls les messages avec un niveau de gravité supérieur ou égal à ERROR sont traités par cet appender.

Cet appender requiert les bibliothèques JavaBeans Activation Framework et JavaMail pour fonctionner.

 

19.2.4.8. SocketAppender

La classe org.apache.log4j.net.SocketAppender envoie les messages dans une socket utilisant TCP/IP.

Les données envoyées sont des objets de type LoggingEvent sérialisés.

La classe SocketAppender possède plusieurs attributs :

Nom

Rôle

LocationInfo

Booléen qui précise si des informations de localisation sont envoyées. Par défaut la valeur est false.

Port

Port de la machine hôte à utiliser.

RemoteHost

Chaîne de caractères qui précise la machine hôte


La méthode activateOptions() permet de réaliser la connexion.

Exemple :
package com.jmdoudoux.test.log4j;

import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;
import org.apache.log4j.net.SocketAppender;

public class TestLog4j18 {
  static Logger logger = Logger.getLogger(TestLog4j18.class);

  public static void main(
      String args[]) {
    SocketAppender appender = null;
    try {
      appender = new SocketAppender();
      appender.setPort(10256);
      appender.setRemoteHost("localhost");
      appender.setLocationInfo(true);
      appender.setLayout(new SimpleLayout());
      appender.activateOptions();
    } catch (Exception e) {
      e.printStackTrace();
    }
    logger.addAppender(appender);

    while (true) {
      System.out.println("envoie log");
      logger.debug("msg de debogage");
      logger.info("msg d'information");
      logger.warn("msg d'avertissement");
      logger.error("msg d'erreur");
      logger.fatal("msg d'erreur fatale");
      try {
        Thread.sleep(5000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

La configuration dans le fichier properties est similaire aux autres appenders.

Exemple :
log4j.appender.socket=org.apache.log4j.net.SocketAppender
log4j.appender.socket.RemoteHost=localhost
log4j.appender.socket.Port=10256
log4j.appender.socket.LocationInfo=true

La configuration dans le fichier XML est similaire aux autres appenders.

Exemple :
...
  <appender name="socket" class="org.apache.log4j.net.SocketAppender">
    <param name="Port" value="10256"/>
    <param name="RemoteHost" value="localhost"/>
    <param name="LocationInfo" value="true"/>
  </appender>
...

 

19.2.4.9. SocketHubAppender

La classe org.apache.log4j.net.SocketHubAppender envoie les messages dans plusieurs sockets.

 

19.2.4.10. SyslogAppender

La classe org.apache.log4j.net.SyslogAppender envoie les messages dans le démon syslog d'un système Unix

 

19.2.4.11. TelnetAppender

La classe org.apache.log4j.net.TelnetAppender envoie les messages dans une socket en lecture seule facilement consultable avec l'outil telnet.

 

19.2.4.12. WriterAppender

La classe org.apache.log4j.WriterAppender possède deux classes filles : ConsoleAppender et FileAppender. La classe FileAppender possède, elle aussi, deux classes filles : DailyRollingAppender et RollingFileAppender.

Elles possèdent plusieurs propriétés dont :

Nom

Rôle

Valeur par défaut

Encoding

Préciser le jeu de caractères à utiliser.

null

ImmediateFlush

Préciser si le tampon doit être vidé à chaque opération (pas de stockage dans un tampon).

true


Exemple :
package com.jmdoudoux.test.log4j;

import java.io.FileOutputStream;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.WriterAppender;
import org.apache.log4j.xml.XMLLayout;

public class TestLog4j11 {

  public static void main(
      String[] args) {
    Logger logRoot = Logger.getRootLogger();

    WriterAppender appender = null;
    try {
      appender = new WriterAppender(new XMLLayout(), new FileOutputStream("c:/malog.txt"));

    } catch (Exception e) {
      e.printStackTrace();
    }

    logRoot.addAppender(appender);
    logRoot.setLevel(Level.DEBUG);

    logRoot.debug("mon message");
  }
}
Résultat : le contenu du fichier c:\malog.txt
<log4j:event logger="root" timestamp="1219683683344" level="DEBUG" thread="main">
<log4j:message><![CDATA[mon message]]></log4j:message>
</log4j:event>

 

19.2.4.13. ConsoleAppender

La classe org.apache.log4j.ConsoleAppender envoie les messages sur la console : soit sur la sortie standard (System.out) par défaut soit vers la sortie d'erreurs (System.err).

Les propriétés d'un ConsoleAppender sont :

Nom

Rôle

Valeur par défaut

Encoding

Préciser le jeu de caractères à utiliser. Héritée de WriterAppender

null

ImmediateFlush

Envoyer les messages immédiatement vers la console (pas de mise dans un tampon). Héritée de WriterAppender

true

Target

System.out ou System.err

System.out

Threshold

Limiter les messages pris en compte par l'appender à ceux dont le niveau de gravité est supérieur ou égal à celui de threshold. Ceci vient en plus du niveau de gravité associé au logger. Héritée de AppenderSkeleton

 

Exemple :
package com.jmdoudoux.test.log4j;

import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;

public class TestLog4j12 {
  
  public static void main(
      String[] args) {
    Logger logRoot = Logger.getRootLogger();
    ConsoleAppender ca = new ConsoleAppender();
    ca.setName("console");
    ca.setLayout(new SimpleLayout());
    ca.activateOptions();
    logRoot.addAppender(ca);
    logRoot.setLevel(Level.DEBUG);
    
    logRoot.info("mon message");
  }
}
Résultat :
2008-06-15 10:22:02,925 [INFO ] (TestLog4j12.java:main:20) mon message
INFO - mon message

 

19.2.4.14. FileAppender

La classe org.apache.log4j.FileAppender envoie les messages dans un fichier.

Les propriétés d'un FileAppender sont :

Nom

Rôle

Valeur par défaut

ImmediateFlush

Envoyer les messages immédiatement vers le fichier (pas de stockage dans un tampon). Héritée de WriterAppender

true

Threshold

Limiter les messages pris en compte par l'appender à ceux dont le niveau de gravité est supérieur ou égal à celui de threshold. Ceci vient en plus du niveau de gravité associé au logger. Héritée de AppenderSkeleton

 

Append

Ajouter le message à la fin du fichier ou remplacer le contenu du fichier

True

Encoding

Jeu de caractères utilisé pour l'encodage

 

BufferedIO

Préciser si un tampon doit être utilisé

False

BufferSize

Préciser la taille du tampon s'il est utilisé

 

File

Nom du fichier

 

Exemple :
package com.jmdoudoux.test.log4j;

import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;

public class TestLog4j13 {

  public static void main(
      String[] args) {
    Logger logRoot = Logger.getRootLogger();

    FileAppender appender = null;
    try {
      appender = new FileAppender();

      appender.setLayout(new SimpleLayout());
      appender.setFile("c:/app_log.txt");
      appender.activateOptions();
      logRoot.addAppender(appender);
      logRoot.setLevel(Level.DEBUG);

      logRoot.info("mon message");

    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

 

19.2.4.15. DailyRollingFileAppender

La classe org.apache.log4j.DailyRollingFileAppender envoie les messages dans un fichier à rotation périodique (qui n'est pas obligatoirement journalière).

Les propriétés d'un DailyRollingFileAppender sont :

Nom

Rôle

Valeur par défaut

ImmediateFlush

Envoyer les messages immédiatement vers le fichier (pas de mise dans un tampon). Héritée de WriterAppender

true

Threshold

Limiter les messages pris en compte par l'appender à ceux dont le niveau de gravité est supérieur ou égal à celui de threshold. Ceci vient en plus du niveau de gravité associé au logger. Héritée de AppenderSkeleton

 

Append

Ajouter le message à la fin du fichier ou remplacer le contenu du fichier. Héritée de FileAppender

True

Encoding

Préciser le jeu de caractères utilisé pour l'encodage. Héritée de WriterAppender

 

BufferedIO

Préciser si un tampon doit être utilisé. Héritée de FileAppender

False

BufferSize

Préciser la taille du tampon s'il est utilisé. Héritée de FileAppender

 

File

Nom du fichier. Héritée de FileAppender

 

DatePattern

Définir la périodicité de rotation et le suffixe des noms des fichiers créés à chaque rotation

 

La valeur de la propriété DatePattern suit le format utilisé par la classe SimpleDateFormat.

Exemple :

'.'yyyy-MM: rotation chaque mois

'.'yyyy-ww: rotation chaque semaine

'.'yyyy-MM-dd: rotation chaque jour à minuit

'.'yyyy-MM-dd-a: rotation chaque jour à midi et à minuit

'.'yyyy-MM-dd-HH: rotation chaque heure

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="false"
  xmlns:log4j="http://jakarta.apache.org/log4j/">
  <appender name="LoggerFile"
    class="org.apache.log4j.DailyRollingFileAppender">
    <param name="File"
      value="c:/monapp.log" />
    <param name="DatePattern" value="'.'yyyy-MM-dd" />
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern"
        value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-8p [%t]:%C - %m%n" />
    </layout>
  </appender>
  <root>
    <level value="info" />
    <appender-ref ref="LoggerFile" />
  </root>
</log4j:configuration>

 

19.2.4.16. RollingFileAppender

La classe org.apache.log4j.DailyRollingFileAppender envoie les messages dans un fichier à rotation selon sa taille.

Le fichier est créé et rempli avec les différents messages. Une fois que la taille du fichier a atteint celle précisée, le fichier est renommé avec le suffixe .1 et le fichier est recréé. Une fois qu'il est de nouveau rempli, le fichier avec le suffixe .1 est renommé avec .2, le fichier est renommé avec le suffixe .1 et un nouveau fichier est créé.

Si le fichier le plus ancien possède un suffixe supérieur à celui précisé, alors il est supprimé.

Les propriétés d'un RollingFileAppender sont :

Nom

Rôle

Valeur par défaut

ImmediateFlush

Envoyer les messages immédiatement vers le fichier (pas de stockage dans un tampon). Héritée de WriterAppender

true

Threshold

Limiter les messages pris en compte par l'appender à ceux dont le niveau de gravité est supérieur ou égal à celui de threshold. Ceci vient en plus du niveau de gravité associé au logger. Héritée de AppenderSkeleton

 

Append

Ajouter le message à la fin du fichier ou remplacer le contenu du fichier. Héritée de FileAppender

True

Encoding

Préciser le jeu de caractères utilisé pour l'encodage. Héritée de WriterAppender

 

BufferedIO

Préciser si un tampon doit être utilisé. Héritée de FileAppender

False

BufferSize

Préciser la taille du tampon s'il est utilisé. Héritée de FileAppender

 

File

Nom du fichier. Héritée de FileAppender

 

MaxFileSize

Taille maximale du fichier avant sa rotation. La taille peut être fournie en KB, MB ou GB
Exemple : 1024KB

 

MaxIndexBackup

Indiquer le nombre de fichiers de sauvegarde conservés. Une fois ce nombre dépassé, le fichier de sauvegarde le plus ancien est supprimé

 

 

19.2.4.17. ExternalyRolledFileAppender

La classe org.apache.log4j.ExternalyRollingFileAppender envoie les messages dans un fichier à rotation déclenchée par la réception dans une socket de la chaîne de caractères "RollOver" en respectant la casse .

L'appender envoie en retour un accusé de traitement ou d'erreur par la socket.

La classe ExternalyRolledFileAppender possède plusieurs attributs :

Nom

Rôle

Valeur par défaut

ImmediateFlush

Envoyer les messages immédiatement vers le fichier (pas de stockage dans un tampon). Héritée de WriterAppender

true

Threshold

Limiter les messages pris en compte par l'appender à ceux dont le niveau de gravité est supérieur ou égal à celui de threshold. Ceci vient en plus du niveau de gravité associé au logger. Héritée de AppenderSkeleton

 

Append

Ajouter le message à la fin du fichier ou remplacer le contenu du fichier. Héritée de FileAppender

True

Encoding

Préciser le jeu de caractères utilisé pour l'encodage. Héritée de WriterAppender

 

BufferedIO

Préciser si un tampon doit être utilisé. Héritée de FileAppender

False

BufferSize

Préciser la taille du tampon s'il est utilisé. Héritée de FileAppender

 

File

Nom du fichier. Héritée de FileAppender

 

MaxFileSize

Taille maximale du fichier avant sa rotation. La taille peut être fournie en KB, MB ou GB
Exemple : 1024KB

 

MaxIndexBackup

Indiquer le nombre de fichiers de sauvegarde conservés. Une fois ce nombre dépassé, le fichier de sauvegarde le plus ancien est supprimé

 

Port

Port d'écoute utilisé par la socket

 

 

19.2.5. Les layouts

Ces composants représentés par la classe org.apache.log4j.Layout permettent de définir le format du message avant son envoi vers ses cibles de destination. Un layout est associé à un Appender lors de son instanciation.

Il existe plusieurs layouts définis par log4j :

Il est possible de créer ses propres layouts en dérivant de la classe Layout.

 

19.2.5.1. SimpleLayout

La classe org.apache.log4j.SimpleLayout formate le message de façon basique en incluant :

Exemple :
package com.jmdoudoux.test.log4j;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;
import org.apache.log4j.FileAppender;

public class TestLog4j8 {
  static Logger logger = Logger.getLogger(TestLog4j8.class);

  public static void main(
      String args[]) {
    SimpleLayout layout = new SimpleLayout();

    FileAppender appender = null;
    try {
      appender = new FileAppender(layout, "c:/monapp.log", false);
    } catch (Exception e) {
      e.printStackTrace();
    }

    logger.addAppender(appender);
    logger.setLevel((Level) Level.DEBUG);

    logger.debug("msg de debogage");
    logger.info("msg d'information");
    logger.warn("msg d'avertissement");
    logger.error("msg d'erreur");
    logger.fatal("msg d'erreur fatale");
  }
}
Résultat : le fichier monapp.log
DEBUG - msg de debogage
INFO - msg d'information
WARN - msg d'avertissement
ERROR - msg d'erreur
FATAL - msg d'erreur fatale

 

19.2.5.2. HTMLLayout

La classe org.apache.log4j.HTMLLayout formate les messages dans un tableau HTML.

Propriété

Rôle

Valeur par défaut

LocationInfo

Inclure des informations sur la classe

False

Title

Précise le titre de la page web

Log4j Log Messages


Exemple :
package com.jmdoudoux.test.log4j;

import java.io.*;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.HTMLLayout;
import org.apache.log4j.WriterAppender;

public class TestLog4j16 {
  static Logger logger = Logger.getLogger(TestLog4j16.class);

  public static void main(
      String args[]) {
    HTMLLayout layout = new HTMLLayout();
    WriterAppender appender = null;
    try {
      FileOutputStream output = new FileOutputStream("c:/log_monapp.htm");
      appender = new WriterAppender(layout, output);
    } catch (Exception e) {
      e.printStackTrace();
    }
    logger.addAppender(appender);
    logger.setLevel((Level) Level.DEBUG);

    logger.debug("msg de debogage");
    logger.info("msg d'information");
    logger.warn("msg d'avertissement");
    logger.error("msg d'erreur");
    logger.fatal("msg d'erreur fatale");
  }
}

 

19.2.5.3. XMLLayout

La classe org.apache.log4j.XMLLayout formate les messages en XML.

Exemple :
<log4j:event logger="com.jmdoudoux.test.log4j.TestLog4j10" 
	  timestamp="1219683683344" level="DEBUG" thread="main">
<log4j:message><![CDATA[mon message]]></log4j:message>
</log4j:event>

 

19.2.5.4. PatternLayout

Le PatternLayout permet de préciser le format du message grâce à un motif dont certaines séquences seront dynamiquement remplacées par leurs valeurs correspondantes à l'exécution.

Les séquences commencent par un caractère % suivi d'une lettre :

Motif

Rôle

%c

Le nom de la catégorie ou du logger qui a émis le message

%C

Le nom de la classe qui a émis le message : l'utilisation de ce motif est coûteuse en ressources

%d

Le timestamp de l'émission du message. Il est possible de fournir un format pour la date/heure en utilisant les motifs de la classe SimpleDateFormat.

Exemple : %d{dd MMM yyyy HH:MM:ss }

Pour améliorer les performances, il est possible d'utiliser des formateurs de dates en précisant ABSOLUTE, RELATIVE ou ISO8601

Exemple : %d{ABSOLUTE}

Sans format précisé, c'est le format défini dans la norme ISO8601 qui est utilisé.

%m

Le message

%n

Un saut de ligne dépendant de la plate-forme

%p

Le niveau de gravité du message

%r

Le nombre de millisecondes écoulées entre le lancement de l'application et l'émission du message

%t

Le nom du thread

%x

NDC (Nested Diagnostic Context) du thread. Ceci est particulièrement utile pour les applications de type web.

%%

Le caractère %

%L

Le numéro de ligne dans le code émettant le message : l'utilisation de ce motif est coûteuse en ressources

%F

Le nom du fichier émettant le message : l'utilisation de ce motif est coûteuse en ressources

%M

Le nom de la méthode émettant le message : l'utilisation de ce motif est coûteuse en ressources

%l

Des informations sur l'origine du message dans le code source (C'est un raccourci dépendant de la JVM qui correspond généralement à %C.%M(%F:%L)): l'utilisation de ce motif est coûteuse en ressources


Il est possible de préciser le formatage de chaque motif grâce à un alignement et/ou une troncature. Dans le tableau ci-dessous, le caractère # représente une des lettres du tableau ci-dessus, n représente un nombre de caractères.

Motif

Rôle

%#

Aucun formatage (par défaut)

%n#

Alignement à droite, des blancs sont ajoutés si la taille du motif est inférieure à n caractères

%-n#

Alignement à gauche, des blancs sont ajoutés si la taille du motif est inférieure à n caractères

%.n

Tronque le motif s'il est supérieur à n caractères

%-n.n#

Alignement à gauche, taille du motif obligatoirement de n caractères (troncature ou complément avec des blancs)


Le motif par défaut du PatternLayout est %m%n.

Le motif permet donc une grande souplesse dans le formatage du message.

Exemple :
package com.jmdoudoux.test.log4j;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.ConsoleAppender;

public class TestLog4j15 {
  static Logger logger = Logger.getLogger(TestLog4j15.class);

  public static void main(String args[]) {

    StringBuilder motif = new StringBuilder();
    motif.append("Date/heure : %d{yyyy-MM-dd HH:mm:ss.SSS} %n");
    motif.append("Classe emettrice : %C %n");
    motif.append("Localisation : %l %n");
    motif.append("Message: %m %n");
    motif.append("%n");

    PatternLayout layout = new PatternLayout(motif.toString());
    ConsoleAppender appender = new ConsoleAppender(layout);

    logger.addAppender(appender);
    logger.setLevel((Level) Level.DEBUG);

    logger.debug("msg de debogage");
    logger.info("msg d'information");
    logger.warn("msg d'avertissement");
    logger.error("msg d'erreur");
    logger.fatal("msg d'erreur fatale");
  }
}
Résultat :
Date/heure : 2008-08-03 11:26:13.705 
Classe emettrice : com.jmdoudoux.test.log4j.TestLog4j15 
Localisation : com.jmdoudoux.test.log4j.TestLog4j15.main(TestLog4j15.java:26) 
Message: msg de debogage 

Date/heure : 2008-08-03 11:26:13.705 
Classe emettrice : com.jmdoudoux.test.log4j.TestLog4j15 
Localisation : com.jmdoudoux.test.log4j.TestLog4j15.main(TestLog4j15.java:27) 
Message: msg d'information 

Date/heure : 2008-08-03 11:26:13.705 
Classe emettrice : com.jmdoudoux.test.log4j.TestLog4j15 
Localisation : com.jmdoudoux.test.log4j.TestLog4j15.main(TestLog4j15.java:28) 
Message: msg d'avertissement 

Date/heure : 2008-08-03 11:26:13.705 
Classe emettrice : com.jmdoudoux.test.log4j.TestLog4j15 
Localisation : com.jmdoudoux.test.log4j.TestLog4j15.main(TestLog4j15.java:29) 
Message: msg d'erreur 

Date/heure : 2008-08-03 11:26:13.705 
Classe emettrice : com.jmdoudoux.test.log4j.TestLog4j15 
Localisation : com.jmdoudoux.test.log4j.TestLog4j15.main(TestLog4j15.java:30) 
Message: msg d'erreur fatale 

Voici un exemple de configuration dans un fichier de configuration XML.

Exemple :
...
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern"
        value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-8p [%t]:%C - %m%n" />
    </layout>
...
Résultat :
2008-08-03 09:42:19.342 DEBUG  [main]:com.jmdoudoux.test.log4j.TestLog4j1 - msg de debogage
2008-08-03 09:42:19.342 INFO  [main]:com.jmdoudoux.test.log4j.TestLog4j1 - msg d'information
2008-08-03 09:42:19.342 WARN  [main]:com.jmdoudoux.test.log4j.TestLog4j1 - msg d'avertissement
2008-08-03 09:42:19.342 ERROR [main]:com.jmdoudoux.test.log4j.TestLog4j1 - msg d'erreur
2008-08-03 09:42:19.342 FATAL [main]:com.jmdoudoux.test.log4j.TestLog4j1 - msg d'erreur fatale

 

19.2.6.  L'externalisation de la configuration

Log4j peut être entièrement configuré directement dans le code de l'application.

Attention : dans ce cas, la configuration d'un appender nécessite généralement l'appel à la méthode activateOptions() de l'instance de l'Appender pour qu'elle soit prise en compte.

Cependant Log4j est généralement mis en oeuvre avec une externalisation de sa configuration pour que ses paramètres ne soient pas codés en dur. Les paramètres sont alors modifiables sans avoir à recompiler le code de l'application ce qui offre plus de souplesse dans l'utilisation de Log4j.

La configuration de Log4j commence par la définition du ou des appenders qui seront utilisés. Il faut ensuite définir le ou les loggers en leur associant au besoin un ou plusieurs appenders. Pour que Log4j fonctionne, il faut au minimum associer un appender au logger racine.

Log4j propose deux formats pour externaliser sa configuration :

Le format XML est plus verbeux mais il est mieux structuré. De plus, certaines fonctionnalités ne sont configurables que dans ce format. C'est donc le format dont l'utilisation est recommandée.

Dans les fichiers de configuration, la valeur d'une propriété peut être initialisée avec une variable d'environnement de la JVM en utilisant la syntaxe ${nom_propriété}

 

19.2.6.1. Les principes généraux

L'initialisation de Log4j n'a besoin d'être réalisée qu'une seule fois de préférence au lancement de l'application.

La configuration suit la même logique que celle des loggers : il est inutile de définir tous les loggers puisque le principe d'héritage permet automatiquement à un logger d'obtenir les caractéristiques de son ascendant le plus proche pour lequel une configuration particulière a été précisée.

Exemple :

Logger

Niveau de gravité

Affectation par

rootLogger

error

assignation (debug par défaut)

com

error

héritage

com.jmdoudoux

error

héritage

com.jmdoudoux.test

info

assignation

com.jmdoudoux.test.log4j

info

héritage

com.jmdoudoux.test.log4j.MaClasse

debug

assignation


Ceci peut permettre de très finement régler le niveau de gravité des différents éléments qui composent une application que ce soit dans les classes de l'application ou d'une bibliothèque tierce.

La configuration au niveau des appenders utilisés suit aussi une logique hiérarchique qui n'est pas de l'héritage mais une additivité. Un appender défini dans un logger s'ajoute à ou aux appenders déjà définis dans les loggers de la hiérarchie mère.

 

19.2.6.1.1. Le mécanisme de recherche de la configuration

Log4j propose par défaut un mécanisme de recherche de sa configuration. Log4j recherche un fichier de configuration dans le classpath car il utilise un classLoader pour cette tâche.

Ce mécanisme de recherche peut être désactivé en positionnant à true la propriété système log4j.defaultInitOverride. Ceci doit être utilisé si le chargement de la configuration est fait manuellement dans l'application.

La propriété système log4j.configuration peut être utilisée pour préciser le nom du fichier de configuration.

Par défaut, Log4j recherche dans le classpath un fichier nommé log4j.xml. Si ce fichier n'est pas trouvé, Log4j recherche un fichier nommé log4j.properties.

Log4j utilise un objet de type org.apache.log4j.spi.Configurator pour charger la configuration.

La propriété log4j.configuratorClass permet de préciser explicitement la classe à utiliser pour charger la configuration.

Par défaut, Log4j utilise un objet de type DomConfigurator pour charger un fichier au format XML sinon c'est un objet de type ProperyConfigurator qui est utilisé pour charger le fichier properties.

Vu le mécanisme par défaut proposé par Log4j, le plus simple est donc de nommer son fichier de configuration log4j.xml ou log4j.properties selon le format de configuration utilisé et de mettre le fichier dans le classpath.

 

19.2.6.2. Le chargement explicite d'une configuration

Si le mode de fonctionnement par défaut ne répond pas aux besoins, il est possible de demander explicitement le chargement d'une configuration.

Pour effectuer ce chargement, l'API fournit plusieurs classes qui implémentent l'interface Configurator. La classe BasicConfigurator est la classe mère des classes PropertyConfigurator (pour la configuration par un fichier de propriétés) et DOMConfigurator (pour la configuration par un fichier XML).

 

19.2.6.2.1. La classe BasicConfigurator

La classe org.apache.log4j.BasicConfigurator permet de créer une configuration basique.

Avant la version 1.2 de Log4j, la classe BasicConfigurator permettait de configurer la catégorie root avec des valeurs par défaut. L'appel à la méthode configure() ajoutait à la catégorie root la priorité DEBUG et un ConsoleAppender vers la sortie standard (System.out) associé à un PatternLayout (TTCC_CONVERSION_PATTERN qui est une constante définie dans la classe PatternLayout).

Exemple :
import org.apache.log4j.*;

public class TestBasicConfigurator {
  static Category cat = Category.getInstance(TestBasicConfigurator.class.getName()) ;
  
  public static void main(String[] args) {
    BasicConfigurator.configure();
    cat.info("Mon message");
  }
}
Résultat :
0 [main] INFO TestBasicConfigurator  - Mon message

A partir de la version 1.2 de Log4j, la méthode configure() instancie une configuration où le rootLogger utilise un appender de type ConsoleAppender et un motif PatternLayout.TTCC_CONVERSION_PATTERN pour cet appender. Le niveau de gravité associé est DEBUG par défaut.

Exemple avec Log4j 1.2 :

Exemple :
package com.jmdoudoux.test.log4j;

import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;

public class TestLog4j14 {
  static Logger logger = Logger.getLogger(TestLog4j14.class);

  public static void main(
      String[] args) {
    
    BasicConfigurator.configure();
    
    logger.info("debut");
    System.out.println("traitement");
    logger.debug("maValeur");
    logger.info("fin");
  }
}
Résultat :
0 [main] INFO
com.jmdoudoux.test.log4j.TestLog4j14  -
debut
traitement
0 [main] DEBUG
com.jmdoudoux.test.log4j.TestLog4j14  -
maValeur
0 [main] INFO
com.jmdoudoux.test.log4j.TestLog4j14  -
fin

 

19.2.6.2.2. La classe PropertyConfigurator

La classe org.apache.log4j.PorpertyConfigurator lit un fichier de configuration au format properties et instancie la configuration correspondante.

La classe PropertyConfigurator permet de configurer Log4j à partir d'un fichier de propriétés ce qui évite la recompilation de classes pour modifier la configuration. La méthode configure() qui attend en paramètre un nom de fichier permet de charger la configuration.

Exemple :
import org.apache.log4j.*;

public class TestLogging6 {
   static Category cat = Category.getInstance(TestLogging6.class.getName());
   
   public static void main(String[] args) {
      PropertyConfigurator.configure("logging6.properties");
      cat.info("Mon message");
   }
}
Exemple : le fichier loggin6.properties de configuration de Log4j
# Affecte a la catégorie root la priorité DEBUG et un appender nommé CONSOLE_APP
log4j.rootCategory=DEBUG, CONSOLE_APP
# l'appender CONSOL_APP est associé à la console
log4j.appender.CONSOLE_APP=org.apache.log4j.ConsoleAppender
# CONSOLE_APP utilise un PatternLayout qui affiche : le nom du thread, la priorité, 
# le nom de la catégorie et le message
log4j.appender.CONSOLE_APP.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE_APP.layout.ConversionPattern= [%t] %p %c - %m%n
Résultat :
C:\>java TestLogging6
[main] INFO TestLogging6 - Mon message

 

19.2.6.2.3. La classe DOMConfigurator

La classe DOMConfigurator permet de configurer Log4j à partir d'un fichier XML ce qui évite aussi la recompilation de classes pour modifier la configuration. La méthode configure() qui attend un nom de fichier permet de charger la configuration. Cette méthode nécessite un parser XML de type DOM compatible avec l'API JAXP.

Le fichier de configuration au format XML doit respecter la DTD log4j.dtd fournie dans la bibliothèque Log4j.

Exemple :
import org.apache.log4j.*;
import org.apache.log4j.xml.*;

public class TestLogging7 {
  static Category cat = Category.getInstance(TestLogging7.class.getName());

  public static void main(String[] args) {
    try {
      DOMConfigurator.configure("logging7.xml");
    } catch (Exception e) {
      e.printStackTrace();
    }
    cat.info("Mon message");
  }
}
Exemple : le fichier loggin7.xml de configuration de log4j
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
  <appender name="CONSOLE_APP" class="org.apache.log4j.ConsoleAppender">
     <layout class="org.apache.log4j.PatternLayout">
     <param name="ConversionPattern" value="[%t] %p %c - %m%n"/>
         </layout>
  </appender>
  <root>
    <priority value ="DEBUG" />
    <appender-ref ref="CONSOLE_APP" />
  </root>
</log4j:configuration>
Résultat :
C:\j2sdk1.4.0-rc\bin>java TestLogging7
[main] INFO TestLogging7 - Mon message

 

19.2.6.3. Les formats des fichiers de configuration

Le fichier de configuration permet :

Il est préférable d'utiliser un fichier de configuration plutôt que de configurer les entités de Log4j dans le code source car cette dernière solution implique une modification du code et une recompilation pour être prise en compte.

Le fichier de configuration permet basiquement de définir le niveau de gravité minimum à traiter, les flux de sorties (Appender) et le format des messages (Layout).

Deux formats de fichiers de configuration sont proposés par Log4j :

L'ordre de déclaration des loggers dans le fichier de configuration n'est pas imposé mais il est préférable de conserver un ordre hiérarchique pour en faciliter la lecture et la compréhension.

Généralement le fichier de configuration est lu et utilisé au lancement de l'application.

Chaque appender possède ses propres propriétés de configuration.

Celles-ci sont définies de façons différentes selon le format du fichier de configuration :

 

19.2.6.4. La configuration par fichier properties

La configuration utilisant un fichier properties est historiquement la plus ancienne : de nombreuses applications utilisant Log4j la mettent encore en oeuvre.

Comme pour tous fichiers properties, les lignes qui commencent par un caractère dièse sont des lignes de commentaires et sont donc ignorées.

Plusieurs options de configuration générale peuvent être définies dans le fichier de configuration :

Options

Description

log4j.debug

Booléen qui précise si Log4j doit fournir des informations de debogage sur ses activités de chargement du fichier de configuration. La valeur par défaut est false.

log4j.disable

Précise le niveau de gravité minimum des messages pour être traités par tous les loggers/categorys.
Remarque : l'option log4j.disableOverrider doit obligatoirement être positionnée à false.

log4j.disableOverride

Doit être positionnée à true pour utiliser l'option log4j.disable. La valeur par défaut est false.


La clé log4j.threshold permet de préciser un niveau minimum de gravité pour tous les loggers ou category définis indépendamment du niveau spécifié pour chacun d'eux.

Remarque : l'ordre de déclaration des clés dans le fichier n'a pas d'importance pour la bonne mise en oeuvre de Log4j mais il est cependant recommandé d'utiliser un ordre logique pour faciliter la compréhension du paramétrage.

Une category est définie en utilisant une clé de la forme :

log4j.category.nom_category

Un logger est défini en utilisant une clé de la forme :

log4j.logger.nom_logger

La category racine est configurée en utilisant une clé de la forme :

log4j.categoryLogger

Le logger racine est configuré en utilisant une clé de la forme :

log4j.rootLogger

La valeur de ces clés est de la forme niveau_gravité, nom_appender1, nom_appender2, ...

Exemple :
# le niveau de gravité debug est associe à la catégorie racine avec deux 
# appenders nommés A1 et A2
log4j.rootCategory=DEBUG, A1, A2
Exemple :
# le niveau de gravité debug est associe au logger racine avec deux 
# appenders nommés A1 et A2
log4j.rootLogger=DEBUG, A1, A2

Remarque : chaque appender doit avoir un nom unique

Le niveau de gravité est optionnel mais dans le cas où il n'est pas fourni, il est impératif de laisser la virgule entre le signe = et le nom du premier appender.

Exemple :
# le niveau de gravité debug (par défaut) est associe au logger racine 
# avec un appender nommé A1
   log4j.rootLogger=, A1

Un appender est défini en utilisant une clé de la forme :

log4j.appender.nom_appender

La valeur de cette clé est le nom pleinement qualifié de la classe qui encapsule l'appender.

Exemple :
      # l'appender nommé A1 est de type ConsoleAppender
      log4j.appender.A1=org.apache.log4j.ConsoleAppender
      # l'appender nommé A2 est de type RollingFileAppender
      log4j.appender.A2=org.apache.log4j.RollingFileAppender

Le layout d'un appender est précisé en utilisant une clé de la forme :

log4j.appender.nom_appender.layout

La valeur de cette clé est le nom pleinement qualifié de la classe qui encapsule le layout.

Exemple :
   log4j.appender.A1.layout=org.apache.log4j.PatternLayout

Une propriété d'un layout est précisée en utilisant une clé de la forme :

log4j.appender.nom_appender.layout.nom_propriété

La valeur sera fournie à la propriété correspondante par introspection.

Exemple :
   log4j.appender.A1.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%-5p] %c- %m%n
   log4j.appender.A2.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%-5p] %c- %m%n
   log4j.appender.A2.File=monapp.log
   log4j.appender.A2.MaxFileSize=1024KB
   log4j.appender.A2.MaxBackupIndex=2

Pour modifier le niveau de gravité pris en compte par un logger il faut utiliser une clé de la forme log4j.logger.nom_logger. La valeur de cette clé doit être le niveau de gravité minimum qui sera traité par le logger.

Exemple :
   log4j.logger.com.jmdoudoux.test=INFO

Il est possible de supprimer l'additivité des appenders d'un logger en utilisant une clé de la forme log4j.additivity.nom_logger. La valeur est un booléen qui précise l'additivité des appenders (la valeur par défaut est true, il faut mettre false pour la supprimer).

Exemple :
   log4j.additivity.com.jmdoudoux.test=false

Il est possible de fournir comme valeur d'une clé la valeur d'une propriété système définie dans la JVM. Pour obtenir la valeur d'une de ces propriétés, il suffit d'utiliser la syntaxe ${nom_de_la_propriete}

 

19.2.6.5. La configuration par un fichier XML

La structure des données contenues dans le fichier XML est organisée en plusieurs parties définies dans la DTD log4j.dtd et comprend :

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="false"
  xmlns:log4j="http://jakarta.apache.org/log4j/">
  <appender name="console" class="org.apache.log4j.ConsoleAppender">
    <param name="Target" value="System.out" />
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern"
        value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-8p [%t]:%C - %m%n" />
    </layout>
  </appender>
  <root>
    <appender-ref ref="console" />
  </root>
</log4j:configuration>

Si le fichier n'est pas valide alors une exception est levée durant sa lecture et son traitement.

Exemple :
log4j:WARN Fatal parsing error 12 and column 5
log4j:WARN The element type "param" must be terminated by the matching end-tag "</param>".
log4j:ERROR Could not parse url 
[file:/C:/Documents%20and%20Settings/jmd/workspace/TestLog4j/bin/log4j.xml].
org.xml.sax.SAXParseException: The element type "param" must be terminated 
by the matching end-tag "</param>".

Les différents éléments qui composent le fichier de configuration sont détaillés dans les sections suivantes.

 

19.2.6.5.1. Le format du fichier de configuration XML

Le fichier de configuration commence par un prologue et une déclaration de la DTD.

La structure du document xml qui va contenir la configuration de Log4j est définie dans la DTD log4j.dtd.

Cette DTD est fournie dans la bibliothèque log4j.jar dans le package org.apache.log4j.xml

Exemple :
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

La DTD définit l'élément racine comme suit :

Exemple :
<!ELEMENT log4j:configuration (renderer*, appender*,plugin*, (category|logger)*,root?,
                               (categoryFactory|loggerFactory)?)>

L'élément racine est le tag <configuration> associé à l'espace de nommage log4j.

Exemple :
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
</log4j:configuration>

Le tag <configuration> peut avoir

La définition des éléments doit impérativement respecter cet ordre défini dans la DTD.

La DTD définit trois attributs pour le tag <configuration>.

Exemple :
<!ATTLIST log4j:configuration
  xmlns:log4j              CDATA #FIXED "http://jakarta.apache.org/log4j/" 
  threshold                (all|trace|debug|info|warn|error|fatal|off|null) "null"
  debug                    (true|false|null)  "null"
  reset                    (true|false) "false">

Le tag racine est le tag <configuration> qui possède trois attributs :

L'attribut debug est particulièrement utile pour comprendre l'utilisation du fichier de configuration et résoudre d'éventuel problème dans son contenu.

 

19.2.6.5.2. La configuration d'un appender

La configuration d'un appender se fait en utilisant un tag <appender>.

La DTD définit l'élément appender comme suit :

Exemple :
<!ELEMENT appender (errorHandler?, param*,
      rollingPolicy?, triggeringPolicy?, connectionSource?,
      layout?, filter*, appender-ref*)>
<!ATTLIST appender
  name         CDATA   #REQUIRED
  class        CDATA   #REQUIRED
>

Le tag <appender> possède deux attributs obligatoires :

Le tag fils facultatif <param> permet de fournir un paramètre à l'appender. Chaque appender possède ses propres paramètres. Le tag <param> permet de fournir des valeurs aux propriétés de l'appender dont le nom correspond à l'attribut name et la valeur à l'attribut value.

Exemple :
<appender name="console" class="org.apache.log4j.ConsoleAppender">
  <param name="Target" value="System.out"/>
</appender>

Le tag facultatif layout permet de préciser le layout associé à l'appender. Le tag <layout> possède l'attribut obligatoire class qui précise le nom pleinement qualifié de la classe qui encapsule le layout.

Exemple :
<appender name="console" class="org.apache.log4j.ConsoleAppender">
  <layout class="org.apache.log4j.SimpleLayout"/>
</appender>

Des paramètres peuvent aussi être fournis au layout en utilisant un ou plusieurs tags fils <param>. Chaque layout possède ses propres propriétés.

Exemple :
<appender name="console" class="org.apache.log4j.ConsoleAppender">
  <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="=%d{yyyy-MM-dd HH:mm:ss} [%-5p] %c- %m%n" />     
  </layout> 
</appender>

 

19.2.6.5.3. La configuration des Loggers

La configuration d'un logger se fait en utilisant un tag <logger>.

La DTD définit l'élément logger comme suit :

Exemple :
<!ELEMENT logger (level?,appender-ref*)>
<!ATTLIST logger
  name         CDATA   #REQUIRED
  additivity   (true|false) "true"  
>

Le tag <logger> possède un attribut obligatoire :

Le tag <logger> possède un attribut facultatif :

Le tag logger peut avoir deux types de tag fils : <level> et <appender-ref>

Le tag facultatif level permet de préciser le niveau de gravité associé au logger. L'attribut value permet de préciser ce niveau de gravité.

Exemple :
<logger name="com.jmdoudoux.test.monapp">
  <level value="info"/> 
</logger>

Le tag <appender-ref> permet d'associer un nouvel appender au logger en plus de ceux associés par additivité des loggers de hiérarchie supérieure. L'attribut ref permet de préciser le nom de l'appender concerné. La valeur de cet attribut doit correspondre à une valeur d'un attribut name d'un appender défini dans la configuration.

Un tag <logger> peut avoir aucun, un ou plusieurs tags <appender-ref> puisqu'un logger peut avoir plusieurs appenders.

Exemple :
<logger name="com.jmdoudoux.test.monapp">    
  <appender-ref ref="console" />  
  <appender-ref ref="journal" />  
</logger>

 

19.2.6.5.4. La configuration du logger racine

La configuration du logger racine se fait en utilisant un tag <root>.

La DTD définit l'élément logger comme suit :

Exemple :
<!ELEMENT root (param*, (priority|level)?, appender-ref*)>

Sa configuration est similaire à celle des loggers sauf que le tag <root> ne possède pas d'attribut.

Exemple :
<root>
  <priority value ="info" />
  <appender-ref ref="console"/>
</root>

 

19.2.6.5.5. Les seuils et les filtres pour les appenders

La propriété threshold permet de définir un seuil minimum de niveau de gravité des messages traités par l'appender.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="false"
  xmlns:log4j="http://jakarta.apache.org/log4j/">
  <appender name="console" class="org.apache.log4j.ConsoleAppender">
    <param name="threshold" value="ERROR" />
    <param name="Target" value="System.out" />
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern"
        value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-8p [%t]:%C - %m%n" />
    </layout>
  </appender>
  <root>
    <appender-ref ref="console" />
  </root>
</log4j:configuration>
Résultat :
2008-08-03 10:03:11.895 ERROR [main]:com.jmdoudoux.test.log4j.TestLog4j1 - msg d'erreur
2008-08-03 10:03:11.910 FATAL [main]:com.jmdoudoux.test.log4j.TestLog4j1 - msg d'erreur fatale

Log4j propose un autre mécanisme plus puissant pour filtrer les messages traités par un appender : les filtres.

Log4j propose plusieurs filtres en standard :

Les filtres ne peuvent être utilisés que dans une configuration par un fichier xml.

Le filtre LevelMatchFilter possède plusieurs paramètres :

Paramètres

Rôle

levelToMatch

Précise le niveau de gravité du message pour qu'il soit traité

acceptOnMatch

Booléen qui indique si le message est traité (true) ou rejeté (false) par le filtre


Le filtre LevelMatchFilter traite les messages qui correspondent au filtre mais laisse passer ceux qui ne correspondent pas. Ainsi pour ignorer ces messages, il est nécessaire d'appliquer en plus un filtre de type DenyAllFilter.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="false"
  xmlns:log4j="http://jakarta.apache.org/log4j/">
  <appender name="console" class="org.apache.log4j.ConsoleAppender">
    <param name="Target" value="System.out" />
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern"
        value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-8p [%t]:%C - %m%n" />
    </layout>
    <filter class="org.apache.log4j.varia.LevelMatchFilter">
      <param name="levelToMatch" value="ERROR" />
    </filter>
    <filter class="org.apache.log4j.varia.DenyAllFilter"/>
  </appender>
  <root>
    <appender-ref ref="console" />
  </root>
</log4j:configuration>
Résultat :
2008-08-03 10:14:49.203 ERROR    [main]:com.jmdoudoux.test.log4j.TestLog4j1 - msg d'erreur

Si le filtre DenyAllFilter n'est pas utilisé alors tous les messages sont traités.

Résultat :
2008-08-03 10:19:28.612 DEBUG [main]:com.jmdoudoux.test.log4j.TestLog4j1 - msg de debogage
2008-08-03 10:19:28.612 INFO  [main]:com.jmdoudoux.test.log4j.TestLog4j1 - msg d'information
2008-08-03 10:19:28.612 WARN  [main]:com.jmdoudoux.test.log4j.TestLog4j1 - msg d'avertissement
2008-08-03 10:19:28.612 ERROR [main]:com.jmdoudoux.test.log4j.TestLog4j1 - msg d'erreur
2008-08-03 10:19:28.612 FATAL [main]:com.jmdoudoux.test.log4j.TestLog4j1 - msg d'erreur fatale

Le filtre LevelRangeFilter possède plusieurs paramètres :

Paramètres

Rôle

levelMin

Précise le niveau de gravité minimal du message pour qu'il soit traité

levelMax

Précise le niveau de gravité maximal du message pour qu'il soit traité

acceptOnMatch

true : le message est traité sans appliquer les autres filtres

false : si le niveau de gravité est hors de la plage, alors le message est ignoré sinon le message est soumis aux autres filtres


Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="false"
  xmlns:log4j="http://jakarta.apache.org/log4j/">
  <appender name="console" class="org.apache.log4j.ConsoleAppender">
    <param name="Target" value="System.out" />
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern"
        value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-8p [%t]:%C - %m%n" />
    </layout>
    <filter class="org.apache.log4j.varia.LevelRangeFilter">
      <param name="levelMin" value="INFO" />
      <param name="levelMax" value="ERROR" />
    </filter>
  </appender>
  <root>
    <appender-ref ref="console" />
  </root>
</log4j:configuration>
Résultat :
2008-08-03 10:44:46.636 INFO  [main]:com.jmdoudoux.test.log4j.TestLog4j1 - msg d'information
2008-08-03 10:44:46.636 WARN  [main]:com.jmdoudoux.test.log4j.TestLog4j1 - msg d'avertissement
2008-08-03 10:44:46.636 ERROR [main]:com.jmdoudoux.test.log4j.TestLog4j1 - msg d'erreur

Avec les filtres, il est par exemple possible de définir un appender qui traite les messages de debogage et un appender qui traite les autres messages.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd" >
<log4j:configuration>
  <appender name="fichierLog"
    class="org.apache.log4j.RollingFileAppender">
    <param name="maxFileSize" value="1024KB" />
    <param name="maxBackupIndex" value="2" />
    <param name="File" value="c:/monapp.log" />
    <param name="threshold" value="info" />
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern"
        value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-8p [%t]:%C - %m%n" />
    </layout>
  </appender>
  
  <appender name="fichierDebug"
    class="org.apache.log4j.RollingFileAppender">
    <param name="maxFileSize" value="1024KB" />
    <param name="maxBackupIndex" value="2" />
    <param name="File" value="c:/monapp_debug.log" />
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern"
        value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-8p [%t]:%C - %m%n" />
    </layout>
    <filter class="org.apache.log4j.varia.LevelMatchFilter">
      <param name="levelToMatch" value="DEBUG" />
    </filter>
    <filter class="org.apache.log4j.varia.DenyAllFilter"/>
  </appender>

  <root>
    <priority value="debug"></priority>
    <appender-ref ref="fichierLog" />
    <appender-ref ref="fichierDebug" />
  </root>
</log4j:configuration>

 

19.2.6.6. log4j.xml versus log4j.properties

La configuration par fichier properties est moins verbeuse que par fichier XML.

Certaines fonctionnalités ne sont pas supportées en utilisant la configuration par properties comme l'utilisation des Filters ou des ErrorHandlers. Certains appenders ne sont configurables que par fichier XML.

 

19.2.6.7. La conversion du format properties en format XML

La conversion d'un fichier de configuration au format properties en un fichier de configuration au format XML doit se faire manuellement.

Voici un premier exemple simple.

Exemple :
log4j.rootLogger=info, console 
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.SimpleLayout
Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd" >
<log4j:configuration>
  <appender name="console" class="org.apache.log4j.ConsoleAppender">
    <layout class="org.apache.log4j.SimpleLayout" />
  </appender>
  <root>
    <priority value="info" />
    <appender-ref ref="console" />
  </root>
</log4j:configuration>

Le second exemple ci-dessous utilise deux appenders.

Exemple :
log4j.rootLogger=debug, console, fichier

log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %-8p [%t]:%C - %m%n

log4j.appender.fichier=org.apache.log4j.RollingFileAppender
log4j.appender.fichier.File=c:/monapp.log
log4j.appender.fichier.MaxFileSize=1024KB
log4j.appender.fichier.MaxBackupIndex=2
log4j.appender.fichier.layout=org.apache.log4j.PatternLayout
log4j.appender.fichier.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %-8p [%t]:%C - %m%n
Exemple :
<?xml version="1.0" encoding="UTF-8" ?>
!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
  <appender name="console" class="org.apache.log4j.ConsoleAppender">
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern"
        value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-8p [%t]:%C - %m%n" />
    </layout>
  </appender>
  <appender name="fichier"
    class="org.apache.log4j.RollingFileAppender">
    <param name="file" value="c:/monapp.log" />
    <param name="MaxFileSize" value="1024KB" />    
	<param name="MaxBackupIndex" value="2" /></
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern"
        value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-8p [%t]:%C - %m%n" />
    </layout>
  </appender>
  <root>
    <priority value="debug" />
    <appender-ref ref="console" />
    <appender-ref ref="fichier" />
  </root>
</log4j:configuration></pre>

 

19.2.7. La mise en oeuvre avancée

Cette section présente quelques fonctionnalités avancées de Log4j.

 

19.2.7.1. La lecture des logs

La consultation des logs peut ne pas être facile si elle doit être réalisée en temps réel ou si le volume de messages est très important.

 

19.2.7.1.1. La lecture pendant l'exécution du programme

Si l'application écrit régulièrement dans le fichier, un simple bloc note n'est pas suffisant pour lire les messages arrivés après l'ouverture du fichier.

Sous Unix, la commande tail est particulièrement utile car elle permet de visualiser les n dernières lignes d'un fichier alors que celui-ci est en train de grossir.

 

19.2.7.1.2. L'application Chainsaw

Log4j propose en standard une application graphique nommée chainsaw qui permet de visualiser des logs formatées avec un layout XMLLayout ou envoyées par un SocketAppender.

Pour exécuter Chainsaw, il faut exécuter la classe org.apache.log4j.chainsaw.Main

Exemple :
C:\>java -cp C:/java/apache-log4j-1.2.15/log4j-1.2.15.jar org.apache.log4j.chainsaw.Main

Pour consulter un fichier XML qui contient des logs formatées avec un XMLLayout, il faut utiliser l'option "Load File" du menu "File".

La partie "Controls" propose plusieurs filtres : il suffit de saisir les caractères recherchés et le filtre est appliqué au fur et à mesure de la saisie.

ChainSaw est aussi très pratique pour consulter les logs envoyées par un SocketAppender.

Il faut définir une variable d'environnement de la JVM nommée chainsaw.port pour préciser le port à écouter.

Exemple :
C:\>java -cp C:/java/apache-log4j-1.2.15/log4j-1.2.15.jar -Dchainsaw.port=10256 
org.apache.log4j.chainsaw.Main

 

19.2.7.2. Les variables d'environnement

Log4j utilise plusieurs variables d'environnement de la JVM pour éventuellement modifier certains comportements.

Variable

Rôle

log4j.debug

Fournir des informations de débogage lors de la recherche de la configuration et de son chargement

log4j.configuration

Permet de préciser le nom du fichier properties qui contient la configuration. Ce fichier doit être dans le classpath

log4j.defaultInitOverride

Booléen qui permet de demander d'inhiber la recherche de la configuration. La valeur par défaut est false.

 

19.2.7.3. L'internationalisation des messages

La classe Category et par héritage la classe Logger proposent deux surcharges de la méthode l7dlog() qui permettent l'émission de messages internationalisés (l7d est le raccourci de localized).

Avant la première utilisation de la méthode l7dlog, il est nécessaire de préciser quel ResourceBundle doit être utilisé en invoquant la méthode setResourceBundle().

Les méthodes l7dlog() attendent en paramètres le niveau de gravité, la clé du message dans le resourceBundle et un objet de type Throwable. La seconde surcharge attend aussi un tableau d'objets qui seront insérés à leurs emplacements définis dans les valeurs de clés.

Exemple :
package com.jmdoudoux.test.log4j;

import java.util.Locale;
import java.util.ResourceBundle;

import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;

public class TestLog4j19 {
  static Logger logger = Logger.getLogger(TestLog4j19.class);

  public static void main(
      String args[]) {
    Logger logRoot = Logger.getRootLogger();
    ConsoleAppender ca = new ConsoleAppender();
    ca.setName("console");
    ca.setLayout(new SimpleLayout());
    ca.activateOptions();
    logRoot.addAppender(ca);
    logRoot.setLevel(Level.DEBUG);
    
    Locale locale = new Locale("fr", "FR");
    ResourceBundle messages = ResourceBundle.getBundle("MessagesLog", locale);
    logger.setResourceBundle(messages);
    
    logger.l7dlog(Level.DEBUG, "MESSAGE", null);
    
    locale = new Locale("en", "EN");
    messages = ResourceBundle.getBundle("MessagesLog", locale);
    logger.setResourceBundle(messages);
    
    logger.l7dlog(Level.DEBUG, "MESSAGE", null);
    
  }
}

Il faut définir les fichiers properties qui seront utilisés par le ResourceBundle. Ces fichiers doivent être stockés dans le classpath.

Exemple : le fichier MessagesLog.properties
MESSAGE=mon message en français
Exemple : le fichier MessagesLog_en.properties
MESSAGE=my message in English
Résultat :
DEBUG - mon message en français
DEBUG - my message in English

 

19.2.7.4. L'initialisation de Log4j dans une webapp

L'utilisation de Log4J dans une webapp est détaillée dans une section du chapitre «Les servlets» de ce tutoriel.

 

19.2.7.5. La modification dynamique de la configuration

 

en construction
Cette section sera développée dans une version future de ce document

 

19.2.7.6. NDC/MDC

Log4J propose deux fonctionnalités qui permettent d'ajouter des données contextuelles dans les logs :

Les classes NDC et MDC sont utilisées pour stocker ces informations contextuelles relatives à l'application qui peuvent être utilisées lors de la journalisation des messages. Les classes NDC et MDC proposent des méthodes statiques qui permettent de gérer des données contextuelles liées au ThreadLocal du thread courant.

Un cas typique d'utilisation est de pouvoir ajouter dans les logs d'une webapp L'identité de l'utilisateur qui est à l'origine de la trace.

La classe org.apache.log4j.NDC propose un contexte composé de chaînes de caractères qui pourront toutes être ajoutées dans le journal si le format le demande.

NDC utilise une pile pour stocker des informations contextuelles. Le NDC est stocké dans le ThreadLocal de chaque thread.

La classe NDC propose plusieurs méthodes :

Méthode Rôle
void push(String)Ajouter une chaîne de caractères dans le contexte
String pop(String)Retirer du contexte le dernier élément ajouté
void remove()Retirer le contexte du thread local
void clear()Vider le contenu de la pile

Exemple :
import org.apache.log4j.Logger;
import org.apache.log4j.NDC;

public class TestNDC {
  private static Logger LOGGER = Logger.getLogger(TestNDC.class);

  public static void main(String[] args) {
    NDC.push("Partie1");
    LOGGER.info("debut des traitements");
    NDC.push("Etape1");
   
    LOGGER.info("traitement 1.1");
    NDC.pop();
    NDC.push("Etape2");
    LOGGER.info("traitement 1.2");
    
    NDC.remove();
    LOGGER.info("fin des traitements");
  }
}

Pour insérer le contenu du NDC dans la trace, il faut utiliser la variable %x dans le pattern. Lorsqu'un message est ajouté dans le journal et que l'option %x est utilisée par le motif du layout, alors tout le contenu courant du NDC est ajouté dans le journal.

Résultat :
log4j.rootLogger=INFO, stdout
 
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p - [%x] %m%n
Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd" >
<log4j:configuration>
  <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p - [%x] %m%n" />
    </layout>
  </appender>
  <root>
    <priority value="info"></priority>
    <appender-ref ref="stdout" />
  </root>
</log4j:configuration>

Toutes les informations contenues dans le NDC sont ajoutées dans le journal.

Résultat :
2013-03-10 15:17:45 INFO  - [Partie1] debut des traitements
2013-03-10 15:17:45 INFO  - [Partie1 Etape1] traitement 1.1
2013-03-10 15:17:45 INFO  - [Partie1 Etape2] traitement 1.2
2013-03-10 15:17:45 INFO  - [] fin des traitements

Les opérations réalisées sur le NDC affectent uniquement le thread courant.

Il est recommandé de limiter au strict nécessaire les informations contenues dans le NDC pour maintenir la lisibilité des logs.

Il est de la responsabilité de l'application de gérer correctement le contenu de la pile selon le contexte.

Il faut faire attention en utilisant le NDC car celui-ci maintient une référence sur le threadlocal ce qui empêche le ramasse-miettes de libérer la mémoire même si le thread est terminé. Pour éviter ce type de soucis, il est nécessaire d'invoquer la méthode remove() de la classe NDC.

La classe org.apache.log4j.MDC associe une Map pour chaque thread dans laquelle les données contextuelles sont stockées avec le nom de la donnée comme clé associée à sa valeur.

L'utilisation de MDC est plus souple que NDC car il permet de sélectionner le ou les éléments du contexte qui doivent apparaître dans le journal.

La gestion des éléments contenus dans le MDC se fait en utilisant les méthodes put() pour ajouter, get() pour obtenir et remove() pour supprimer un élément.

Exemple :
import org.apache.log4j.Logger;
import org.apache.log4j.MDC;

public class TestMDC {
  private static Logger LOGGER = Logger.getLogger(TestNDC.class);
  public static void main(String[] args) {
    MDC.put("user","jm");
    MDC.put("partie","Partie1");
    MDC.put("etape","Etape1");
    LOGGER.info("debut des traitements");
    MDC.put("etape","Etape2");
    LOGGER.info("fin des traitements");
  }
}

Pour insérer un élément contenu dans le MDC dans le journal, il faut utiliser la variable %X dans le pattern suivie du nom de la clé entre accolades.

Résultat :
log4j.rootLogger=INFO, stdout
 
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p - 
  [%X{partie} %X{etape}] %m%n
Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM
"log4j.dtd" >
<log4j:configuration>
  <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="**%d{yyyy-MM-dd HH:mm:ss}%-5p - 
        [%X{partie} %X{etape}] %m%n" />
    </layout>
  </appender>
  <root>
    <priority value="info"></priority>
    <appender-ref ref="stdout"/>
  </root>
</log4j:configuration>

Seuls les éléments dont la clé est précisée dans le pattern sont insérés dans le journal.

Résultat :
2013-03-10 15:38:14 INFO  - [Partie1 Etape1] debut des traitements
2013-03-10 15:38:14 INFO  - [Partie1 Etape2] fin des traitements

L'utilisation de MDC requiert un JDK 1.2 minimum.

Les threads fils héritent automatiquement du MDC du thread parent.

Le choix d'utiliser NDC ou MDC dépend des besoins. Si les informations contextuelles sont imbriquées alors le NDC est le meilleur choix sinon il faut utiliser le MDC.

Avec les deux solutions, il est nécessaire de prendre en compte correctement la réinitialisation des données contextuelles : par exemple dans une application web, les données étant stockées dans le ThreadLocal, elles sont communes pour toutes les requêtes http qui sont traitées par le processeur associé à ce ThreadLocal.

Dans une application web, il est fréquent de stocker des informations contextuelles, comme l'utilisateur, l'adresse IP ou l'url, dans le NDC ou le MDC dans un point d'entrée du traitement des requêtes http, par exemple en utilisant un filtre.

 

19.2.8. Des best practices

Cette section fournit quelques conseils pour la mise en oeuvre de Log4j.

 

19.2.8.1. Le choix du niveau de gravité des messages

Le choix du niveau de gravité de chaque message émis est très important.

Voici quelques exemples d'utilisation de chaque niveau de gravité :

Hors de l'environnement de développement, le niveau de gravité minimum des messages doit être info. Le niveau debug n'est à utiliser que dans l'environnement de développement ou à utiliser temporairement pour des besoins spécifiques dans les autres environnements.

 

19.2.8.2. L'amélioration des performances

Log4j a été développé dans le souci de réduire au minimum le surcoût de son utilisation.

Cependant le logging a nécessairement un coût et ce coût peut devenir important si certaines précautions ne sont pas prises par le développeur.

Il est nécessaire de limiter le coût d'émission d'un message dont la construction est complexe surtout si ce dernier sera ignoré par le logger.

Exemple :
logger.debug("valeur="+valeur+" , i="+i+" ,next="+next);

Dans cet exemple, même si le niveau de gravité du logger est supérieur à debug, le coût de l'émission du message contiendra la création du message par concaténation des différentes valeurs.

Pour limiter ce coût, il est préférable de conditionner l'émission du message par un test préalable sur le niveau de gravité pris en charge par le logger lors de l'exécution du traitement.

Les classes Category et Logger proposent des méthodes pour effectuer ces tests.

Exemple :
if (logger.isDebugEnabled()) {
  logger.debug("valeur="+valeur+" , i="+i+" ,next="+next);
}

Avec ce test, le message n'est construit que s'il est pris en compte par le logger. L'inconvénient de ce test est qu'il est réalisé deux fois : une fois par la méthode isDebugEnabled() et une autre fois par la méthode debug(). Cependant ce surcoût est beaucoup moins important que la création du message.

Les temps de traitement de Log4j sont obligatoirement dépendants de l'utilisation qui en est faite dans l'application notamment :

Lors de l'utilisation d'un layout de type PatternLayout, l'utilisation de certains motifs sont connus pour être gourmands en temps de traitement. Même si les informations de ces motifs sont particulièrement utiles, il faut tenir compte de leur temps de traitement lors de leur utilisation.

Pour économiser de la mémoire, il est préférable de déclarer les loggers en tant que variables statiques.

Exemple :
private static Logger LOGGER = Logger.getLogger(MaClasse.class);

 

19.2.8.3. D'autres recommandations

Lorsqu'une erreur doit être journalisée, il faut toujours utiliser la surcharge qui accepte l'exception pour permettre d'ajouter la stacktrace dans le journal.

LOGGER.error("Erreur dans les traitements", e);

Dans un contexte multithread, comme dans une webapp par exemple, il est important d'ajouter le nom du thread dans le pattern des messages en utilisant %t.

 

19.3. L'API logging

L'usage de fonctionnalités de logging dans les applications est tellement répendu que SUN a décidé de développer sa propre API et de l'intégrer au JDK à partir de la version 1.4.

Cette API a été proposée à la communauté sous la Java Specification Request numéro 047 (JSR-047).

Le but est de proposer un système qui puisse être exploité facilement par toutes les applications.

L'API repose sur plusieurs classes principales et une interface:

Un logger possède un ou plusieurs Handler qui sont des entités recevant les messages. Chaque Handler peut avoir un filtre associé en plus du filtre associé au Logger.

Chaque message possède un niveau de sévérité représenté par la classe Level.

 

19.3.1. La classe LogManager

La classe LogManager encapsule la configuration et les loggers de l'API de logging.

Cette classe est un singleton qui propose la méthode getLogManager() pour obtenir l'unique référence sur un objet de ce type.

Cet objet permet :

Pour réaliser ces actions, la classe LogManager possède plusieurs méthodes dont les principales sont :

Méthode Rôle
boolean addLogger(Logger) Ajouter un logger : cette méthode renvoie simplement false si le logger existe déjà avec le même nom, sinon elle ajoute le logger et renvoie true
Logger getLogger(String) Obtenir un logger à partir de son nom
Enumeration getLoggerName() Obtenir une énumération de tous les noms des logger
String getProperty(String) Obtenir la valeur d'une propriété de la configuration
void readConfiguration() Relire la configuration
LogManager getLogManager() Renvoyer l'instance unique de cette classe
void reset() Réinitialiser la configuration

La méthode getLogger() de la classe LogManager permet d'instancier un nouvel objet Logger si aucun Logger possédant le nom passé en paramètre n'a déjà été défini sinon elle renvoie l'instance existante.

Le LogManager conserve les instances de type Logger sous la forme de références faibles : il est donc possible qu'elle ne renvoie pas toujours la même instance pour un même nom.

Par défaut, la liste des Loggers contient toujours un Logger nommé global qui peut être facilement utilisé.

 

19.3.2. La classe Logger

La classe Logger est la classe qui se charge d'envoyer les messages dans la log. Un Logger est identifié par un nom qui est habituellement le nom qualifié de la classe dans laquelle le Logger est utilisé. Ce nom permet de gérer une hiérarchie de Logger. Cette gestion est assurée par le LogManager. Cette hiérarchie permet d'appliquer des modifications sur un Logger ainsi qu'à toute sa "descendance".

La méthode statique getLogger() de la classe Logger est un helper qui permet d'obtenir une instance de type Logger pour le nom fourni en paramètre.

Exemple :
package com.jmdoudoux.test.logging;

import java.util.logging.Logger;

public class TestLogging {

  private static Logger LOGGER = Logger.getLogger(TestLogging.class.getName());

  public static void main(String[] args) {
    LOGGER.info("Lancement de l'application");
  } 
}

Il est aussi possible de créer des Logger anonymes.

La méthode getLogger() est un délégué qui demande au LogManager de lui donner l'instance de type Logger correspondant au nom passé en paramètre.

La classe Logger se charge d'envoyer les messages aux Handler enregistrés sous la forme d'un objet de type LogRecord. Par défaut, ces Handlers sont ceux enregistrés dans le LogManager. L'envoi des messages est conditionné par la comparaison du niveau de sévérité du message avec celui associé au Logger.

La classe Logger possède de nombreuses méthodes pour émettre des messages, notamment :

Méthode Rôle
void addHandler(Handler handler) Ajouter un Handler qui va recevoir les messages émis par le Logger
void config(String msg) Emettre un message avec le niveau de gravité CONFIG
void entering(String sourceClass, String sourceMethod) Emettre un message pour l'entrée dans une méthode
void entering(String sourceClass, String sourceMethod, Object param1) Emettre un message pour l'entrée dans une méthode avec son premier paramètre
void entering(String sourceClass, String sourceMethod, Object[] params) Emettre un message pour l'entrée dans une méthode avec un tableau des paramètres passés à la méthode
void exiting(String sourceClass, String sourceMethod) Emettre un message de retour d'une méthode
void exiting(String sourceClass, String sourceMethod, Object result) Emettre un message de retour d'une méthode avec la valeur de retour
void fine(String msg) Emettre un message avec le niveau de gravité FINE
void finer(String msg) Emettre un message avec le niveau de gravité FINER
void finest(String msg) Emettre un message avec le niveau de gravité FINEST
static Logger getAnonymousLogger() Créer un Logger anonyme
static Logger getAnonymousLogger(String resourceBundleName) Créer un Logger anonyme
Filter getFilter() Obtenir le filtre associé au Logger
Handler[] getHandlers() Obtenir les Handlers associés au Logger
Level getLevel() Obtenir le niveau minimum associé au Logger
static Logger getLogger(String name) Obtenir une instance du Logger pour le nom fourni en paramètre
static Logger getLogger(String name, String resourceBundleName) Obtenir une instance du Logger pour le nom fourni en paramètre
String getName() Renvoyer le nom du Logger
Logger getParent() Renvoyer le Logger père
ResourceBundle getResourceBundle() Renvoyer le ResourceBundle associé au Logger pour la Locale courante
String getResourceBundleName() Renvoyer le nom du RessourceBundle associé au Logger
void info(String msg) Emettre un message avec le niveau de gravité INFO
boolean isLoggable(Level level) Vérifier si un message avec le niveau passé en paramètre sera pris en compte par le Logger ou non
void log(Level level, String msg) Emettre un message
void log(Level level, String msg, Object param1) Emettre un message avec un objet en paramètre
void log(Level level, String msg, Object[] params) Emettre un message avec un tableau d'objets en paramètre
void log(Level level, String msg, Throwable thrown) Emettre un message avec un objet de type Throwable
void log(LogRecord record) Emettre un message
void removeHandler(Handler handler) Retirer un Handler associé au Logger
void setFilter(Filter newFilter) Définir le filtre associé au Logger
void setLevel(Level newLevel) Définir le niveau minimum d'un message émis par le Logger
void setParent(Logger parent) Définir le Logger père
void severe(String msg) Emettre un message avec le niveau de gravité SEVERE
void warning(String msg) Emettre un message avec le niveau de gravité WARNING
void throwing(String sourceClass, String sourceMethod, Throwable thrown) Emettre un message avec le niveau de gravité WARNING

Plusieurs méthodes sont définies pour chaque niveau de sévérité. Plutôt que d'utiliser la méthode log() en précisant le niveau de sévérité, il est possible d'utiliser la méthode dont le nom correspondant au niveau de sévérité.

La méthode log() possède plusieurs surcharges : le framework tente de déterminer dynamiquement les noms de la classe et de la méthode.

La méthode logp() possède plusieurs surcharges qui permettent de fournir des informations précises sur l'origine de l'émission du message notamment le nom de la classe, le nom de la méthode et le message.

Les différentes surcharges de la méthode logrb() permettent en plus de préciser le nom d'un ResourceBundle à utiliser.

Les méthodes entering() et exiting() sont très utiles pour faciliter le débogage.

 

19.3.3. La classe Level

Chaque message est associé à un niveau de sévérité représenté par un objet de type Level. Cette classe définit 7 niveaux de sévérité :

La valeur Level.OFF, initialisée avec la valeur Integer.MAX_VALUE, permet de désactiver toutes les actions de logging de l'API. La valeur Level.ALL, initialisée avec la valeur Integer.MIN_VALUE, permet de logger tous les messages quelque soit leur niveau.

Il est possible de définir des niveaux personnalisés en créant des classes filles : ces nouveaux niveaux doivent impérativement avoir une valeur unique.

Exemple :
     LOGGER.setLevel(Level.INFO);

La classe Level propose plusieurs méthodes :

Méthode Rôle
String getName() Obtenir le nom du niveau
String getLocalizedName() Obtenir le nom du niveau dans la langue de la JVM
int intValue() Obtenir la valeur du niveau
Level parse(String) Obtenir le niveau à partir de son nom (méthode statique)

 

19.3.4. La classe LogRecord

La classe java.util.logging.LogRecord permet de passer des requêtes de logging à un Handler.

Elle possède un constructeur qui attend un objet de type Level et un message de type String et plusieurs méthodes qui sont des getters/setters pour des propriétés notamment :

Méthode Rôle
Level getLevel() Obtenir le niveau de sévérité du message
String getLoggerName() Obtenir le nom du Logger
String getMessage() Obtenir le message brut, avant le formatage et la localisation
long getMillis() Obtenir le timestamp (nombre de millisecondes depuis 1970)
Object[] getParameters() Obtenir les paramètres du message
ResourceBundle getResourceBundle() Obtenir le ResourceBundle
String getResourceBundleName() Obtenir le nom du ResourceBundle
long getSequenceNumber() Obtenir le numéro de séquence
String getSourceClassName() Obtenir le nom de la classe ayant émis le message
String getSourceMethodName() Obtenir le nom de la méthode à l'origine du message
int getThreadID() Obtenir l'identifiant du thread ayant émis le message
Throwable getThrown() Obtenir l'objet de type Throwable associé au message
void setLevel(Level level) Définir le niveau de sévérité du message
void setLoggerName(String name) Définir le nom du Logger
void setMessage(String message) Définir le message brut, avant le formatage et la localisation
void setMillis(long millis) Définir le timestamp
void setParameters(Object[] parameters) Définir les paramètres du message
void setResourceBundle(ResourceBundle bundle) Définir le ResourceBundle
void setResourceBundleName(String name) Définir le nom du ResourceBundle
void setSequenceNumber(long seq) Définir le numéro de séquence
void setSourceClassName(String sourceClassName) Définir le nom de la classe émettant le message
void setSourceMethodName(String sourceMethodName) Définir le nom de la méthode émettant le message
void setThreadID(int threadID) Définir l'identifiant du thread ayant émis le message
void setThrown(Throwable thrown) Définir l'objet de type Throwable associé au message

Les données contenues dans un objet de type LogRecord sont utilisées par les filtres et les formateurs lors de l'émission d'un message vers les handlers.

 

19.3.5. La classe Handler

Un Handler reçoit un message d'un logger et l'envoie vers une cible. Un logger peut être associé à plusieurs Handler.

La classe java.util.logging.Handler possède plusieurs méthodes :

MéthodeRôle
void close() Fermer le Handler et libérer les éventuelles ressources utilisées
void flush() Vider le tampon
String getEncoding() Renvoyer le jeu d'encodage de caractères utilisé par le Handler
ErrorManager getErrorManager() Renvoyer le gestionnaire d'erreurs associé au Handler
Filter getFilter() Renvoyer le filtre associé au Handler
Formatter getFormatter() Renvoyer le formateur associé au Handler
Level getLevel() Renvoyer le niveau minimum des messages traités par le Handler
boolean isLoggable(LogRecord record) Vérifier si le message peut être traité par le Handler
void setEncoding(String encoding) Définir le jeu d'encodage de caractères utilisé par le Handler
void setErrorManager(ErrorManager em) Définir le gestionnaire d'erreurs associé au Handler
void setFilter(Filter newFilter) Définir le filtre associé au Handler
void setFormatter(Formatter newFormatter) Définir le formateur associé au Handler
void setLevel(Level newLevel) Définir le niveau minimum d'un message pour être pris en compte par le Handler

Le framework propose plusieurs classes filles qui représentent différentes destinations pour émettre les messages :

Exemple :
package com.jmdoudoux.test.logging;

import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Logger;

public class TestLogging {

  private static Logger LOGGER = Logger.getLogger(TestLogging.class.getName());

  public static void main(String[] args) {
    Handler fh;
    try {
      fh = new FileHandler("TestLogging.log");
      LOGGER.addHandler(fh);
    } catch (SecurityException e) {
      LOGGER.severe("Impossible d'associer le FileHandler");
    } catch (IOException e) {
      LOGGER.severe("Impossible d'associer le FileHandler");
    }
    LOGGER.info("Lancement de l'application");
  }
}

Il est possible de désactiver un handler simplement en invoquant sa méthode setLevel() avec la valeur Level.OFF en paramètre. Pour le réactiver, il faut invoquer la méthode setLevel() avec le niveau désiré.

 

19.3.5.1. La classe FileHandler

La classe java.util.logging.FileHandler permet d'écrire des messages dans un fichier. Il est possible de définir une rotation sur plusieurs fichiers : dès que le fichier atteint une certaine taille, le fichier est fermé et un nouveau fichier est créé. Les fichiers précédents sont renommés en utilisant un suffixe numérique. Le formateur par défaut est une instance de type XMLFormatter.

La classe FileHandler possède plusieurs constructeurs :

Constructeur

Rôle

FileHandler()

 

FileHandler(String pattern)

Précise le motif du nom du fichier

FileHandler(String pattern, boolean append)

Précise le motif du nom du fichier et si le fichier existant doit être complété

FileHandler(String pattern, int limit, int count)

Précise le motif du nom du fichier, la taille maximale des fichiers et le nombre de fichiers à conserver lors de la rotation

FileHandler(String pattern, int limit, int count, boolean append)

Précise le motif du nom du fichier, la taille maximale des fichiers, le nombre de fichiers à conserver lors de la rotation et si le fichier existant doit être complété

Le fonctionnement d'un FileHandler peut être configuré en utilisant plusieurs propriétés :

Propriété

Rôle

java.util.logging.FileHandler.level

Définir le niveau de sévérité par défaut du Handler (Level.ALL par défaut)

java.util.logging.FileHandler.filter

Définir le nom de la classe de type Filter associée au Handler (aucun par défaut)

java.util.logging.FileHandler.formatter

Définir le nom de la classe de type  Formatter associée au Handler (java.util.logging.XMLFormatter par défaut)

java.util.logging.FileHandler.encoding

Définir le jeu d'encodage de caractères à utiliser (par défaut celui de la plate-forme)

java.util.logging.FileHandler.limit

Définir la taille maximale du fichier en octets. La valeur zéro précise qu'il n'y a pas de limite (0 par défaut)

java.util.logging.FileHandler.count

Définir le nombre maximum de fichiers lors des rotations (1 par défaut)

java.util.logging.FileHandler.pattern

Définir un motif pour le nom du fichier (la valeur par défaut est "%h/java%u.log")

java.util.logging.FileHandler.append

Définir si les messages doivent être ajoutés à un fichier existant avec la valeur true, avec la valeur false (par défaut), si le fichier doit être écrasé

La valeur par défaut d'une propriété est utilisée si aucune valeur n'est explicitement précisée ou si la valeur précisée est invalide.

Le motif pour le nom du fichier est une chaîne de caractères qui peut contenir une ou plusieurs séquences qui seront remplacées dynamiquement à l'exécution par leurs valeurs respectives.

Motif Rôle

/

Le séparateur de chemin du système

%t

Le répertoire temporaire du système

%h

La valeur de la propriété système user.home

%g

Le numéro généré pour chaque fichiers lors de la rotation des fichiers. Chaque fichier utilise un numéro dont le premier est 0

%u

Un nombre unique permettant de gérer les conflits. 0 par défaut, ce nombre est incrémenté tant que le fichier est utilisé par un processus jusqu'à ce que le fichier soit utilisable. Le fichier doit être stocké sur un disque local

%%

Le caractère %

Si le motif ne contient pas de %g et que le nombre de fichiers est supérieur à 1 alors un nombre unique sera ajouté à la fin du nom du fichier précédé d'un caractère point.

 

19.3.6. L'interface Filter

L'interface java.util.logging.Filter définit une seule méthode isLoggable(LogRecord) qui retourne un booléen. Cette méthode permet de déterminer si le message doit être loggué ou non. Si la méthode renvoie false alors l'objet de type LogRecord est ignoré.

Un objet de type Filter peut être associé à un Logger ou à un Handler. La méthode setFilter() de la classe Logger permet de lui associer un filtre.

Pour créer son propre filtre, il suffit de créer une classe qui implémente l'interface Filter.

 

19.3.7. La classe Formatter

La classe Formatter permet de formater un message. Une instance de type Formatter est associée à chaque Handler.

Le framework propose deux implémentations :

XMLFormatter utilise une DTD particulière. Le tag racine est <log>. Chaque enregistrement est inclus dans un tag <record>. Chaque tag <record> possède plusieurs tags fils :

Tag Rôle
Date Date et heure d'émission du message
Millis Timestamp de l'émission du message
Sequence  
Logger Nom du Logger utilisé pour émettre le message
Level Niveau de sévérité du message
Class Nom de la classe
Method Nom de la méthode
Thread Numéro du thread
Message Contenu du message

Il est possible de créer son propre formateur en créant une classe fille de la classe Formatter et en redéfinissant les méthodes format(), getHead() et getTail().

Chaque Handler est associé à une instance de type Formatter. La méthode setFormatter() de la classe Handler permet d'associer un autre formateur.

 

19.3.8. Le fichier de configuration

Un fichier particulier au format Properties permet de préciser des paramètres de configuration pour le système de log tels que le niveau de sévérité géré par un Logger particulier et sa descendance, les paramètres de configuration des Handlers, etc...

Il est possible de préciser le niveau de sévérité pris en compte par tous les Logger :

.level = niveau

Il est possible de définir les handlers par défaut :

handlers = java.util.logging.FileHandler

Pour préciser d'autres handlers, il faut les séparer par des virgules.

Pour définir le niveau de sévérité d'un Handler, il suffit de le préciser :

java.util.logging.FileHandler.level = niveau

Un fichier par défaut est défini avec les autres fichiers de configuration dans le répertoire lib du JRE. Ce fichier se nomme logging.properties.

Il est possible de préciser un fichier particulier précisant son nom dans la propriété système java.util.logging.config.file

exemple : java -Djava.util.logging.config.file=monLogging.properties

 

19.4. Jakarta Commons Logging (JCL)

Le projet Jakarta Commons propose un sous-projet nommé Logging qui encapsule l'usage de plusieurs systèmes de logging et facilite ainsi leur utilisation dans les applications. Ce n'est pas un autre système de log mais il propose un niveau d'abstraction qui permet sans changer le code d'utiliser indifféremment n'importe lequel des systèmes de logging supportés. Son utilisation est d'autant plus pratique qu'il existe plusieurs systèmes de log dont aucun des plus répandus, Log4j et l'API logging du JDK 1.4 n'est dominant.

Le grand intérêt de cette bibliothèque est donc de rendre l'utilisation d'un système de log dans le code indépendant de l'implémentation de ce système. JCL encapsule l'utilisation de Log4j, l'API logging du JDK 1.4 et LogKit.

De nombreux projets du groupe Jakarta tels que Tomcat ou Struts utilisent cette bibliothèque. La version de JCL utilisée dans cette section est le 1.0.3

Le package, contenu dans le fichier commons-logging-1.0.3.zip peut être téléchargé sur le site http://commons.apache.org/logging/. Il doit ensuite être décompressé dans un répertoire du système d'exploitation.

Pour utiliser la bibliothèque, il faut ajouter le fichier dans le classpath.

L'inconvénient d'utiliser cette bibliothèque est qu'elle n'utilise que le dénominateur commun des systèmes de log qu'elle supporte : ainsi certaines caractéristiques d'un système de log particulier ne pourront être utilisées avec cette API .

La bibliothèque propose une fabrique qui renvoie, en fonction du paramètre précisé, un objet qui implémente l'interface Log. La méthode statique getLog() de la classe LogFactory permet d'obtenir cet objet : elle attend en paramètre soit un nom sous la forme d'une chaîne de caractères soit un objet de type Class dont le nom sera utilisé. Si un objet de type log possédant ce nom existe déjà alors c'est cette instance qui est renvoyée par la méthode sinon c'est une nouvelle instance qui est retournée. Ce nom représente la catégorie pour le système log utilisé, si celui-ci supporte une telle fonctionnalité.

Par défaut, la méthode getLog() utilise les règles suivantes pour déterminer le système de log à utiliser :

Il est possible de forcer l'usage d'un système de log particulier en précisant la propriété org.apache.commons.logging.Log à la machine virtuelle.

Pour complètement désactiver le système de log, il suffit de fournir la valeur org.apache.commons.logging.impl.NoOpLog à la propriété org.apache.commons.logging.Log de la JVM. Attention dans ce cas, plus aucun log ne sera émis.

Il existe plusieurs niveaux de gravité que la bibliothèque tentera de faire correspondre au mieux avec le système de log utilisé.

 

19.5. D'autres API de logging

Il existe d'autres API de logging dont voici une liste non exhaustive :

Produit URL
Lumberjack http://javalogging.sourceforge.net/
Javalog http://sourceforge.net/projects/javalog/
Simple Logging Facade for Java (SLF4J) http://www.slf4j.org/
Logback http://logback.qos.ch/
Blitz4j https://github.com/Netflix/blitz4j

 

 


[ Précédent ] [ Sommaire ] [ Suivant ] [Télécharger ]      [Accueil ]

72 commentaires Donner une note à l'article (5)

 

Copyright (C) 1999-2016 Jean-Michel DOUDOUX. Vous pouvez copier, redistribuer et/ou modifier ce document selon les termes de la Licence de Documentation Libre GNU, Version 1.1 ou toute autre version ultérieure publiée par la Free Software Foundation; les Sections Invariantes étant constitués du chapitre Préambule, aucun Texte de Première de Couverture, et aucun Texte de Quatrième de Couverture. Une copie de la licence est incluse dans la section GNU FreeDocumentation Licence. La version la plus récente de cette licence est disponible à l'adresse : GNU Free Documentation Licence.

Responsables bénévoles de la rubrique Java : Mickael Baron - Robin56 -