gpt4 book ai didi

java - 使用带有单独线程的 java.util.logger 来写入文件?

转载 作者:行者123 更新时间:2023-12-03 01:10:16 25 4
gpt4 key购买 nike

我有 2 个 FileHandler,它们写入两个单独的文件,并且发生的 I/O 量大大减慢了我的应用程序的速度:

  1. 我决定让 FileHandler 在单独的线程上运行。

  2. 由于它们位于单独的线程上,因此我需要一个“队列”的概念,以便这些单独的线程可以轮询该队列并打印出任何传入的消息。

  3. 我已经预先格式化了消息,以便使用的任何参数在实际到达 FileHandler 中的打印输出之前都不会更改。

  4. 现在我意识到我无法使用记录器提供的“log”方法,因为它尝试调用当前线程上的方法来格式化和打印消息。

  5. 因此,我只需调用一个方法,将跟踪消息添加到队列中。

  6. 然后,我使用 FileHandlers 的 run() 方法通过publish() 打印出消息。

  7. 我现在意识到publish()只接受LogRecord,它只是一个级别+消息。

  8. 我的跟踪还有更多,不能简单地放在整个消息中,我希望能够使用我为 FileHandler 设置的“Formatter”。

    <
  9. 因此,我在 FileHandler 中创建了一个记录器实例,这样我就可以使用 log 方法并按照 Formatter 中的设计格式化字符串。

这确实有效。

...

这有点愚蠢,是否值得继续像这样,在 java.util.Logger 周围工作而不是使用它? java.util.Logger 的有用部分之一是为每个类提供一个单独的记录器实例,并且能够对消息进行更多控制...

有什么建议吗?

代码很长,但是我认为从上面的描述中很容易理解,如果没有让我知道,我会上传到某个地方。

最佳答案

如果 I/O 确实是瓶颈,并且您不需要文件轮换和文件锁定,则创建 Handler将 LogRecord +“跟踪消息”中的完全格式化的输出字符串/字节缓冲区排队。然后将完全格式化的输出字符串/字节缓冲区移交给线程/将其排队以执行 I/O。

否则,如果您需要使用FileHandler并且想要将 LogRecord + 您的跟踪传递到发布方法,您只需对 FileHandler 进行子类化,然后在您的 LogRecord 和跟踪之间创建一个对自定义格式化程序可见的映射。有几种方法可以做到这一点:

  1. 创建一个对处理程序和格式化程序都可见的 Map
  2. 创建一个 LogRecord 子类来保存跟踪并将每个 LogRecord 转换为新的子类并 super.publish LogRecord 子类。然后将每个 LogRecord 转换到格式化程序中以访问跟踪。

4.Now I've realised that I can't use the "log" methods provided by the logger, as that attempts to invoke methods on the current thread to format and print out the messages.

默认情况下,Logger.log 创建 LogRecords 并为附加处理程序和父处理程序调用 handler.publish。 handler.publish 在当前线程上执行 I/O。您要做的就是删除所有在发布时执行 I/O 的处理程序,并将它们替换为仅在发布时对 LogRecords 进行排队的处理程序。

以下是如何创建 AsyncFileHandler 的示例:

    public class AsyncFileHandler extends FileHandler implements Runnable {

private static final int offValue = Level.OFF.intValue();
private final BlockingQueue<LogRecord> queue = new ArrayBlockingQueue<>(5000);
private volatile Thread worker;

public AsyncFileHandler() throws IOException {
super();
}

public AsyncFileHandler(String pattern, int limit, int count, boolean append)
throws IOException {
super(pattern, limit, count, append);
}

@Override
public void publish(LogRecord record) {
int levelValue = getLevel().intValue();
if (record.getLevel().intValue() < levelValue || levelValue == offValue) {
return;
}

final Thread t = checkWorker();
record.getSourceMethodName(); //Infer caller.
boolean interrupted = Thread.interrupted();
try {
for (;;) {
try {
boolean offered = queue.offer(record, 10, TimeUnit.MILLISECONDS);
if (t == null || !t.isAlive()) {
if (!offered || queue.remove(record)) {
handleShutdown(record);
}
break;
} else {
if (offered || handleFullQueue(record)) {
break;
}
}
} catch (InterruptedException retry) {
interrupted = true;
}
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}

private boolean handleFullQueue(LogRecord r) {
super.publish(r);
return true; //true if handled.
}

private void handleShutdown(LogRecord r) {
super.publish(r);
}

@Override
public void close() {
try {
try {
final Thread t = this.worker;
if (t != null) {
t.interrupt();
shutdownQueue();
t.join();
shutdownQueue();
}
} finally {
super.close();
}
} catch (InterruptedException reAssert) {
Thread.currentThread().interrupt();
}
}

private void shutdownQueue() {
for (LogRecord r; (r = queue.poll()) != null;) {
handleShutdown(r);
}
}

@Override
public void run() {
try {
final BlockingQueue<LogRecord> q = this.queue;
for (;;) {
super.publish(q.take());
}
} catch (InterruptedException shutdown) {
shutdownQueue();
Thread.currentThread().interrupt();
}
}

private Thread checkWorker() {
Thread t = worker;
if (t == null) {
t = startWorker();
}
return t;
}

private synchronized Thread startWorker() {
if (worker == null) {
worker = Executors.defaultThreadFactory().newThread(this);
worker.setDaemon(true); //JDK-8060132
worker.setContextClassLoader(getClass().getClassLoader());
worker.start();
}
return worker;
}
}

LogRecord中有建议文档甚至是 original authors fail to followMemoryHandler 。其内容如下:

Therefore, if a logging Handler wants to pass off a LogRecord to another thread, or to transmit it over RMI, and if it wishes to subsequently obtain method name or class name information it should call one of getSourceClassName or getSourceMethodName to force the values to be filled in.

因此,如果要在队列中缓冲 LogRecords,则必须在将记录添加到队列之前调用 getSourceClassName 或 getSourceMethodName。否则你的日志将记录错误的源类和源方法名称。

关于java - 使用带有单独线程的 java.util.logger 来写入文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27406632/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com