Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to output logs to a JTextArea using Log4j2

I have been trying to output logs to a JTextArea for days and still no luck. Basically what I have tried is creating my own custom appender following existing appender like consoleAppender and tried to configure it in log4j2.xml. I feel I am heading the right direction but somehow I couldn't get it to work. I have asked in log4j2 user mailing list and no one seems to care to help me. Hopefully I can get my help here. If you know how to achieve it, please give me steps or even code snippets.

Thank you for your help in advanced.

Okay, since someone down voted my question as it doesn't show any effort then I'd better show some. I didn't show anything I have done was because I am not so sure what I was doing was the correct way and people might have their own approach.

the problems I am facing are,

  • I cannot find a way to pass that JTextArea object to my TextAreaAppender
  • When I tried to run the test classes, always getting an error saying TextAreaAppender CLASS_NOT_FOUND, but I have tried all the possible way I can find to specify the class attribute in log4j2.xml

codes are as following,

TextAreaAppender

public class TextAreaAppender extends AbstractOutputStreamAppender<OutputStreamManager>{
    private static TextAreaManagerFactory factory = new TextAreaManagerFactory();

    public enum Target {
        TEXTAREA
    }

    protected TextAreaAppender(String name, Layout<? extends Serializable> layout, Filter filter,
            OutputStreamManager manager, boolean ignoreExceptions) {
        super(name, layout, filter, ignoreExceptions, true, manager);
        // TODO Auto-generated constructor stub
    }

    @PluginFactory
    public static TextAreaAppender createAppender(
            @PluginElement("Layout") Layout<? extends Serializable> layout,
            @PluginElement("Filters") final Filter filter,
            @PluginAttribute("target") final String t,
            @PluginAttribute("name") final String name,
            @PluginAttribute("follow") final String follow,
            @PluginAttribute("ignoreExceptions") final String ignore) {
        if (name == null) {
            LOGGER.error("No name provided for TextAreaAppender");
            return null;
        }
        if (layout == null) {
            layout = PatternLayout.createLayout(null, null, null, null, null, null);
        }
        final boolean isFollow = Boolean.parseBoolean(follow);
        final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
        final Target target = t == null ? Target.TEXTAREA : Target.valueOf(t);
        return new TextAreaAppender(name, layout, filter, getManager(isFollow, target, layout), ignoreExceptions);
    }

    private static OutputStreamManager getManager(final boolean follow, final Target target, final Layout<? extends Serializable> layout) {
        final String type = target.name();
        //should change to getOutputStream(JTextArea), 
        //but not sure how I can pass textarea object to this class
        final OutputStream os = getOutputStream(follow, target);
        return OutputStreamManager.getManager(target.name() + "." + follow, new FactoryData(os, type, layout), factory);
    }

    private static OutputStream getOutputStream(JTextArea ta){ 
        return new TextAreaOutputStream(ta); 
    }
    private static class TextAreaOutputStream extends OutputStream {
        private final JTextArea output;
        public TextAreaOutputStream(JTextArea ta){
            this.output = ta; 
        }
        @Override
        public void write(int i) throws IOException{
            output.append(String.valueOf((char) i)); 
        }
    }

    /**
     * Data to pass to factory method.
     */
    private static class FactoryData {
        private final OutputStream os;
        private final String type;
        private final Layout<? extends Serializable> layout;

        /**
         * Constructor.
         * @param os The OutputStream.
         * @param type The name of the target.
         * @param layout A Serializable layout
         */
        public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) {
            this.os = os;
            this.type = type;
            this.layout = layout;
        }
    }
    /**
     * Factory to create the Appender.
     */
    private static class TextAreaManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {

        /**
         * Create an OutputStreamManager.
         * @param name The name of the entity to manage.
         * @param data The data required to create the entity.
         * @return The OutputStreamManager
         */
        @Override
        public OutputStreamManager createManager(final String name, final FactoryData data) {
            return new TextAreaOutputStreamManager(data.os, data.type, data.layout);// protected constructor???
        }
    }

    private static class TextAreaOutputStreamManager extends OutputStreamManager{

        public TextAreaOutputStreamManager(OutputStream os, String name,
                Layout<?> layout) {
            super(os, name, layout);
            // TODO Auto-generated constructor stub
        }
    }
}

