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,
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>
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 :)
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);
...
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With