3 votes

Comment lier le journal de Logback dans une application Swing ?

Je dois ajouter un panneau à une application, ce panneau enregistrera les erreurs de l'application. J'ai créé une classe qui étend AppenderBase et j'ai configuré le fichier xml pour utiliser cette classe.

Donc, quand j'enregistre quelque chose dans l'application, l'appender est appelé.

Mais pour le moment, je ne sais pas comment relier mon appender à mon panneau.

Pouvez-vous me guider ?

11voto

rodgarcialima Points 301

Solution de travail :

enter image description here

Classe d'appender personnalisée :

package br.com.mobhub.fdv.sync.utils;

import br.com.mobhub.fdv.sync.App;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;

import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;

/**
 * @author Rodrigo Garcia Lima (email: rodgarcialima@gmail.com | github: rodgarcialima)
 * @see ch.qos.logback.core.AppenderBase
 */
public class Appender extends AppenderBase<ILoggingEvent> {

    /**
     * Utilizo para formatar a mensagem de log
     */
    private PatternLayout patternLayout;

    /**
     * Cada nível de log tem um estilo próprio
     */
    private static SimpleAttributeSet ERROR_ATT, WARN_ATT, INFO_ATT, DEBUG_ATT, TRACE_ATT, RESTO_ATT;

    /**
     * Definição dos estilos de log
     */
    static {
        // ERROR
        ERROR_ATT = new SimpleAttributeSet();
        ERROR_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.TRUE);
        ERROR_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.FALSE);
        ERROR_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(153, 0, 0));

        // WARN
        WARN_ATT = new SimpleAttributeSet();
        WARN_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.FALSE);
        WARN_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.FALSE);
        WARN_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(153, 76, 0));

        // INFO
        INFO_ATT = new SimpleAttributeSet();
        INFO_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.FALSE);
        INFO_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.FALSE);
        INFO_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(0, 0, 153));

        // DEBUG
        DEBUG_ATT = new SimpleAttributeSet();
        DEBUG_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.FALSE);
        DEBUG_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.TRUE);
        DEBUG_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(64, 64, 64));

        // TRACE
        TRACE_ATT = new SimpleAttributeSet();
        TRACE_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.FALSE);
        TRACE_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.TRUE);
        TRACE_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(153, 0, 76));

        // RESTO
        RESTO_ATT = new SimpleAttributeSet();
        RESTO_ATT.addAttribute(StyleConstants.CharacterConstants.Bold, Boolean.FALSE);
        RESTO_ATT.addAttribute(StyleConstants.CharacterConstants.Italic, Boolean.TRUE);
        RESTO_ATT.addAttribute(StyleConstants.CharacterConstants.Foreground, new Color(0, 0, 0));
    }

    @Override
    public void start() {
        patternLayout = new PatternLayout();
        patternLayout.setContext(getContext());
        patternLayout.setPattern("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n");
        patternLayout.start();

        super.start();
    }

    @Override
    protected void append(ILoggingEvent event) {
        // Formata mensagem do log
        String formattedMsg = patternLayout.doLayout(event);

        // Forma segura de atualizar o JTextpane
        SwingUtilities.invokeLater(() -> {
            // Alias para o JTextPane no frame da aplicação
            JTextPane textPane = App.MAIN_FORM.getTextPane();

            try {
                // Trunca linhas para economizar memória
                // Quando atingir 2000 linhas, eu quero que
                // apague as 500 primeiras linhas
                int limite = 1000;
                int apaga = 200;
                if (textPane.getDocument().getDefaultRootElement().getElementCount() > limite) {
                    int end = getLineEndOffset(textPane, apaga);
                    replaceRange(textPane, null, 0, end);
                }

                // Decide qual atributo (estilo) devo usar de acordo com o nível o log
                if (event.getLevel() == Level.ERROR)
                    textPane.getDocument().insertString(textPane.getDocument().getLength(), formattedMsg, ERROR_ATT);
                else if (event.getLevel() == Level.WARN)
                    textPane.getDocument().insertString(textPane.getDocument().getLength(), formattedMsg, WARN_ATT);
                else if (event.getLevel() == Level.INFO)
                    textPane.getDocument().insertString(textPane.getDocument().getLength(), formattedMsg, INFO_ATT);
                else if (event.getLevel() == Level.DEBUG)
                    textPane.getDocument().insertString(textPane.getDocument().getLength(), formattedMsg, DEBUG_ATT);
                else if (event.getLevel() == Level.TRACE)
                    textPane.getDocument().insertString(textPane.getDocument().getLength(), formattedMsg, TRACE_ATT);
                else
                    textPane.getDocument().insertString(textPane.getDocument().getLength(), formattedMsg, RESTO_ATT);

            } catch (BadLocationException e) {
                // Faz nada
            }

            // Vai para a última linha
            textPane.setCaretPosition(textPane.getDocument().getLength());
        });
    }

    /**
     * Código copiado do {@link JTextArea#getLineCount()}
     * @param textPane de onde quero as linhas contadas
     * @return quantidade de linhas &gt; 0
     */
    private int getLineCount(JTextPane textPane) {
        return textPane.getDocument().getDefaultRootElement().getElementCount();
    }

    /**
     * Código copiado do {@link JTextArea#getLineEndOffset(int)}
     * @param textPane de onde quero o offset
     * @param line the line &gt;= 0
     * @return the offset &gt;= 0
     * @throws BadLocationException Thrown if the line is
     * less than zero or greater or equal to the number of
     * lines contained in the document (as reported by
     * getLineCount)
     */
    private int getLineEndOffset(JTextPane textPane, int line) throws BadLocationException {
        int lineCount = getLineCount(textPane);
        if (line < 0) {
            throw new BadLocationException("Negative line", -1);
        } else if (line >= lineCount) {
            throw new BadLocationException("No such line", textPane.getDocument().getLength()+1);
        } else {
            Element map = textPane.getDocument().getDefaultRootElement();
            Element lineElem = map.getElement(line);
            int endOffset = lineElem.getEndOffset();
            // hide the implicit break at the end of the document
            return ((line == lineCount - 1) ? (endOffset - 1) : endOffset);
        }
    }

    /**
     * Código copiado do {@link JTextArea#replaceRange(String, int, int)}<br>
     *
     * Replaces text from the indicated start to end position with the
     * new text specified.  Does nothing if the model is null.  Simply
     * does a delete if the new string is null or empty.<br>
     *
     * @param textPane de onde quero substituir o texto
     * @param str the text to use as the replacement
     * @param start the start position &gt;= 0
     * @param end the end position &gt;= start
     * @exception IllegalArgumentException if part of the range is an invalid position in the model
     */
    private void replaceRange(JTextPane textPane, String str, int start, int end) throws IllegalArgumentException {
        if (end < start) {
            throw new IllegalArgumentException("end before start");
        }
        Document doc = textPane.getDocument();
        if (doc != null) {
            try {
                if (doc instanceof AbstractDocument) {
                    ((AbstractDocument)doc).replace(start, end - start, str, null);
                }
                else {
                    doc.remove(start, end - start);
                    doc.insertString(start, str, null);
                }
            } catch (BadLocationException e) {
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }
}

Exemple d'instance de JTextPane :

public class App {

    private static final Logger  logger = LoggerFactory.getLogger(App.class);
    public static final MainForm MAIN_FORM;

    static {
        // Look and Feel
        try {
            UIManager.setLookAndFeel(new NimbusLookAndFeel());
        } catch (UnsupportedLookAndFeelException e) {
            logger.error("Erro ao configurar NimbusLookAndFeel");
        }

        // Esse painel do form principal está sendo usando em outros lugares da aplicação
        MAIN_FORM = new MainForm();
    }

    public static void main(String[] args) {
        ...

        // Chama o form principal
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("Força de Vendas (Sync)");
            frame.setJMenuBar(criaMenus(frame));
            frame.setContentPane(MAIN_FORM.$$$getRootComponent$$$());
            frame.setResizable(true);
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });

        ...
    }
    ...
}

