36

我有一个在启动后异步运行的方法,使用 OutputStream 或 Writer 作为参数。

它充当 OutputStream 或 Writer 的记录适配器(它是我无法更改的第三方 API)。

如何将 Log4J 的内部 OutputStream 或 Writer 传递给该方法?
...因为 Log4J 吞下了 System.out 和 System.err,我以前使用过。

4

7 回答 7

16

我的建议是,那你为什么不写你的 OutputStream 呢?!本来想写一个给你的,但是我在网上找到了这个很好的例子,看看吧!

日志输出流.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:cspannagel@web.de">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 = "";
    }
}
于 2011-08-09T12:24:30.913 回答
10

您可以使用Log4j IOStreams

IOStreams 组件是一个 Log4j API 扩展,它提供了来自 java.io 的许多类,这些类可以在写入另一个 OutputStream 或 Writer 的同时写入 Logger,或者由 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();

希望这可以帮助!!!

于 2018-10-29T13:57:47.347 回答
4

资料来源:http ://sysgears.com/articles/how-to-redirect-stdout-and-stderr-writing-to-a-log4j-appender/

块引用

Log4j 不允许开箱即用地捕获 stdout 和 stderr 消息。但是,如果您使用第三方组件并且必须记录它们刷新到流中的消息,那么您可以做一些小技巧并实现支持日志记录的自定义输出流。

这已经由 Jim Moore 完成(参见 log4j 源代码中的 LoggingOutputStream)。唯一的问题是 JimMoore 的 LoggingOutputStream 需要 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

Dmitriy Pavlenko,SysGears

块引用

于 2015-02-18T08:25:36.040 回答
4

基于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();
    }
}
于 2019-07-31T09:52:45.983 回答
1

浏览这里的答案,似乎它们都没有清楚地说明将字节解码为字符串(扩展 CharSequence)。bytes 是chars 不等价的(请参阅 Java 中的 OutputStream 与 Writer)。一个简单的非拉丁字符例如羼可以表示为一系列字节:E7 BE BC(羼的UTF-8序列)。

其他人不考虑特定编码的原因:

  • (char) b将在不解释 UTF-8 的情况下转换非拉丁字符,因此羼 变为ç¾¼,或“Róbert”变为“Róbert”(哦,我见过很多次了)。你可能更熟悉这种美:(UTF-8 BOM)
  • new String(bytes)创建一个“使用平台的默认字符集”的字符串,这取决于您运行代码的位置,因此您可能会在服务器和本地计算机上获得不同的行为。这比 更好(char)b,因为您至少可以指定编码。
  • log4j-iostreams的 IoBuilder 默认也使用平台的默认字符集,但是是可配置的。此外,这不是通用解决方案,仅在您使用 SLF4J 而不是 Log4j 2 时才有效。尽管那是 OP 的问题。

(原谅我的 Kotlin,你可以用不同的语法在 Java 中做同样的事情。)

private fun loggerStream(outputLine: (line: String) -> Unit): PipedOutputStream {
    val output = PipedOutputStream()
    val input = PipedInputStream(output).bufferedReader()
    thread(isDaemon = true) {
        input.lineSequence().forEach(outputLine)
    }
    return output
}

使用此解决方案:

  • 过渡是高性能的(缓冲的)
  • 编码是一致指定的(bufferedReader有一个默认参数:charset = Charsets.UTF_8,可以根据需要更改)
  • 编码的开销在后台线程中,虽然有点奇怪,但这就是管道的工作方式。
  • 代码很简单/高级
    (没有字节数组、索引、副本、字符串构造函数等)

注意:我使用它将 Selenium ChromeDriver 的输出(默认为 stderr)重定向到 Log4J 2 上的 SLF4J:

    val service = ChromeDriverService.createServiceWithConfig(options).apply {
        sendOutputTo(loggerStream(LoggerFactory.getLogger(ChromeDriver::class.java)::info))
    }
    val driver = ChromeDriver(service, options)
于 2021-10-30T21:56:19.210 回答
1

在阅读了https://stackoverflow.com/a/6996147/1773490的答案后,我开始查看现有的OutputStream实现,并偶然发现了org.apache.commons.exec.LogOutputStream.

您只需要将它包含到您的项目中,就像使用 Maven 一样:

// ...
<dependencies>
    // ...
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-exec</artifactId>
        <version>1.3</version>
    </dependency>
    // ...
</dependencies>
// ...

这是我在使用 Selenium 与 Chrome 驱动程序和 Google 的 Flogger 的项目中使用的一个实现示例:

package com.vk.logging;

import com.google.common.flogger.FluentLogger;
import org.apache.commons.exec.LogOutputStream;

import java.util.List;
import java.util.logging.Level;

public class FloggerOutputStream extends LogOutputStream {
    
    // TODO This can be replaced with your favorite logger
    private static final FluentLogger LOGGER = FluentLogger.forEnclosingClass();

    private static final List<Level> KNOWN_LEVELS = List.of(
            Level.OFF,
            Level.SEVERE,
            Level.WARNING,
            Level.INFO,
            Level.CONFIG,
            Level.FINE,
            Level.FINER,
            Level.FINEST,
            Level.ALL
    );

    @Override
    protected void processLine(String line, int logLevel) {
        // TODO This can be replaced with your favorite logger
        LOGGER.at(findClosestLevel(logLevel))
              .log(line);
    }

    private Level findClosestLevel(int logLevel) {
        try {
            return Level.parse(String.valueOf(logLevel));
        } catch (IllegalArgumentException e) {
            // Find the closest level
            for (Level knownLevel : KNOWN_LEVELS) {
                if (knownLevel.intValue() < logLevel) {
                    return knownLevel;
                }
            }
        }

        throw new IllegalArgumentException(
                "Log level " + logLevel + " cannot be mapped to a known log level");
    }
}

通常不应该有任何奇怪logLevel的情况,但这种极端情况是通过找到最接近的已知日志级别来管理的。

希望这可以帮助

于 2022-02-16T10:07:48.793 回答
0

由于上一个指向 Log4J 的 Writer 示例已失效:http ://www.opensource.apple.com/source/JBoss/JBoss-737/jboss-all/common/src/main/org/jboss/logging/ util/LoggerWriter.java

于 2015-08-13T17:06:31.717 回答