使用带有单独线程的java.util.logger在文件上写入? [英] Using java.util.logger with a separate thread to write on file?

查看:68
本文介绍了使用带有单独线程的java.util.logger在文件上写入?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有2个FileHandler分别写出两个单独的文件,并且发生的I/O数量使我的应用程序速度大大降低:

I have 2 FileHandlers that write out to two separate files, and the amount of I/O occurring is slowing down my application quite a bit:

  1. 我已经决定让FileHandlers在单独的线程上运行.

  1. I've decided to have the FileHandlers run on separate threads.

由于它们位于单独的线程中,因此我需要一个队列"概念,以便这些单独的线程可以轮询此队列并打印出所有传入消息.

As they are on separate threads, I needed a concept of a "queue", so that these separate threads can poll this queue and print out any incoming messages.

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

I've preformatted messages so that any arguments used do not change before they actually reach the print out in the FileHandler.

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

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.

因此,我仅调用一个将跟踪消息添加到队列中的方法.

So I simply call a method that adds my trace message to the queue.

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

I then use run() method of the FileHandlers to print out the messages using publish().

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

I now realise publish() only takes in a LogRecord, which is just a level + message.

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

My traces have much more, which can't simply be placed in an overall message, I want to be able to use the "Formatter" that I've set to the FileHandler.

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

So I create an instance of the logger in the FileHandler, so I can use the log method and format the string as designed in the Formatter.

有点用,

...

这变得有点愚蠢了,值得继续这样,围绕java.util.Logger工作而不是使用它吗? java.util.Logger的有用部分之一是每个类都有一个单独的logger实例,并且能够更好地控制消息...

This is getting a bit silly, is it worth continuing on like this, working AROUND the java.util.Logger rather than working with it? One of the useful parts of the java.util.Logger is having a separate logger instance for each class and being able to have more control over the messages...

有什么建议吗?

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

The code is quite long, but I think it's easily understood from the description above, if not let me know and I'll upload somewhere.

推荐答案

如果I/O确实是瓶颈,并且您不需要文件旋转和文件锁定,则创建一个

If I/O is truly the bottleneck and you don't need file rotation and file locking then create a Handler that queues the fully formatted output string/bytebuffer from your LogRecord + "trace message". Then hand off/queue the fully formatted output string/bytebuffer to a thread to perform the I/O.

否则,如果您需要使用 FileHandler 并想将LogRecord +您的跟踪传递给publish方法,您可以将FileHandler子类化,然后在LogRecord和跟踪之间创建自定义格式器可见的映射.做到这一点的几种方法是:

Otherwise, if you need to use the FileHandler and want to pass a LogRecord + your trace to the publish method you can just subclass the FileHandler and then create a mapping between your LogRecord and trace that is visible to your custom formatter. A few ways to do that are:

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

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

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排队的处理程序.

Logger.log creates LogRecords and invokes handler.publish for the attached handlers and parent handlers by default. It is handler.publish that is performing the I/O on the current thread. What you have to do is remove all handlers that perform I/O on publish and replace them with handlers that just queue LogRecords on publish.

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

Here is an example of how to create an 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;
        }
    }

LogRecord 中有建议a>甚至原始作者也无法遵循的文档 "http://docs.oracle.com/javase/8/docs/api/java/util/logging/MemoryHandler.html" rel ="nofollow"> MemoryHandler .内容如下:

There is advice in the LogRecord documentation which even the original authors fail to follow in the MemoryHandler. It reads as the following:

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

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,然后再将记录添加到队列中.否则,您的日志将记录错误的源类和源方法名称.

So if you are going to buffer LogRecords in a queue you have to call getSourceClassName or getSourceMethodName before you add the records to the queue. Otherwise your log will record the wrong source class and source method names.

这篇关于使用带有单独线程的java.util.logger在文件上写入?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