14

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.

Viraj
  • 5,083
  • 6
  • 35
  • 76
  • 1
    this page explains it very detailled: https://examples.javacodegeeks.com/core-java/lang/system/out/logging-system-println-results-log-file-example/ – Aydin K. Jan 24 '18 at 15:36
  • Don't bodge by redirecting system out, instead bite the bullet and use a decent IDE and refactor the whole code base in one go to replace that with log.info(...) instead. – Martin of Hessle Mar 08 '19 at 14:16

6 Answers6

8

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;
  }

}
Fabio Bonfante
  • 5,128
  • 1
  • 32
  • 37
  • in method `println(String)` the call `getLogger(caller.getClass())` should be replaced with `getLogger(caller.getClassName())`, otherwise the logging framework configured for SLF4J will print the class name of `StackTraceElement`. – Lolo Mar 18 '18 at 06:41
3

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

Community
  • 1
  • 1
rajesh
  • 3,247
  • 5
  • 31
  • 56
3

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.

nickgrim
  • 5,387
  • 1
  • 22
  • 28
pravat
  • 41
  • 1
1

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
picomancer
  • 1,786
  • 13
  • 15
0
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);
}
}
mishik
  • 9,973
  • 9
  • 45
  • 67
0

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());

gromio
  • 1
  • 1