UI test class

public class Log4j2Example {
    class LogModel extends AbstractTableModel{

        @Override
        public int getColumnCount() {
            // TODO Auto-generated method stub
            return 1;
        }

        @Override
        public int getRowCount() {
            // TODO Auto-generated method stub
            return 0;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            // TODO Auto-generated method stub
            switch(columnIndex){
                case 0: return null;
                default: return null;
            }
        }

    }
    private final static JTextArea textarea = new JTextArea();
    private final LogModel model = new LogModel();
    private final JTable table = new JTable(model);
    static Log4j2Example INSTANCE = new Log4j2Example();
    JFrame frame = new JFrame();

    void run(){
        frame.setLayout(new BorderLayout());
        table.setBorder(new TitledBorder("Table"));
        textarea.setBorder(new TitledBorder("Text Area"));
        textarea.setPreferredSize(new Dimension(100, 150));
        textarea.setEditable(false);

        frame.add(table);
        frame.add(textarea, BorderLayout.SOUTH);

        frame.setVisible(true);
        frame.setSize(400, 400);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    static final Logger logger = LogManager.getLogger(Log4j2Example.class.getName());
    public static void main(String[] args) {
        INSTANCE.run();
        System.out.println("test");
        logger.trace("Entering Log4j Example.");
        Hello hello = new Hello();
        if (!hello.callMe()) {
            logger.error("Ohh!Failed!");
        }
        logger.trace("Exiting Log4j Example.");


    }
}

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="CONSOLE" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
        <TextArea name="TextArea" class="testing.Log4j2Example.TextAreaAppender">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </TextArea>
    </Appenders>
    <Loggers>
        <Logger name="testing.Log4j2Example" level="ALL">
          <AppenderRef ref="TextArea"/>
        </Logger>

        <Root level="ERROR">
            <AppenderRef ref="CONSOLE"/>
        </Root>
    </Loggers>
</Configuration>
like image 356
ANG Avatar asked Jun 03 '14 01:06

ANG


3 Answers

try following in your log4j2.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" packages="testing.Log4j2Example">
    <Appenders>
        <Console name="CONSOLE" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
        <TextArea name="TextArea">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </TextArea>
    </Appenders>
    <Loggers>
        <Logger name="testing.Log4j2Example" level="ALL">
          <AppenderRef ref="TextArea"/>
        </Logger>
        <Root level="ERROR">
            <AppenderRef ref="CONSOLE"/>
        </Root>
    </Loggers>
</Configuration>

I am not sure, but I think following line is required in your TextAreaAppender class:

@Plugin(name = "TextArea", category = "Core", elementType = "appender", printObject = true)
public class TextAreaAppender extends AbstractOutputStreamAppender<OutputStreamManager>{
....
}


This setup was working for me :)

like image 74
Andy Avatar answered Sep 22 '22 12:09

Andy


The following approach worked for me, which I have based on the log4j solution presented here with additional general information on log4j2 appenders here.

JTextAreaAppender.java

import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;

import javax.swing.*;
import java.util.ArrayList;

import static javax.swing.SwingUtilities.invokeLater;
import static org.apache.logging.log4j.core.config.Property.EMPTY_ARRAY;
import static org.apache.logging.log4j.core.layout.PatternLayout.createDefaultLayout;

@Plugin(name = "JTextAreaAppender", category = "Core", elementType = "appender", printObject = true)
public class JTextAreaAppender extends AbstractAppender
{
    private static volatile ArrayList<JTextArea> textAreas = new ArrayList<>();

    private int maxLines;

    private JTextAreaAppender(String name, Layout<?> layout, Filter filter, int maxLines, boolean ignoreExceptions)
    {
        super(name, filter, layout, ignoreExceptions, EMPTY_ARRAY);
        this.maxLines = maxLines;
    }

