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

sro*_*sro 2 java logging

我有2个FileHandler写出两个单独的文件,发生的I/O量正在减慢我的应用程序:

  1. 我决定让FileHandler在不同的线程上运行.

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

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

  4. 现在我意识到我不能使用记录器提供的"log"方法,因为它试图调用当前线程上的方法来格式化和打印出消息.

  5. 所以我只是调用一个方法将跟踪消息添加到队列中.

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

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

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

  9. 所以我在FileHandler中创建了一个logger实例,所以我可以使用log方法并按照Formatter中的设计格式化字符串.

哪个有用,有点儿.

...

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

有什么建议?

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

jme*_*ens 5

如果I/O确实是瓶颈并且您不需要文件轮换和文件锁定,那么创建一个Handler,它将完全格式化的输出字符串/ bytebuffer排入LogRecord +"trace message".然后将完全格式化的输出字符串/ bytebuffer移交/排队到线程以执行I/O.

否则,如果您需要使用FileHandler并希望将LogRecord +跟踪传递给publish方法,则可以将FileHandler子类化,然后在LogRecord和跟踪之间创建自定义格式化程序可见的映射.有几种方法可以:

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

4.现在我已经意识到我不能使用记录器提供的"log"方法,因为它试图调用当前线程上的方法来格式化并打印出消息.

Logger.log默认创建LogRecords并为附加的处理程序和父处理程序调用handler.publish.正是在当前线程上执行I/O的handler.publish.您需要做的是删除在发布时执行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);
                worker.setContextClassLoader(getClass().getClassLoader());
                worker.start();
            }
            return worker;
        }
    }
Run Code Online (Sandbox Code Playgroud)

LogRecord文档中提供了一些建议,即使原始作者也无法MemoryHandler中进行操作.它读作如下:

因此,如果日志记录处理程序想要将LogRecord传递给另一个线程,或者通过RMI传输它,并且如果它希望随后获取方法名称或类名称信息,则它应该调用getSourceClassName或getSourceMethodName之一来强制值为填写.

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