views:

302

answers:

1

I have a web page, used for admin purposes, which runs a task (image fetching from a remote site).
In order to be able to debug the task using the browser only, no ssh etc, I'd like to be able to read all log output from the executing thread and spit it out to the web page.
The task boils down to:

  1. Changing log level for current thread at the beginning of the call and restore when the call is done.
  2. Reading all log output by current thread and storing it in a string.

So in pseudocode my execute() method would look like this: (I'm using struts2)

public String execute() throws Exception {
  turnLoggingLevelToDebugOnlyForThisThread()
  ... do stuff...
  restoreLoggingLevelForThisThread()
  String logs = readAllLogsByThisThread();
}

Can this be done with log4j?

I'm using tomcat, struts2, log4j and slf4j.

EDIT 1: I should note that the motivation is to be able to see the existing logs on a web page without needing to add new log lines in code. Think of a nice web debug interface that lets you run your operation and the result spits out the logs of the operation.
EDIT 2: I should also note that I'm already using log4j (via slf4j) and a log4j.xml, so the solution I'm seeking needs to live aside the currently logging system, not ruin it.

+3  A: 

You can create a log4j appender to write to a StringWriter. Here is an example I did some time ago:

consoleWriter = new StringWriter();
WriterAppender appender = new WriterAppender(new PatternLayout("%d{ISO8601} %p - %m%n"),consoleWriter);
appender.setName("CONSOLE_APPENDER");
appender.setThreshold(org.apache.log4j.Level.ERROR);
Logger.getRootLogger().addAppender(appender);

It writes all ERROR logs to the consoleWriter, but you can set the scope or the level of the logs as you wish. The scope (logger name) should be a unique identifier of the thread. something like this:

Logger.getLogger("Thread-00001").addAppender(appender);

Your thread should write to this logger.

Logger.getLogger("Thread-00001").info("blah blah blah");

And when you want to finish logging the thread:

Logger.getLogger("Thread-00001").removeAppender("CONSOLE_APPENDER");

UPDATE: Here is a working example. Writes error logs to a file (set in log4j.xml) + writes all thread logs to a StringWriter, when enabled:

import java.io.StringWriter;
import org.apache.log4j.Logger;
import org.apache.log4j.Level;
import org.apache.log4j.WriterAppender;
import org.apache.log4j.PatternLayout;

public class Log4jTest implements Runnable {

    public static final String CONSOLE_APPENDER = "CONSOLE_APPENDER";
    private static WriterAppender appender = null;
    private static int counter = 1;

    public static synchronized String getNextId() {
        return "Thread_00"+(counter++);
    }

    public void run() {
        String id="UNKNOWN";
        try {
            id = getNextId();
            Logger log = Logger.getLogger(id);
            log.addAppender(appender);
            log.setLevel(Level.DEBUG);
            log.info(id+" log message 1");
            log.removeAppender(CONSOLE_APPENDER);
            log.info(id+" log message 2");
            log.error(id+" log message 3");
        } catch (Exception e) {
            System.out.println("Error in "+id);
            e.printStackTrace();
        }
    }

    public static void main(String [] args) {
        try {
            StringWriter consoleWriter = new StringWriter();
            appender = new WriterAppender(new PatternLayout("%d{ISO8601} %p - %m%n"),consoleWriter);
            appender.setName(CONSOLE_APPENDER);
            appender.setThreshold(org.apache.log4j.Level.DEBUG);

            for (int i=0; i<5; i++) {
                Thread t = new Thread(new Log4jTest());
                t.start();
            }

            Thread.sleep(200);
            System.out.println(consoleWriter.getBuffer().toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

And here is my log4j.xml:

<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration>
  <appender name="FILE" class="org.apache.log4j.RollingFileAppender">
    <param name="File" value="./Log4jTest.log" />
    <param name="MaxFileSize" value="1000KB" />
    <param name="MaxBackupIndex" value="5" />
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d{ISO8601} %p - %m%n" />
    </layout>
    <filter class="org.apache.log4j.varia.LevelRangeFilter">
      <param name="LevelMin" value="WARN" />
      <param name="LevelMax" value="FATAL" />
    </filter>
  </appender>
  <root>
    <level value="ERROR" />
    <appender-ref ref="FILE" />
  </root>
</log4j:configuration>
Miklos
I realize this more or less the direction it should work, but unfortunately I couldn't make it work for me...If I have a log4j.xml file it doesn't work. If I don't have log4j.xml it does... I suppose adding the appender doesn't work as I think it does :(
Ran
@Ran probably your root logger (a.k.a. category) is set to ERROR level. You need to override this with a logger.setLevel() command, or by creating a parent logger configuration in log4j.xml with a lower log level. Look at "Logger hierarchy" in the Log4J manual. I've updated my response with a working example, with log4j.xml. I hope it helps.
Miklos
Thanks I got this to work with sjf4j as well now, cool
Ran