    @SuppressWarnings("unused")
    @PluginFactory
    public static JTextAreaAppender createAppender(@PluginAttribute("name") String name,
                                                   @PluginAttribute("maxLines") int maxLines,
                                                   @PluginAttribute("ignoreExceptions") boolean ignoreExceptions,
                                                   @PluginElement("Layout") Layout<?> layout,
                                                   @PluginElement("Filters") Filter filter)
    {
        if (name == null)
        {
            LOGGER.error("No name provided for JTextAreaAppender");
            return null;
        }

        if (layout == null)
        {
            layout = createDefaultLayout();
        }
        return new JTextAreaAppender(name, layout, filter, maxLines, ignoreExceptions);
    }

    // Add the target JTextArea to be populated and updated by the logging information.
    public static void addLog4j2TextAreaAppender(final JTextArea textArea)
    {
        JTextAreaAppender.textAreas.add(textArea);
    }

    @Override
    public void append(LogEvent event)
    {
        String message = new String(this.getLayout().toByteArray(event));

        // Append formatted message to text area using the Thread.
        try
        {
            invokeLater(() ->
            {
                for (JTextArea textArea : textAreas)
                {
                    try
                    {
                        if (textArea != null)
                        {
                            if (textArea.getText().length() == 0)
                            {
                                textArea.setText(message);
                            } else
                            {
                                textArea.append("\n" + message);
                                if (maxLines > 0 & textArea.getLineCount() > maxLines + 1)
                                {
                                    int endIdx = textArea.getDocument().getText(0, textArea.getDocument().getLength()).indexOf("\n");
                                    textArea.getDocument().remove(0, endIdx + 1);
                                }
                            }
                            String content = textArea.getText();
                            textArea.setText(content.substring(0, content.length() - 1));
                        }
                    } catch (Throwable throwable)
                    {
                        throwable.printStackTrace();
                    }
                }
            });
        } catch (IllegalStateException exception)
        {
            exception.printStackTrace();
        }
    }
}

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" packages="">
    <Properties>
        <Property name="log-path">log</Property>
    </Properties>
    <Appenders>
        <Console name="console-log" target="SYSTEM_OUT">
            <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"/>
        </Console>
        <JTextAreaAppender name="jtextarea-log" maxLines="100">
            <PatternLayout>
                <pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss} %msg%n</pattern>
            </PatternLayout>
        </JTextAreaAppender>
    </Appenders>
    <Loggers>
        <Logger name="My Logger" level="debug" additivity="false">
            <appender-ref ref="console-log" level="debug"/>
            <appender-ref ref="jtextarea-log" level="debug"/>
        </Logger>
        <Root level="info" additivity="false">
            <AppenderRef ref="console-log"/>
        </Root>
    </Loggers>
</Configuration>

Add the following lines to your application code, where your GUI is defined, in this example added to the constructor of the class MyClass:

protected static Logger logger;

public MyClass() {
      // Setup logger
      logger = LogManager.getLogger("My Logger");

      ...

      // Create logging panel
      JTextArea jLoggingConsole = new JTextArea(5,0); // 5 lines high here
      jLoggingConsole.setLineWrap(true);
      jLoggingConsole.setWrapStyleWord(true);
      jLoggingConsole.setEditable (false);
      jLoggingConsole.setFont(new Font("Courier", Font.PLAIN, 12));

      // Make scrollable console pane
      JScrollPane jConsoleScroll = new JScrollPane(this.jLoggingConsole);
      jConsoleScroll.setVerticalScrollBarPolicy ( ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS );

      // Subscribe the text area to JTextAreaAppender
      JTextAreaAppender.addLog4j2TextAreaAppender(this.jLoggingConsole);

      ...
}
like image 31
vshiro Avatar answered Sep 19 '22 12:09

vshiro


You can output logs from Log4j to a temporary file or a memory buffer and tail this file into JTextArea. I suggest you refrain from logging directly into JTextArea. Logging is pretty low level and doing this would couple your user interface elements with business and database layers, if you have this layers that is.

like image 35
ali köksal Avatar answered Sep 21 '22 12:09

ali köksal