重定向 System.out.println

35

我的应用程序中有许多System.out.println()语句。

我想捕获来自println的消息并将它们发送到标准记录器(Log4j,JUL等)。

如何做到这一点?


可能是Removing access to System.out in java的重复问题。 - Robert Munteanu
虽然在可能重复的答案中有交叉,但我认为它不够接近以配对。更多的是询问如何完全阻止输出 - 而这个问题则是为了在其他地方捕获输出。 - paxdiablo
6个回答

41

System类有一个setOutsetErr可以用来改变输出流,例如使用支持File的新PrintStream或者使用你选择的日志子系统的另一个流。


请记住,如果您将日志库配置为输出到标准输出或错误(可能是无限递归类型),则可能会遇到麻烦。

如果是这种情况,您可能需要将System.out.print-类型语句替换为实际的日志调用。


为了使您的解决方案完整,我建议您添加说明,以便在拦截所需内容后如何恢复到正常的标准输出,即System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out))); - Vic Seedoubleyew
@VicSeedoubleyew,将原始的PrintStream存储起来并在之后恢复它不是更好吗?如果还有其他东西也重定向了System.out,这样做不一定能正确地恢复它。 - neuralmer

36

我曾经也有类似的需求。我需要拦截一些第三方组件的输出并在出现错误消息时做出反应。 这个概念看起来像这样:

private class Interceptor extends PrintStream
{
    public Interceptor(OutputStream out)
    {
        super(out, true);
    }
    @Override
    public void print(String s)
    {//do what ever you like
        super.print(s);
    }
}
public static void main(String[] args)
{
    PrintStream origOut = System.out;
    PrintStream interceptor = new Interceptor(origOut);
    System.setOut(interceptor);// just add the interceptor
}

1
你可能还需要覆盖 println(String s) 方法。 - gaborsch

9
更好的解决方案是逐步将所有的println语句更改为使用正确的日志库。你所尝试的方法是一个大型的hack。

1
也许他正在尝试将System.out重定向到一个Logger中;) - Riduidel
1
+1:日志框架的整个目的是为了能够抽象记录输出的位置,您应该将其日志方法称为代码的“确定性”输出。更不用说,重定向System.out以调用记录器似乎可以工作;直到有一天您配置记录器将输出到stdout,然后-堆栈溢出。 - Andrzej Doyle
这并不是那么困难。+1 - Nivas
3
我大致同意,但如我在上面的回答中提到的,我需要截取第三方 System.out 的消息。没有其他方式。因此,这是有用的案例。甚至有使用 GOTO 的情况 :-) - Wizard of Kneup

6

以下是如何捕获System.out的输出,并按顺序放回的方法:

// Start capturing
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
System.setOut(new PrintStream(buffer));

// Run what is supposed to output something
...

// Stop capturing
System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));

// Use captured content
String content = buffer.toString();
buffer.reset();

3

扩展PrintStream是一个不好的解决方案,因为你将不得不覆盖所有的print()println()方法。相反,你可以捕获流:

public class ConsoleInterceptor {

    public interface Block {
        void call() throws Exception;
    }

    public static String copyOut(Block block) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        PrintStream printStream = new PrintStream(bos, true);
        PrintStream oldStream = System.out;
        System.setOut(printStream);
        try {
            block.call();
        }
        finally {
            System.setOut(oldStream);
        }
        return bos.toString();
    }
}

现在你可以像这样捕获它:
   String result = ConsoleInterceptor.copyOut(() ->{
        System.out.print("hello world");
        System.out.print('!');
        System.out.println();
        System.out.println("foobar");
    });
    assertEquals("hello world!\nfoobar\n", result);

不错的可重用解决方案。 我认为块接口可以被Runnable替换。 - Dimitar II

-1

我应用了这个基本思路,它运行良好。 不需要更改所有的System.out.###和System.err.###内容。

import java.io.OutputStream;
import java.io.PrintStream;   

public class Interceptor extends PrintStream
{
  /** the logger */
  private Logger log;
  /** the origin output stream */
  PrintStream orig;

  /**
   * Initializes a new instance of the class Interceptor.
   *
   * @param out the output stream to be assigned
   * @param log the logger
   */
  public Interceptor( OutputStream out, Logger log )
  {
    super( out, true );
    this.log = log;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void finalize() throws Throwable
  {
    detachOut();
    super.finalize();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void print( String s )
  {
    //do what ever you like
    orig.print( s );
    log.logO( s, true );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void println( String s )
  {
    print( s + Defines.LF_GEN );
  }

  /**
   * Attaches System.out to interceptor.
   */
  public void attachOut()
  {
    orig  = System.out;
    System.setOut( this );
  }

  /**
   * Attaches System.err to interceptor.
   */
  public void attachErr()
  {
    orig = System.err;
    System.setErr( this );
  }

  /**
   * Detaches System.out.
   */
  public void detachOut()
  {
    if( null != orig )
    {
      System.setOut( orig );
    }
  }

  /**
   * Detaches System.err.
   */
  public void detachErr()
  {
    if( null != orig )
    {
      System.setErr( orig );
    }
  }
}


public class InterceptionManager
{
  /** out */
  private Interceptor out;

  /** err */
  private Interceptor err;

  /** log  */
  private Logger log;

  /**
   * Initializes a new instance of the class InterceptionManager.
   *
   * @param logFileName the log file name
   * @param append the append flag
   */
  public InterceptionManager( String logFileName, boolean append )
  {
    log = new Logger();
    log.setLogFile( logFileName, append );
    this.out = new Interceptor( System.out, log );
    this.out.attachOut();
    this.err = new Interceptor( System.err, log );
    this.err.attachErr();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void finalize() throws Throwable
  {
    if( null != log )
    {
      log.closeLogFile();
    }
    super.finalize();
  }
}

这些视图行将使日志记录无需进一步的代码更改:

  if( writeLog )
  {
    logFileName = this.getClassName() + "_Log.txt";
    icMan = new InterceptionManager( logFileName, false );
    System.out.format( "Logging to '%s'\n", logFileName );
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接