Et :

public class MainForm {

    private static final Logger logger = LoggerFactory.getLogger(MainForm.class);

    private JPanel contentPanel;
    private JButton iniciarButton;
    private JTextPane textPane;
    private JButton pararButton;
    private JButton limparLogButton;

    ...

    public JTextPane getTextPane() {
        return textPane;
    }

    ...

}

logback.xml :

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property file="config.properties" />

    <!-- Console -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- File -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logging.path}${logging.file}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logging.path}${logging.file}-%d{yyyy-MM-dd}</fileNamePattern>
            <maxHistory>${logging.maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
        </encoder>
    </appender>

    <!-- Form -->
    <appender name="FORM" class="br.com.mobhub.fdv.sync.utils.Appender" />

    <!--<logger name="br.com.mobhub.fdv.sync.App" level="DEBUG"/>-->

    <root level="${logging.level}">
        <appender-ref ref="FILE" />
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FORM" />
    </root>

</configuration>

3voto

MJM Points 1962

Pour cela, vous devez écrire votre propre Appender Pour cela, voir :logback.qos.ch/manual/appenders.html. Ensuite, vous devez utiliser un composant tel que JTextArea pour afficher les éléments suivants log dans celui-ci. Ensuite, vous devez écrire un Programmatic Configuration logback pour la relation entre votre custom Appender et votre Swing-Component . voir :logback.qos.ch/manuel/joran.html

0voto

yankee Points 6587

La solution de @Rod Lima est cool, mais le code est plus compliqué que nécessaire. Je l'ai porté en Kotlin et l'ai simplifié dans le processus :

import ch.qos.logback.classic.Level
import ch.qos.logback.classic.PatternLayout
import ch.qos.logback.classic.spi.ILoggingEvent
import ch.qos.logback.core.AppenderBase
import java.awt.Color
import javax.swing.JTextPane
import javax.swing.SwingUtilities
import javax.swing.text.SimpleAttributeSet
import javax.swing.text.StyleConstants

private fun createStyle(bold: Boolean = false, italic: Boolean = false, foreground: Color = Color.BLACK) =
    SimpleAttributeSet().apply {
        addAttribute(StyleConstants.CharacterConstants.Bold, bold)
        addAttribute(StyleConstants.CharacterConstants.Italic, italic)
        addAttribute(StyleConstants.CharacterConstants.Foreground, foreground)
    }

private val LOG_STYLES = mapOf(
    Level.ERROR to createStyle(bold = true, foreground = Color(153, 0, 0)),
    Level.WARN to createStyle(foreground = Color(153, 76, 0)),
    Level.INFO to createStyle(foreground = Color(0, 0, 153)),
    Level.DEBUG to createStyle(italic = true, foreground = Color(64, 64, 64)),
    Level.TRACE to createStyle(italic = true, foreground = Color(153, 0, 76))
)
private val LOG_STYLE_DEFAULT = createStyle(italic = true)

/**
 * Based on Appender by Rodrigo Garcia Lima (https://stackoverflow.com/a/33657637)
 */
class SwingLogger(private val lineLimit: Int = 2000) : AppenderBase<ILoggingEvent>() {
    override fun start() {
        patternLayout.context = context
        patternLayout.start()
        super.start()
    }

    override fun append(event: ILoggingEvent) {
        SwingUtilities.invokeLater {
            textPane.document.apply {
                val lineCountOverLimit = defaultRootElement.elementCount - lineLimit
                if (lineCountOverLimit > 0) {
                    remove(0, defaultRootElement.getElement(lineCountOverLimit).endOffset)
                }
            }
            textPane.document.insertString(
                textPane.document.length,
                patternLayout.doLayout(event),
                LOG_STYLES.getOrDefault(event.level, LOG_STYLE_DEFAULT)
            )
            textPane.caretPosition = textPane.document.length
        }
    }

    companion object {
        val textPane = JTextPane().apply { isEditable = false }
        private val patternLayout: PatternLayout = PatternLayout().apply {
            pattern = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
        }
    }
}

Pour l'utiliser, il suffit d'ajouter le JTextPane quelque part en utilisant la propriété static : add(JScrollPane(SwingLogger.textPane))

Bien entendu, vous devez également connecter l'appender à logback, par exemple dans logback.xml.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X