Tous les programmeurs Java connaissent bien le processus d'insertion des appels à System.out.println() dans un code qui pose des problèmes, pour se faire une idée du comportement du programme. Bien sûr, lorsque vous aurez découvert la cause du problème, les instructions d'affichage s'effacerons ; malgré tout, elles réapparaîtrons au prochain problème. L'API de consignation vise à surmonter cela, et comme son nom l'indique, elle propose de mettre en place plusieurs journaux (envoyés sur la console, dans un fichier, etc.) où l'ensemble des problèmes sont consignés suivant une certaine organisation.
Cette étude fait suite à la gestion d'exception qu'il faut bien maîtriser afin de bien comprendre le rôle de la consignation. Retour à la gestion d'exception.
§
Ses principaux avantages sont les suivants :
Commençons par le cas le plus simple. Le système de consignation gère un enregistreur par défaut, nommé java.util.logging.Logger.global, que vous pouvez utiliser à la place de System.out. Sa méthode info() permet de consigner un message informatif :
Logger.global.info("ATTENTION : Ce fichier -"+fichier.getName()+"- n'est pas une image");
12 août 2008 13:40:16 jwsphotos.PanneauImage setImage
INFO: ATTENTION : Ce fichier ( index.html ) n'est pas une image
Vous remarquez que l'heure et les noms de la classe et de la méthode d'appel apparaissent automatiquement.
§
Toutefois, si vous appelez la méthode setLevel() de l'objet Logger.global, avec la valeur Level.OFF passée en argument, à un endroit qui convient (par exemple au début de la méthode main()), l'ensemble de la consignation est supprimée.
Logger.global.setLevel(Level.OFF);
A titre d'exemple, je vous propose de mettre en place une application qui est une visionneuse des photos présentes sur la machine locale. Il est possible que la récupération de la photo se passe mal. Effectivement, l'utilisateur peut éventuellement choisir un fichier qui n'est pas une photo. Un journal est alors mis en oeuvre pour recenser les différents cas de disfonctionnement à l'administrateur de la machine locale.
package jwsphotos; import java.io.*; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import java.util.logging.*; import javax.imageio.ImageIO; import javax.swing.*; public class AfficherPhotos extends JFrame implements ActionListener { private JButton soumettre = new JButton("Récupérer la photo à afficher"); private PanneauImage panneau = new PanneauImage(); public AfficherPhotos() { setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(500, 400); setTitle("Afficher photos"); soumettre.addActionListener(this); add(soumettre, BorderLayout.NORTH); add(panneau); setVisible(true); } public static void main(String[] args) { // Logger.global.setLevel(Level.OFF); new AfficherPhotos(); } public void actionPerformed(ActionEvent e) { JFileChooser fichier = new JFileChooser(); fichier.setDialogTitle("Sélectionner la photo"); fichier.showOpenDialog(this); panneau.setImage(fichier.getSelectedFile()); panneau.repaint(); } } class PanneauImage extends JPanel { private BufferedImage image; private double ratio; public void setImage(File fichier) { try { image = ImageIO.read(fichier); ratio = (double)image.getWidth()/image.getHeight(); } catch (Exception ex) { Logger.global.info("ATTENTION : Ce fichier ( "+fichier.getName()+" ) n'est pas une image"); } } @Override protected void paintComponent(Graphics surface) { if (image!=null) surface.drawImage(image, 0, 0, this.getWidth(), (int)(this.getWidth()/ratio), null); } }
La consignation de base est maintenant considérée comme désuette. il est donc préférable de prévoir une consignation avancée que nous allons aborder maintenant.
Après cette introduction plus que sommaire, voyons à quoi ressemble la consignation pour les professionnels. Dans une application professionnelle, il est inutile d'enregistrer toutes les entrées d'un enregistreur global. Il vaut mieux définir quelques critères.
Lorsque vous appelez pour la première fois un enregistreur ayant un nom donné, celui-ci se crée au moyen de la méthode statique getLogger() de la classe Logger :
Logger enregistrement = Logger.getLogger("org.manu.visionneuse");
Les appels ultérieurs à ce nom permettrons de recenser les problèmes éventuels sur ce même enregistrement.
§
A l'instar des noms de paquetage, les noms d'enregistreurs affichent une structure hiérarchique. Ils sont en fait plus hiérarchiques que les paquetages. En effet, il n'existe aucune relation sémantique entre un paquetage et son parent ; or, les enfants et parents des enregistreurs (logger) partagent certaines propriétés.
Par exemple, si vous définissez le niveau de consignation sur l'enregistreur "org.manu", ses enfants en hériteront.
§
Il existe sept niveaux de consignation qui correspondent à des constantes prédéfinies de la classe Level :
enregistrement.setLevel(Level.FINE);
Vous pouvez également utiliser Level.ALL pour consigner tous les niveaux ou Level.OFF pour annuler toute consignation.
§
enregistrement.warning(message);
enregistrement.fine(message);
Mais, vous pouvez aussi utiliser la méthode log() de la classe Logger, en indiquant le niveau de consignation désiré :
enregistrement.log(Level.FINE, message);
Astuce : La configuration par défaut permet de consigner tous les enregistrements ayant le niveau INFO ou supérieur. Vous devez donc utiliser les niveaux CONFIG, FINE, FINER et FINEST pour déboguer les messages insignifiants pour l'utilisateur du programme et pourtant utiles au diagnostic.
Attention : Si vous réglez le niveau de consignation sur une valeur plus précise que INFO, modifiez également la configuration du gestionnaire de l'enregistreur. Ce gestionnaire par défaut supprime les messages inférieurs au niveau INFO. Consultez le chapitre suivant pour en savoir plus.
La consignation par défaut précise le nom de la classe et de la méthode qui contient l'appel de consignation, comme cela est suggéré par la pile d'appel. Malgré tout, si la machine virtuelle optimise l'exécution, aucune information précise ne sera disponible.
void logp(Level niveau, String classe, String méthode, String message)
void entering(String classe, String méthode)
void entering(String classe, String méthode, Object[ ] paramètres)
void exiting(String classe, String méthode)
void exiting(String classe, String méthode, Object résultat)
Ces appels génèrent des enregistrements de journaux du niveau FINER, qui commencent par les chaînes ENTRY et RETURN.
§
public void setImage(File fichier) { consignation.entering("PanneauImage", "setImage", new Object[] {fichier}); ... consignation.exiting("PanneauImage", "setImage"); }
16 août 2008 07:39:23 PanneauImage setImage
PLUS FIN: ENTRY C:\Photos\Papillon.jpg
16 août 2008 07:39:24 PanneauImage setImage
PLUS FIN: RETURN
consignation.entering("PanneauImage", "setImage", fichier);
void throwing(String classe, String méthode, Throwable exception)
void log(Level niveau, String message, Throwable exception)
if (...) { IOException exception = IOException(...); enregtistrement.throwing("PanneauImage", "setImage", exception); throw exception; }
L'appel de la méthode throwing() génèrent un enregistrement du niveau FINER et un message commencent par la chaîne THROW.
§
et :
try { ... } catch (Exception ex) { consignation.log(Level.WARNING,"Ce fichier ( "+fichier.getName()+" ) n'est pas une image", ex); }
public void setImage(File fichier) { consignation.entering("PanneauImage", "setImage", new Object[] {fichier}); try { image = ImageIO.read(fichier); ratio = (double)image.getWidth()/image.getHeight(); } catch (Exception ex) { consignation.log(Level.WARNING,"Ce fichier ( "+fichier.getName()+" ) n'est pas une image", ex); } consignation.exiting("PanneauImage", "setImage"); }
16 août 2008 08:01:37 PanneauImage setImage
PLUS FIN: ENTRY H:\COURS linux\l'accès aux services.pdf
16 août 2008 08:01:37 jwsphotos.AfficherPhotos$PanneauImage setImage ATTENTION: Ce fichier ( l'accès aux services.pdf ) n'est pas une image
java.lang.NullPointerExceptionat jwsphotos.AfficherPhotos$PanneauImage.setImage(AfficherPhotos.java:51)
at jwsphotos.AfficherPhotos.actionPerformed(AfficherPhotos.java:39)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1995)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2318)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:387)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:242)
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:236)
at java.awt.Component.processMouseEvent(Component.java:6041)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3265)
at java.awt.Component.processEvent(Component.java:5806)
at java.awt.Container.processEvent(Container.java:2058)
at java.awt.Component.dispatchEventImpl(Component.java:4413)
at java.awt.Container.dispatchEventImpl(Container.java:2116)
at java.awt.Component.dispatchEvent(Component.java:4243)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4322)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:3986)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:3916)
at java.awt.Container.dispatchEventImpl(Container.java:2102)
at java.awt.Window.dispatchEventImpl(Window.java:2440)
at java.awt.Component.dispatchEvent(Component.java:4243)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:273)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:183)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:173)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:168)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:160)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:121)
16 août 2008 08:01:38 PanneauImage setImage
PLUS FIN: RETURN
public void setImage(File fichier) { consignation.entering("PanneauImage", "setImage", fichier); try { image = ImageIO.read(fichier); ratio = (double)image.getWidth()/image.getHeight(); } catch (Exception ex) { consignation.warning("Ce fichier ( "+fichier.getName()+" ) n'est pas une image"); } consignation.exiting("PanneauImage", "setImage"); }
16 août 2008 08:01:37 PanneauImage setImageOu alors, nous pouvons aussi prendre une des méthodes log() qui prend en compte un type Object en paramètre plutôt que le type Throwable.
PLUS FIN: ENTRY H:\COURS linux\l'accès aux services.pdf
16 août 2008 08:01:37 jwsphotos.AfficherPhotos$PanneauImage setImage ATTENTION: Ce fichier ( l'accès aux services.pdf ) n'est pas une image
16 août 2008 08:01:38 PanneauImage setImage
PLUS FIN: RETURN
public void setImage(File fichier) { consignation.entering("PanneauImage", "setImage", fichier); try { image = ImageIO.read(fichier); ratio = (double)image.getWidth()/image.getHeight(); } catch (Exception ex) { consignation.log(Level.WARNING, "Ce fichier ({0}) n'est pas une image", fichier.getName()); } consignation.exiting("PanneauImage", "setImage"); }
16 août 2008 08:01:37 PanneauImage setImage
PLUS FIN: ENTRY H:\COURS linux\l'accès aux services.pdf
16 août 2008 08:01:37 jwsphotos.AfficherPhotos$PanneauImage setImage ATTENTION: Ce fichier ( l'accès aux services.pdf ) n'est pas une image
16 août 2008 08:01:38 PanneauImage setImage
PLUS FIN: RETURN
Attention, pour valider toutes ces fonctionnalités, je me suis servi de mon propre gestionnaire de consignation. Voir plus loin dans le chapitre correspondant.
Diverses propriétés du système de consignation peuvent être modifiées par un changement apporté au fichier de configuration. Le fichier par défaut est situé à :
{JRE}/lib/logging.properties
Pour utiliser un autre fichier, faites passer la propriété java.util.logging.config.file sur l'emplacement du fichier en lançant votre application avec :
java -Djava.util.config.file=FichierDeConfiguration ClassePrincipale
Attention : appeler System.setProperty("java.util.logging.config.file", fichier) dans la méthode main() de votre classe principale n'est d'aucun effet car le gestionnaire de journaux est initialisé au démarrage de la machine virtuelle, avant l'exécution de la méthode main().
.level=INFO
jwsphotos.level=FINER
Il s'agit ici d'ajouter le suffixe .level au nom de l'enregistreur.
§
Les paramètres de configuration du gestionnaire de journaux ne sont pas des propriétés système. Lancer un programme par -Djwsphotos.level=FINER n'a aucune influence sur l'enregistreur.
java.util.logging.ConsoleHandler.level=FINER
Par défaut, les enregistreurs envoient leurs enregistrements à un ConsoleHandler qui les affiche dans le flux System.err. Plus précisément, l'enregistreur envoie l'enregistrement au gestionnaire parent, et le dernier ancêtre (avec pour nom " ") possède un ConsoleHandler.
De même les gestionnaires disposent d'un niveau de consignation. Pour qu'un enregistrement soit consigné, son niveau de consignation doit être supérieur au seuil de l'enregistrement et à celui du gestionnaire.
Le fichier de configuration du gestionnaire de consignation définit le niveau de consignation du gestionnaire de console par défaut comme suit :
java.util.logging.ConsoleHandler.level = INFO
Vous pouvez aussi contourner le fichier de configuration et installer votre propre gestionnaire :
Logger consignation = Logger.getLogger("jwsphotos");
consignation.setLevel(Level.FINER);
consignation.setUseParentHandlers(false);
Handler console = new ConsoleHandler();
console.setLevel(Level.FINER);
consignation.addHandler(console);
A titre d'exemple, je vous propose de reprendre la visionneuse des photos.
package jwsphotos; import java.io.*; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import java.util.logging.*; import javax.imageio.ImageIO; import javax.swing.*; public class AfficherPhotos extends JFrame implements ActionListener { private JButton soumettre = new JButton("Récupérer la photo à afficher"); private PanneauImage panneau = new PanneauImage(); private Logger consignation = Logger.getLogger("jwsphotos"); private Handler console = new ConsoleHandler(); public AfficherPhotos() { setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(500, 400); setTitle("Afficher photos"); soumettre.addActionListener(this); add(soumettre, BorderLayout.NORTH); add(panneau); setVisible(true); consignation.setLevel(Level.FINER); consignation.setUseParentHandlers(false); console.setLevel(Level.FINER); consignation.addHandler(console); } public static void main(String[] args) { new AfficherPhotos(); } public void actionPerformed(ActionEvent e) { JFileChooser fichier = new JFileChooser(); fichier.setDialogTitle("Sélectionner la photo"); fichier.showOpenDialog(this); panneau.setImage(fichier.getSelectedFile()); panneau.repaint(); } class PanneauImage extends JPanel { private BufferedImage image; private double ratio; public void setImage(File fichier) { consignation.entering("PanneauImage", "setImage", new Object[] {fichier}); try { image = ImageIO.read(fichier); ratio = (double)image.getWidth()/image.getHeight(); } catch (Exception ex) { consignation.warning("Ce fichier ( "+fichier.getName()+" ) n'est pas une image"); } consignation.exiting("PanneauImage", "setImage"); } @Override protected void paintComponent(Graphics surface) { if (image!=null) surface.drawImage(image, 0, 0, this.getWidth(), (int)(this.getWidth()/ratio), null); } } }
Pour envoyer des enregistrements de journal partout ailleurs, ajoutez un autre gestionnaire. L'API de consignation fournit deux gestionnaires qui se montreront fort utiles à cet effet :
Vous pouvez envoyer les enregistrements vers un gestionnaire de fichiers par défaut, comme ceci :
Logger consignation = Logger.getLogger("jwsphotos");
consignation.setLevel(Level.FINER);
consignation.setUseParentHandlers(false);
FileHandler journal = new FileHandler();
journal.setLevel(Level.FINER);
consignation.addHandler(journal);
Les enregistrements sont envoyés vers un fichier javan.log dans le répertoire de base de l'utilisateur, où n est remplacé par un nombre pour rendre le fichier unique.
Lorsqu'un système ne connaît pas le répertoire de base de l'utilisateur (par exemple sous Windows 95/98/Me), le fichier est stocké dans un emplacement par défaut comme C:\Windows.
Par défaut, les enregistrements sont mis en forme suivant une structure XML.
§
A titre d'exemple, je vous propose de reprendre la visionneuse des photos en enregistrant cette fois-ci les consignations dans un fichier journal :
package jwsphotos; import java.io.*; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import java.util.logging.*; import javax.imageio.ImageIO; import javax.swing.*; public class AfficherPhotos extends JFrame implements ActionListener { private JButton soumettre = new JButton("Récupérer la photo à afficher"); private PanneauImage panneau = new PanneauImage(); private Logger consignation = Logger.getLogger("jwsphotos"); public AfficherPhotos() { setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(500, 400); setTitle("Afficher photos"); soumettre.addActionListener(this); add(soumettre, BorderLayout.NORTH); add(panneau); setVisible(true); consignation.setLevel(Level.FINER); consignation.setUseParentHandlers(false); FileHandler journal = new FileHandler(); // journal.setLevel(Level.FINER); consignation.addHandler(journal); } ... }
Propriété de configuration | Description | Paramètre par défaut |
---|---|---|
java.util.logging.FileHandler.level | Le niveau du gestionnaire. | Level.ALL |
java.util.logging.FileHandler.append | Contrôle si le gestionnaire doit continuer un fichier existant ou ouvrir un nouveau fichier pour chaque programme. | false |
java.util.logging.FileHandler.limit | Le nombre maximal approximatif d'octets à écrire dans un fichier avant d'en ouvrir un autre (0 = pas de limite). | 0 (pas de limite) dans la classe FileHandler, 50 000 dans la configuration par défaut du gestionnaire de journaux. |
java.util.logging.FileHandler.pattern | Le modèle de nom pour le fichier journal. Voir le tableau suivant pour connaître ses variables. | %h/java%u.log |
java.util.logging.FileHandler.count | Le nombre de journaux dans une suite. | 1 (pas de rotation) |
java.util.logging.FileHandler.filter | La classe du filtre à utiliser. | Pas de filtrage |
java.util.logging.FileHandler.encoding | Le codage de caractères à utiliser. | Le codage de la plate-forme. |
java.util.logging.FileHandler.formatter | Le formatteur des enregistrements. | java.util.logging.XMLFormatter |
Variable | Description |
---|---|
%h | La valeur de la propriété système user.home |
%t | Le répertoire temporaire du système |
%u | Un numéro unique pour éviter les conflits |
%g | Le numéro pour les journaux par rotation (le suffixe .%g indique que la rotation est spécifiée, le modèle ne contient pas %g) |
%% | Le caractère % |
Astuce : De nombreux programmeurs utilisent la consignation pour aider le personnel du support technique. En cas de dérèglement d'un programme au cours d'une session, l'utilisateur peut renvoyer les fichiers journaux pour analyse. Dans ce cas, il convient d'activer la balise "append", d'utiliser la rotation des fichiers, voire de faire les deux.
A titre d'exemple, je vous propose simplement de proposer le nom du fichier journal jwsphotos.0.journal, avec un format de texte classique sans les balises XML :
... public class AfficherPhotos extends JFrame implements ActionListener { private JButton soumettre = new JButton("Récupérer la photo à afficher"); private PanneauImage panneau = new PanneauImage(); private Logger consignation = Logger.getLogger("jwsphotos"); public AfficherPhotos() { setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(500, 400); setTitle("Afficher photos"); soumettre.addActionListener(this); add(soumettre, BorderLayout.NORTH); add(panneau); setVisible(true); consignation.setLevel(Level.FINER); consignation.setUseParentHandlers(false); FileHandler journal = new FileHandler("%h/jwsphotos.%u.journal"); journal.setFormatter(new SimpleFormatter()); consignation.addHandler(journal); } ... }
Par défaut, les enregistrements sont filtrés en fonction de leurs niveaux de consignation. Chaque enregistreur et chaque gestionnaire peuvent avoir un filtre optionnel qui réalisera un filtrage complémentaire.
interface Filter { boolean isLoggable(LogRecord enregistrement); }
Dans cette méthode isLoggable(), analysez l'enregistrement du journal à l'aide des critères souhaités et renvoyez true pour ceux que vous voulez inclure dans le journal.
Par exemple, un filtre particulier peut n'être intéressé que par les messages générés par les méthode entering() et exiting(). Le filtre doit alors appeler la méthode getMessage() de la classe LogRecord et vérifier s'il commence par ENTRY ou RETURN.
Pour installer un filtre dans un enregistreur ou un gestionnaire, appelez simplement la méthode setFilter(). Sachez que vous pouvez installer plusieurs filtres à la fois.
package jwsphotos; import java.io.*; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import java.util.logging.*; import javax.imageio.ImageIO; import javax.swing.*; public class AfficherPhotos extends JFrame implements ActionListener { private JButton soumettre = new JButton("Récupérer la photo à afficher"); private PanneauImage panneau = new PanneauImage(); private Logger consignation = Logger.getLogger("jwsphotos"); public AfficherPhotos() { setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(500, 400); setTitle("Afficher photos"); soumettre.addActionListener(this); add(soumettre, BorderLayout.NORTH); add(panneau); setVisible(true); consignation.setLevel(Level.FINER); consignation.setUseParentHandlers(false); Handler journal = new ConsoleHandler(); journal.setLevel(Level.FINER); consignation.setFilter(panneau); consignation.addHandler(journal); } public static void main(String[] args) { new AfficherPhotos(); } public void actionPerformed(ActionEvent e) { JFileChooser fichier = new JFileChooser(); fichier.setDialogTitle("Sélectionner la photo"); fichier.showOpenDialog(this); panneau.setImage(fichier.getSelectedFile()); panneau.repaint(); } class PanneauImage extends JPanel implements Filter { private BufferedImage image; private double ratio; public void setImage(File fichier) { consignation.entering("PanneauImage", "setImage", fichier); try { image = ImageIO.read(fichier); ratio = (double)image.getWidth()/image.getHeight(); } catch (Exception ex) { consignation.log(Level.WARNING, "Ce fichier ({0}) n'est pas une image", fichier.getName()); } consignation.exiting("PanneauImage", "setImage"); } @Override protected void paintComponent(Graphics surface) { if (image!=null) surface.drawImage(image, 0, 0, this.getWidth(), (int)(this.getWidth()/ratio), null); } public boolean isLoggable(LogRecord enregistrement) { String message = enregistrement.getMessage(); return message.startsWith("ENTRY") || message.startsWith("RETURN"); } } }
Les classes ConsoleHandler et FileHandler délivrent des enregistrements des journaux respectivement aux formats texte et XML. Mais vous pouvez aussi définir vos propres formats.
String format(LogRecord enregistrement)
String format(LogRecord enregistrement)
String getHead(Handler h)
String getTail(Handler h)
A noter qu'il existe des classes prédéfinies SimpleFormatter et XMLFormatter qui mettent en place des formats tout prêts, respectivement le format texte et le format XML. Ces classes sont d'ailleurs utilisées par les classes ConsoleHandler et FileHandler. Ainsi, si vous désirez obtenir un format standard sous forme de texte simple sans balises XML dans un fichier de consignation, il suffit d'exécuter la ligne suivante :
journal.setFormatter(new SimpleFormatter())
Je vous invite à reprendre l'application précédente dans laquelle je propose de fabriquer un nouveau formatteur de messages de consignation. Voici par exemple ce que nous pouvons obtenir sur la console lorsque le choix du fichier à visualiser n'est pas une image :
<22 août 2008 - 15:48:34> Entrée dans la méthode PanneauImage.setImage( )
<22 août 2008 - 15:48:34> ATTENTION : Ce fichier (index.html) nest pas une image
<22 août 2008 - 15:48:34> Sortie de la méthode PanneauImage.setImage( )
package jwsphotos; import java.io.*; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import java.text.*; import java.util.Date; import java.util.logging.*; import javax.imageio.ImageIO; import javax.swing.*; public class AfficherPhotos extends JFrame implements ActionListener { private JButton soumettre = new JButton("Récupérer la photo à afficher"); private PanneauImage panneau = new PanneauImage(); private Logger consignation = Logger.getLogger("jwsphotos"); public AfficherPhotos() { setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(500, 400); setTitle("Afficher photos"); soumettre.addActionListener(this); add(soumettre, BorderLayout.NORTH); add(panneau); setVisible(true); consignation.setLevel(Level.FINER); consignation.setUseParentHandlers(false); Handler journal = new ConsoleHandler(); journal.setLevel(Level.FINER); journal.setFormatter(new Format()); consignation.addHandler(journal); } public static void main(String[] args) { new AfficherPhotos(); } public void actionPerformed(ActionEvent e) { JFileChooser fichier = new JFileChooser(); fichier.setDialogTitle("Sélectionner la photo"); fichier.showOpenDialog(this); panneau.setImage(fichier.getSelectedFile()); panneau.repaint(); } class PanneauImage extends JPanel { private BufferedImage image; private double ratio; public void setImage(File fichier) { consignation.entering("PanneauImage", "setImage", fichier); try { image = ImageIO.read(fichier); ratio = (double)image.getWidth()/image.getHeight(); } catch (Exception ex) { consignation.log(Level.WARNING, "Ce fichier ({0}) n'est pas une image", fichier.getName()); } consignation.exiting("PanneauImage", "setImage"); } @Override protected void paintComponent(Graphics surface) { if (image!=null) surface.drawImage(image, 0, 0, this.getWidth(), (int)(this.getWidth()/ratio), null); } } class Format extends Formatter { @Override public String format(LogRecord enregistrement) { String date = MessageFormat.format("<{0, date, long} - {0, time, medium}> ", new Date(enregistrement.getMillis())); if (enregistrement.getLevel() == Level.WARNING) { return date+"ATTENTION : "+formatMessage(enregistrement)+ ; } else { String type = ""; if (enregistrement.getMessage().startsWith("ENTRY")) type = "Entrée dans la méthode "; else if (enregistrement.getMessage().startsWith("RETURN")) type = "Sortie de la méthode "; String classe = enregistrement.getSourceClassName(); String méthode = enregistrement.getSourceMethodName(); return date+type+classe+ +méthode+ ; } } } }