簡體   English   中英

重定向System.out和System.err

[英]Redirect System.out and System.err

我有一些遺留代碼(或者更確切地說是一些我們無法控制但我們必須使用的代碼),它們將大量語句寫入system.out / err。

與此同時,我們正在使用一個框架,該框架使用圍繞log4j的自定義日志記錄系統(不幸的是,我們不能控制它)。

所以我正在嘗試將out和err流重定向到將使用日志記錄系統的自定義PrintStream。 我正在閱讀有關System.setLog()System.setErr()方法的信息,但問題是我需要編寫自己的PrintStream類來包裝正在使用的日志記錄系統。 那將是一個巨大的麻煩。

有沒有一種簡單的方法來實現這一目標?

只是為了添加Rick和Mikhail的解決方案,這是本場景中唯一的選擇,我想舉例說明如何創建自定義OutputStream可能會導致不容易檢測/修復問題。 這是一些代碼:

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import org.apache.log4j.Logger;

public class RecursiveLogging {
  /**
   * log4j.properties file:
   * 
   * log4j.rootLogger=DEBUG, A1
   * log4j.appender.A1=org.apache.log4j.ConsoleAppender
   * log4j.appender.A1.layout=org.apache.log4j.PatternLayout
   * log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
   * 
   */
  public static void main(String[] args) {
    // Logger.getLogger(RecursiveLogging.class).info("This initializes log4j!");
    System.setOut(new PrintStream(new CustomOutputStream()));
    System.out.println("This message causes a stack overflow exception!");
  }
}

class CustomOutputStream extends OutputStream {
  @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.getLogger(CustomOutputStream.class).info((char) b);
  }
}

此示例顯示了使用自定義輸出流的缺陷。 為簡單起見,write()函數使用log4j記錄器,但這可以替換為任何自定義日志記錄工具(例如我的方案中的那個)。 main函數創建一個PrintStream,它包裝CustomOutputStream並將輸出流設置為指向它。 然后它執行System.out.println()語句。 此語句將重定向到CustomOutputStream,后者將其重定向到記錄器。 不幸的是,由於記錄器是惰性初始化的,它將獲取控制台輸出流的副本(根據定義ConsoleAppender的log4j配置文件)太晚,即輸出流將指向我們剛剛創建的CustomOutputStream導致重定向循環,因此運行時的StackOverflowError。

現在,使用log4j,這很容易解決:我們只需要調用System.setOut() 之前初始化log4j框架,例如,通過取消注釋main函數的第一行。 幸運的是,我必須處理的自定義日志記錄工具只是log4j的一個包裝器,我知道它會在它為時已晚之前進行初始化。 但是,對於在封面下使用System.out / err的完全自定義日志記錄工具,除非可以訪問源代碼,否則無法判斷是否以及在何處執行對System.out / err的直接調用而不是調用初始化期間獲取的PrintStream引用。 對於這種特殊情況,我能想到的唯一工作就是檢索函數調用堆棧並檢測重定向循環,因為write()函數不應該是遞歸的。

您不應該包圍您正在使用的自定義日志記錄系統。 當應用程序初始化時,只需創建一個LoggingOutputStream並在System.setOut()和System.setErr()方法上設置該流,並為每個方法設置所需的日志記錄級別。 從那時起,應用程序中遇到的任何System.out語句都應直接轉到日志中。

看一下PrintStream提供的構造函數。 您可以將日志記錄框架OutputStream直接傳遞給PrintStream,然后將System.out和System.err設置為使用PrintStream。

編輯:這是一個簡單的測試用例:

public class StreamTest
{
    public static class MyOutputStream extends FilterOutputStream
    {
        public MyOutputStream(OutputStream out)
        {
            super(out);
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException
        {
            byte[] text = "MyOutputStream called: ".getBytes();         
            super.write(text, 0, text.length);
            super.write(b, off, len);
        }
    }

    @Test   
    public void test()
    {       
        //Any OutputStream-implementation will do for PrintStream, probably
        //you'll want to use the one from the logging framework
        PrintStream outStream = new PrintStream(new MyOutputStream(System.out), true);  //Direct to MyOutputStream, autoflush
        System.setOut(outStream); 

        System.out.println("");
        System.out.print("TEST");
        System.out.println("Another test");
    }
}

輸出是:

MyOutputStream called: 
MyOutputStream called: TESTMyOutputStream called: Another testMyOutputStream called: 

第二行有一個“空”調用( MyOutputStream調用: -output,之后沒有任何內容),因為println將首先將另一個測試 -string傳遞給write-method,然后再使用換行符再次調用它。

只需編寫自己的OutputStream,然后將其包裝到標准PrintStream中。 OutputStream只有一個必須實現的方法,另一個由於性能原因值得重寫。

唯一的技巧是將字節流切片為單獨的日志消息,例如'\\ n',但這也不太難實現。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM