Log4J: Как мне перенаправить OutputStream или Writer на записывающие устройства регистратора?

У меня есть метод, который запускается асинхронно после запуска, используя в качестве параметра OutputStream или Writer.

Он действует как адаптер записи для OutputStream или Writer (это сторонний API, который я не могу изменить).

Как мне передать внутренний OutputStream или Writer Log4J этому методу?
...потому что Log4J поглощает System.out и System.err, которые я использовал раньше.


person java.is.for.desktop.indeed    schedule 09.08.2011    source источник
comment
Не могли бы вы просто реализовать свой собственный OutputStream, делегировав write(...) регистратору?   -  person Rekin    schedule 09.08.2011
comment
@Rekin именно то, что я сделал ниже!   -  person Arthur Neves    schedule 09.08.2011
comment
Что вы имеете в виду, говоря, что Log4J проглатывает System.err?   -  person Tom G    schedule 09.08.2011
comment
Проглатывает вывод == вывод нигде не отображается ;)   -  person java.is.for.desktop.indeed    schedule 09.08.2011


Ответы (5)


Мое предложение: почему бы вам тогда не написать свой OutputStream?! Я собирался написать один для вас, но я нашел этот хороший пример в сети, проверьте его!

LogOutputStream.java

/*
 * Jacareto Copyright (c) 2002-2005
 * Applied Computer Science Research Group, Darmstadt University of
 * Technology, Institute of Mathematics & Computer Science,
 * Ludwigsburg University of Education, and Computer Based
 * Learning Research Group, Aachen University. All rights reserved.
 *
 * Jacareto is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * Jacareto is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with Jacareto; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

package jacareto.toolkit.log4j;


import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import java.io.OutputStream;

/**
 * This class logs all bytes written to it as output stream with a specified logging level.
 *
 * @author <a href="mailto:[email protected]">Christian Spannagel</a>
 * @version 1.0
 */
public class LogOutputStream extends OutputStream {
    /** The logger where to log the written bytes. */
    private Logger logger;

    /** The level. */
    private Level level;

    /** The internal memory for the written bytes. */
    private String mem;

    /**
     * Creates a new log output stream which logs bytes to the specified logger with the specified
     * level.
     *
     * @param logger the logger where to log the written bytes
     * @param level the level
     */
    public LogOutputStream (Logger logger, Level level) {
        setLogger (logger);
        setLevel (level);
        mem = "";
    }

    /**
     * Sets the logger where to log the bytes.
     *
     * @param logger the logger
     */
    public void setLogger (Logger logger) {
        this.logger = logger;
    }

    /**
     * Returns the logger.
     *
     * @return DOCUMENT ME!
     */
    public Logger getLogger () {
        return logger;
    }

    /**
     * Sets the logging level.
     *
     * @param level DOCUMENT ME!
     */
    public void setLevel (Level level) {
        this.level = level;
    }

    /**
     * Returns the logging level.
     *
     * @return DOCUMENT ME!
     */
    public Level getLevel () {
        return level;
    }

    /**
     * Writes a byte to the output stream. This method flushes automatically at the end of a line.
     *
     * @param b DOCUMENT ME!
     */
    public void write (int b) {
        byte[] bytes = new byte[1];
        bytes[0] = (byte) (b & 0xff);
        mem = mem + new String(bytes);

        if (mem.endsWith ("\n")) {
            mem = mem.substring (0, mem.length () - 1);
            flush ();
        }
    }

    /**
     * Flushes the output stream.
     */
    public void flush () {
        logger.log (level, mem);
        mem = "";
    }
}
person Arthur Neves    schedule 09.08.2011
comment
Рад, что нашел это. В моем сценарии использования у данного LogOutputStream есть следующая проблема, которую мне нужно было исправить: 1. Он полагается на log4j вместо commons-logging, который я изменил, и я жестко запрограммировал logger.info(), поскольку commons-logging не имеет уровней (насколько я вижу в секунд) 1. Предполагается, что новая строка равна \n, которую мне нужно было изменить на System.getProperty("line.separator") 1. Поскольку ее flush() не проверяет, является ли mem пустым, вы получаете дополнительную пустую строку на выходе, когда обертка Writer вызывает flush() после последнего println() - person hokr; 25.07.2013
comment
и в вашем классе Logger logger = Logger.getLogger(new Object(){}.getClass().getEnclosingClass()); OutputStream stdout = new PrintStream(new LogOutputStream(logger, Level.INFO)); ----Затем напишите данные в свой стандартный вывод;) - person saygley; 17.02.2020
comment
Однако это очень неэффективная реализация. Он не только записывает байт за байтом, запись каждой строки занимает время выполнения и потребление памяти O (n²) с новой строкой, которую он создает с каждым байтом. Его уже можно было улучшить, используя StringBuffer вместо String и повторно используя его память после сброса. - person Jimmy T.; 19.09.2020

Вы можете использовать Log4j IOStreams.

Компонент IOStreams — это расширение API Log4j, предоставляющее многочисленные классы из java.io, которые могут либо записывать в Logger, одновременно записывая в другой OutputStream или Writer, либо содержимое, прочитанное InputStream или Reader, может прослушиваться Logger.

Вы можете создать OutputStream следующим образом:

OutputStream outputStream = IoBuilder
            .forLogger(logger)
            .buildOutputStream();

Ниже приведен пример с Appium, запускающим его программно и управляющим его логом с помощью log4j.

    final Logger logger = LogManager.getLogger(getClass());

    cap = new DesiredCapabilities();
    cap.setCapability("noReset", "false");

    //Build the Appium service
    builder = new AppiumServiceBuilder();
    builder.withIPAddress("127.0.0.1");
    builder.usingPort(4723);
    builder.withCapabilities(cap);
    builder.withArgument(GeneralServerFlag.SESSION_OVERRIDE);
    builder.withArgument(GeneralServerFlag.LOG_LEVEL,"debug");

    //Start the server with the builder
    service = AppiumDriverLocalService.buildService(builder);

    OutputStream outputStream = IoBuilder
            .forLogger(logger)
            .buildOutputStream();
    service.addOutPutStream(outputStream);

    service.start();

Надеюсь это поможет!!!

person Carlos Cuesta    schedule 29.10.2018

Источник: http://sysgears.com/articles/how-to-redirect-stdout-and-stderr-writing-to-a-log4j-appender/

Цитата

Log4j не позволяет перехватывать сообщения stdout и stderr из коробки. Однако, если вы используете сторонние компоненты и должны регистрировать сообщения, которые они сбрасывают в потоки, вы можете пойти на небольшой трюк и реализовать собственный поток вывода, который поддерживает ведение журнала.

Это уже сделал Джим Мур (см. LoggingOutputStream в исходном коде log4j). Единственная проблема заключается в том, что для LoggingOutputStream JimMoore требуются org.apache.log4j.Category и org.apache.log4j.Priority, которые теперь частично устарели.

Вот модифицированный LoggingOutputStream, который позволяет избежать устаревших методов:

public class LoggingOutputStream extends OutputStream {

    /**
     * Default number of bytes in the buffer.
     */
    private static final int DEFAULT_BUFFER_LENGTH = 2048;

    /**
     * Indicates stream state.
     */
    private boolean hasBeenClosed = false;

    /**
     * Internal buffer where data is stored.
     */
    private byte[] buf;

    /**
     * The number of valid bytes in the buffer.
     */
    private int count;

    /**
     * Remembers the size of the buffer.
     */
    private int curBufLength;

    /**
     * The logger to write to.
     */
    private Logger log;

    /**
     * The log level.
     */
    private Level level;

    /**
     * Creates the Logging instance to flush to the given logger.
     *
     * @param log         the Logger to write to
     * @param level       the log level
     * @throws IllegalArgumentException in case if one of arguments
     *                                  is  null.
     */
    public LoggingOutputStream(final Logger log,
                               final Level level)
            throws IllegalArgumentException {
        if (log == null || level == null) {
            throw new IllegalArgumentException(
                    "Logger or log level must be not null");
        }
        this.log = log;
        this.level = level;
        curBufLength = DEFAULT_BUFFER_LENGTH;
        buf = new byte[curBufLength];
        count = 0;
    }

    /**
     * Writes the specified byte to this output stream.
     *
     * @param b the byte to write
     * @throws IOException if an I/O error occurs.
     */
    public void write(final int b) throws IOException {
        if (hasBeenClosed) {
            throw new IOException("The stream has been closed.");
        }
        // don't log nulls
        if (b == 0) {
            return;
        }
        // would this be writing past the buffer?
        if (count == curBufLength) {
            // grow the buffer
            final int newBufLength = curBufLength +
                    DEFAULT_BUFFER_LENGTH;
            final byte[] newBuf = new byte[newBufLength];
            System.arraycopy(buf, 0, newBuf, 0, curBufLength);
            buf = newBuf;
            curBufLength = newBufLength;
        }

        buf[count] = (byte) b;
        count++;
    }

    /**
     * Flushes this output stream and forces any buffered output
     * bytes to be written out.
     */
    public void flush() {
        if (count == 0) {
            return;
        }
        final byte[] bytes = new byte[count];
        System.arraycopy(buf, 0, bytes, 0, count);
        String str = new String(bytes);
        log.log(level, str);
        count = 0;
    }

    /**
     * Closes this output stream and releases any system resources
     * associated with this stream.
     */
    public void close() {
        flush();
        hasBeenClosed = true;
    }
}

Теперь вы можете перехватывать сообщения, которые сбрасываются на stderr или stdout, следующим образом:

System.setErr(new PrintStream(new LoggingOutputStream(
        Logger.getLogger("outLog"), Level.ERROR)));

Конфигурация log4j.properties:

log4j.logger.outLog=error, out_log

log4j.appender.out_log=org.apache.log4j.RollingFileAppender
log4j.appender.out_log.file=/logs/error.log
log4j.appender.out_log.MaxFileSize=10MB
log4j.appender.out_log.threshold=error

Дмитрий Павленко, SysGears

Цитата

person Stefan    schedule 18.02.2015

Основываясь на ответе Arthur Neves, я перенес это для Slf4J. Я также немного улучшил это с помощью StringBuffer и прямого преобразования байта в char:

import java.io.OutputStream;

import org.slf4j.Logger;

public class LogOutputStream extends OutputStream {
    private final Logger logger;

    /** The internal memory for the written bytes. */
    private StringBuffer mem;

    public LogOutputStream( final Logger logger ) {
        this.logger = logger;
        mem = new StringBuffer();
    }

    @Override
    public void write( final int b ) {
        if ( (char) b == '\n' ) {
            flush();
            return;
        }
        mem = mem.append( (char) b );
    }

    @Override
    public void flush() {
        logger.info( mem.toString() );
        mem = new StringBuffer();
    }
}
person roesslerj    schedule 31.07.2019

Поскольку предыдущий пример Writer, направленный на Log4J, вышел из строя: http://www.opensource.apple.com/source/JBoss/JBoss-737/jboss-all/common/src/main/org/jboss/logging/util/LoggerWriter.java

person gerardw    schedule 13.08.2015
comment
Хотя теоретически это может ответить на вопрос, было бы предпочтительнее включить сюда основные части ответа и предоставить ссылку для справки . - person Karl Richter; 13.10.2017