log4j перенаправить стандартный вывод в DailyRollingFileAppender
У меня есть приложение Java, которое использует log4j.
Config:
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
Таким образом, все операторы журнала правильно добавлены в файл, но я теряю stdout и stderr. Как перенаправить трассировки и системные вызовы стека исключений в ежедневно обновляемый файл?
9 ответов
// 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);
}
};
}
}
Я взял идею от Майкла С., но, как упоминалось в одном комментарии, у него есть некоторые проблемы: он не захватывает все, и печатает несколько пустых строк.
Также я хотел отделить 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)));
}
}
В коде Скаффмана: чтобы удалить пустые строки в логах log4j, просто добавьте метод println в PrintStream createLoggingProxy
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);
}
};
}
Ответы выше дают отличную идею использовать прокси для регистрации 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://stackru.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. Теперь есть компонент для создания этих потоков для вас.
Требуется включение jar-файла log4j-iostreams
См. 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);
Если вы используете сервер приложений, контейнер сервлетов или что-то подобное, см . Ответ kgiannakakis.
Для автономных приложений смотрите это. Вы можете перезапустить stdin, stdout и stderr, используя класс java.lang.System. По сути, вы создаете новый подкласс 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());
Стандартные потоки вывода и ошибок управляются из вашего контейнера. Например, Tomcat использует JULI для регистрации выходных данных и потоков ошибок.
Я рекомендую оставить все как есть. Избегайте использования System.out.print в вашем приложении. Смотрите здесь для следов стека.
Я предполагаю, что вы регистрируете трассировки стека через e.printStackTrace()
? Вы можете передать объект исключения в методы ведения журнала Log4j, и они появятся в вашем журнале (см. Logger.error (Object obj, Throwable t))
Обратите внимание, что вы можете изменить System.out и System.err на другой PrintStream, который перенаправляет на Log4j. Это было бы простым изменением и избавило бы вас от конвертации всех ваших System.out.println()
заявления.
Ансер @Michael - это хорошая точка. Но расширение PrintStream не очень хорошо, потому что он использует внутренний метод void write(String)
написать все вещи в OutputStream.
Я предпочитаю использовать LoggingOutputStream
Класс из пакета Log4J Contrib.
Затем я перенаправляю системные потоки так:
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 мы должны сбросить объект java.util.logging.LogManager с помощью метода reset().
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();
}
}