简体   繁体   中英

Redirect System.out.println to log

In my project test suite there is big usage of

System.out.println 

I'm trying to redirect these output to log file (through configuration or from single point without refactoring whole project ) so that can be disabled when necessary to improve performance. I'm using log4j for logging. Does any one know is this possible? if so how to do it? Thanks in advance.

Given that it's better replace the System.out.println() , sometimes we have no choice. Anyway I've made a little utility for that:

SystemOutToSlf4j.enableForClass(MyClass.class)

Then all the println originated from MyClass will be redirected to the logger. See this post for more details...

public class SystemOutToSlf4j extends PrintStream {

  private static final PrintStream originalSystemOut = System.out;
  private static SystemOutToSlf4j systemOutToLogger;

  /**
   * Enable forwarding System.out.println calls to the logger if the stacktrace contains the class parameter
   * @param clazz
   */
  public static void enableForClass(Class clazz) {
    systemOutToLogger = new SystemOutToSlf4j(originalSystemOut, clazz.getName());
    System.setOut(systemOutToLogger);
  }


  /**
   * Enable forwarding System.out.println calls to the logger if the stacktrace contains the package parameter
   * @param packageToLog
   */
  public static void enableForPackage(String packageToLog) {
    systemOutToLogger = new SystemOutToSlf4j(originalSystemOut, packageToLog);
    System.setOut(systemOutToLogger);
  }

  /**
   * Disable forwarding to the logger resetting the standard output to the console
   */
  public static void disable() {
    System.setOut(originalSystemOut);
    systemOutToLogger = null;
  }

  private String packageOrClassToLog;

  private SystemOutToSlf4j(PrintStream original, String packageOrClassToLog) {
    super(original);
    this.packageOrClassToLog = packageOrClassToLog;
  }

  @Override
  public void println(String line) {
    StackTraceElement[] stack = Thread.currentThread().getStackTrace();
    StackTraceElement caller = findCallerToLog(stack);
    if (caller == null) {
      super.println(line);
      return;
    }

    org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(caller.getClass());
    log.info(line);
  }

  public StackTraceElement findCallerToLog(StackTraceElement[] stack) {
    for (StackTraceElement element : stack) {
      if (element.getClassName().startsWith(packageOrClassToLog))
        return element;
    }

    return null;
  }

}

My suggestion would be to refactor if possible. For a possible solution, check these similar questions

log4j redirect stdout to DailyRollingFileAppender

Redirect System.out.println to Log4J, while keeping class name information

I think you can use System.setOut(PrintStream) to set your output to a file output stream. Then you can put this line in your BeforeClass method. I like to use a BaseTest class and put this line of code in the beforeclass method of that class. Then make all test cases extend this cclass.

Use shell redirection. Figure out the "java" invocation for your project, if you're on most vaguely UNIX-like systems, ps aux | grep java will help.

Then just run this command with > /path/to/logfile. Example:

java -jar myjar.jar -cp path/to/lib.jar:path/to/otherlib.jar com.bigcorp.myproject.Main > /var/log/myproject.log
public class RecursiveLogging {
public static void main(String[] args) {
    System.setOut(new PrintStream(new CustomOutputStream()));

    TestMyException.testPrint();
}

}

class CustomOutputStream extends OutputStream {
private Logger logger = Logger.getLogger(this.getClass());

@Override
public final void write(int b) throws IOException {
    // the correct way of doing this would be using a buffer
    // to store characters until a newline is encountered,
    // this implementation is for illustration only
    logger.info((char) b);
}

@Override
public void write(byte[] b, int off, int len) throws IOException {
    if (b == null) {
        throw new NullPointerException();
    } else if ((off < 0) || (off > b.length) || (len < 0) ||
               ((off + len) > b.length) || ((off + len) < 0)) {
        throw new IndexOutOfBoundsException();
    } else if (len == 0) {
        return;
    }
    byte[] pb = new byte[len];
    for (int i = 0 ; i < len ; i++) {
        pb[i] = (b[off + i]);
    }
    String str = new String(pb);
    logger.info(str);
}
}

My solution is pretty simple and supports all PrintStream functionality without overloading everything. overloading only flush() as it called by PrintStream methods every new line.

public class ConsoleToLogger
{
  private Logger log;
  private PrintStream originalStream;
  private Level logLevel;
  private ByteArrayBufferOutStream buffer;
  private PrintStream bufferPrintStream;

  ConsoleToLogger(PrintStream realPrintStream, Level pLogLevel)
  {
    buffer = new ByteArrayBufferOutStream();
    bufferPrintStream = new PrintStream(buffer);
    originalStream = realPrintStream;
    logLevel = pLogLevel;
    log = Logger.getLogger(Level.ERROR.equals(pLogLevel) ? "STDERR" : "STDOUT");
  }

  public PrintStream getPrintStream()
  {
    return bufferPrintStream;
  }

  private class ByteArrayBufferOutStream
    extends ByteArrayOutputStream
  {
    @Override
    public void flush()
      throws IOException
    {
      super.flush();
      String message = buffer.toString();
      originalStream.println(message);
      log.log(logLevel, message);
      buffer.reset();
    }
  }
}


// assign to System.out and system.err
System.setOut(new ConsoleToLogger(System.out, Level.INFO).getPrintStream());
System.setErr(new ConsoleToLogger(System.err, Level.ERROR).getPrintStream());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM