[英]log4j redirect stdout to DailyRollingFileAppender
我有一個使用 log4j 的 java 應用程序。
配置:
log4j.rootLogger=info, file
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File=${user.home}/logs/app.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d [%t] %c %p %m%n
所以所有日志語句都正確附加到文件中,但我丟失了標准輸出和標准錯誤。 我如何將異常堆棧跟蹤和系統輸出重定向到每日滾動文件?
// I set up a ConsoleAppender in Log4J to format Stdout/Stderr
log4j.rootLogger=DEBUG, CONSOLE
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=[%t] %-5p %c - %m%n
// And I call this StdOutErrLog.tieSystemOutAndErrToLog() on startup
public class StdOutErrLog {
private static final Logger logger = Logger.getLogger(StdOutErrLog.class);
public static void tieSystemOutAndErrToLog() {
System.setOut(createLoggingProxy(System.out));
System.setErr(createLoggingProxy(System.err));
}
public static PrintStream createLoggingProxy(final PrintStream realPrintStream) {
return new PrintStream(realPrintStream) {
public void print(final String string) {
realPrintStream.print(string);
logger.info(string);
}
};
}
}
在 Skaffman 代碼中:要刪除 log4j 日志中的空行,只需在 createLoggingProxy 的 PrintStream 中添加“println”方法
public static PrintStream createLoggingProxy(final PrintStream realPrintStream) {
return new PrintStream(realPrintStream) {
public void print(final String string) {
logger.warn(string);
}
public void println(final String string) {
logger.warn(string);
}
};
}
我從 Michael S. 那里得到了這個想法,但就像在一條評論中提到的那樣,它有一些問題:它沒有捕獲所有內容,並且會打印一些空行。
此外,我想將System.out
和System.err
分開,以便System.out
以日志級別'INFO'
記錄,而System.err
以'ERROR'
(或'WARN'
如果您願意)記錄。
所以這是我的解決方案:首先是一個擴展OutputStream
的類(覆蓋OutputStream
所有方法比覆蓋PrintStream
更容易)。 它使用指定的日志級別記錄並將所有內容復制到另一個OutputStream
。 並且它還檢測“空”字符串(僅包含空格)並且不記錄它們。
import java.io.IOException;
import java.io.OutputStream;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public class LoggerStream extends OutputStream
{
private final Logger logger;
private final Level logLevel;
private final OutputStream outputStream;
public LoggerStream(Logger logger, Level logLevel, OutputStream outputStream)
{
super();
this.logger = logger;
this.logLevel = logLevel;
this.outputStream = outputStream;
}
@Override
public void write(byte[] b) throws IOException
{
outputStream.write(b);
String string = new String(b);
if (!string.trim().isEmpty())
logger.log(logLevel, string);
}
@Override
public void write(byte[] b, int off, int len) throws IOException
{
outputStream.write(b, off, len);
String string = new String(b, off, len);
if (!string.trim().isEmpty())
logger.log(logLevel, string);
}
@Override
public void write(int b) throws IOException
{
outputStream.write(b);
String string = String.valueOf((char) b);
if (!string.trim().isEmpty())
logger.log(logLevel, string);
}
}
然后,一個非常簡單的工具類,設置out
和err
:
import java.io.PrintStream;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public class OutErrLogger
{
public static void setOutAndErrToLog()
{
setOutToLog();
setErrToLog();
}
public static void setOutToLog()
{
System.setOut(new PrintStream(new LoggerStream(Logger.getLogger("out"), Level.INFO, System.out)));
}
public static void setErrToLog()
{
System.setErr(new PrintStream(new LoggerStream(Logger.getLogger("err"), Level.ERROR, System.err)));
}
}
如果您使用的是應用程序服務器、servlet 容器或類似的東西,請參閱kgiannakakis 的回答。
對於獨立的應用程序,請參閱此。 您可以使用java.lang.System類重新評估stdin 、 stdout和stderr 。 基本上,您創建 PrintStream 的一個新子類並將該實例設置為 System.out。
在您的應用程序開始時遵循這些內容(未經測試)。
// PrintStream object that prints stuff to log4j logger
public class Log4jStream extends PrintStream {
public void write(byte buf[], int off, int len) {
try {
// write stuff to Log4J logger
} catch (Exception e) {
}
}
}
// reassign standard output to go to log4j
System.setOut(new Log4jStream());
上面的答案提供了使用代理進行 STDOUT/ERR 日志記錄的好主意。 然而,提供的實現示例並不適用於所有情況。 例如,嘗試
System.out.printf("測試%s\\n", "ABC");
上面示例中的代碼將在控制台和多個不可讀的 Log4j 條目中將輸出切割成單獨的部分。
解決方案是緩沖輸出,直到在緩沖區的末尾找到觸發器 '\\n'。 有時緩沖區以 '\\r\\n' 結尾。 下面的課程解決了這個問題。 它功能齊全。 調用靜態方法 bind() 來激活它。
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
// Based on
// http://stackoverflow.com/questions/1200175/log4j-redirect-stdout-to-dailyrollingfileappender
public class Log4jStdOutErrProxy {
public static void bind() {
bind(Logger.getLogger("STDOUT"), Logger.getLogger("STDERR"));
}
@SuppressWarnings("resource")
public static void bind(Logger loggerOut, Logger loggerErr) {
System.setOut(new PrintStream(new LoggerStream(loggerOut, Level.INFO, System.out), true));
System.setErr(new PrintStream(new LoggerStream(loggerErr, Level.ERROR, System.err), true));
}
private static class LoggerStream extends OutputStream {
private final Logger logger;
private final Level logLevel;
private final OutputStream outputStream;
private StringBuilder sbBuffer;
public LoggerStream(Logger logger, Level logLevel, OutputStream outputStream) {
this.logger = logger;
this.logLevel = logLevel;
this.outputStream = outputStream;
sbBuffer = new StringBuilder();
}
@Override
public void write(byte[] b) throws IOException {
doWrite(new String(b));
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
doWrite(new String(b, off, len));
}
@Override
public void write(int b) throws IOException {
doWrite(String.valueOf((char)b));
}
private void doWrite(String str) throws IOException {
sbBuffer.append(str);
if (sbBuffer.charAt(sbBuffer.length() - 1) == '\n') {
// The output is ready
sbBuffer.setLength(sbBuffer.length() - 1); // remove '\n'
if (sbBuffer.charAt(sbBuffer.length() - 1) == '\r') {
sbBuffer.setLength(sbBuffer.length() - 1); // remove '\r'
}
String buf = sbBuffer.toString();
sbBuffer.setLength(0);
outputStream.write(buf.getBytes());
outputStream.write('\n');
logger.log(logLevel, buf);
}
}
} // inner class LoggerStream
}
對於那些在 log4j2 中尋找如何做到這一點的人。 現在有一個組件可以為您創建這些流。
需要包含 log4j-iostreams jar
請參閱:https ://logging.apache.org/log4j/2.x/log4j-iostreams/index.html
例子:
PrintStream logger = IoBuilder.forLogger("System.out").setLevel(Level.DEBUG).buildPrintStream();
PrintStream errorLogger = IoBuilder.forLogger("System.err").setLevel(Level.ERROR).buildPrintStream();
System.setOut(logger);
System.setErr(errorLogger);
我想您是通過e.printStackTrace()
記錄堆棧e.printStackTrace()
嗎? 您可以將異常對象傳遞到 Log4j 日志記錄方法中,這些對象將出現在您的日志中(請參閱Logger.error(Object obj, Throwable t) )
請注意,您可以將 System.out 和 System.err 更改為另一個重定向到 Log4j 的PrintStream 。 這將是一個簡單的更改,並且可以節省您轉換所有System.out.println()
語句。
標准輸出和錯誤流由您的容器管理。 例如,Tomcat 使用 JULI 來記錄輸出和錯誤流。
我的建議是保持原樣。 避免在您的應用程序中使用 System.out.print。 有關堆棧跟蹤,請參見此處。
@Michael 的 anser 是一個很好的觀點。 但是擴展 PrintStream 並不是很好,因為它使用內部方法void write(String)
將所有內容寫入 OutputStream。
我更喜歡使用 Log4J Contrib 包中的LoggingOutputStream
類。
然后我像這樣重定向系統流:
public class SysStreamsLogger {
private static Logger sysOutLogger = Logger.getLogger("SYSOUT");
private static Logger sysErrLogger = Logger.getLogger("SYSERR");
public static final PrintStream sysout = System.out;
public static final PrintStream syserr = System.err;
public static void bindSystemStreams() {
// Enable autoflush
System.setOut(new PrintStream(new LoggingOutputStream(sysOutLogger, LogLevel.INFO), true));
System.setErr(new PrintStream(new LoggingOutputStream(sysErrLogger, LogLevel.ERROR), true));
}
public static void unbindSystemStreams() {
System.setOut(sysout);
System.setErr(syserr);
}
}
在使用 System.setOut 和 System.setErr 方法之前,我們應該使用 reset() 方法重置 java.util.logging.LogManager 對象。
public static void tieSystemOutAndErrToLog() {
try{
// initialize logging to go to rolling log file
LogManager logManager = LogManager.getLogManager();
logManager.reset();
// and output on the original stdout
System.out.println("Hello on old stdout");
System.setOut(createLoggingProxy(System.out));
System.setErr(createLoggingProxy(System.err));
//Following is to make sure whether system.out and system.err is redirecting to the stdlog.log file
System.out.println("Hello world!");
try {
throw new RuntimeException("Test");
} catch (Exception e) {
e.printStackTrace();
}
}catch(Exception e){
logger.error("Caught exception at StdOutErrLog ",e);
e.printStackTrace();
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.