我有2个FileHandler写入两个单独的文件,发生的I / O量正在减慢我的应用程序:
我决定让FileHandler在不同的线程上运行。
由于它们位于不同的线程上,我需要一个"队列"的概念,以便这些单独的线程可以轮询此队列并打印出任何传入的消息。
我预先格式化了消息,以便在实际到达FileHandler中的打印输出之前,所使用的任何参数都不会更改。
现在我意识到我无法使用" log"记录器提供的方法,因为它试图调用当前线程上的方法来格式化并打印出消息。
所以我只是调用一个方法将跟踪消息添加到队列中。
然后我使用FileHandlers的run()方法使用publish()打印出消息。
我现在意识到,publish()只接受一个LogRecord,这只是一个关卡+消息。
我的痕迹还有很多,不能简单地放在一个整体信息中,我希望能够使用"格式化程序"我已经设置了FileHandler。
所以我在FileHandler中创建了一个logger实例,所以我可以使用log方法并按照Formatter中的设计格式化字符串。
哪个有用,有点。
...
这有点傻了,值得继续这样,在java.util.Logger上工作而不是使用它吗? java.util.Logger的一个有用部分是为每个类提供一个单独的记录器实例,并且能够对消息进行更多控制......
有什么建议吗?
代码很长,但我认为从上面的描述可以很容易理解,如果不让我知道,我会上传到某个地方。
答案 0 :(得分:5)
如果I / O确实是瓶颈并且您不需要文件轮换和文件锁定,那么创建一个Handler,它将完全格式化的输出字符串/ bytebuffer从您的LogRecord +“跟踪消息”排队。然后将完全格式化的输出字符串/ bytebuffer移交/排队到线程以执行I / O.
否则,如果您需要使用FileHandler并希望将LogRecord +跟踪传递给publish方法,则可以将FileHandler子类化,然后在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;
}
}
LogRecord文档中有建议,甚至是original authors fail to follow中的MemoryHandler。它如下所示:
因此,如果日志记录处理程序想要将LogRecord传递给另一个线程,或者通过RMI传输它,并且如果它希望随后获取方法名称或类名信息,则它应该调用getSourceClassName或getSourceMethodName中的一个来强制要填写的值。
因此,如果要在队列中缓冲LogRecords,则必须在将记录添加到队列之前调用getSourceClassName或getSourceMethodName。否则,您的日志将记录错误的源类和源方法名称。
答案 1 :(得分:2)
你应该考虑使用SLF4J。
您描述的这些问题伴随着滚动您自己的记录器。 SLF4J是非常常用的,因为它不是非常具有侵入性,如果你决定需要不同的东西,你可以将它换成不同的框架。
http://saltnlight5.blogspot.ca/2013/08/how-to-configure-slf4j-with-different.html
http://www.slf4j.org/manual.html
如果您决定坚持使用自己的记录器,我会说你应该首先将它作为一个单独的几个logwriters连接到日志级别,然后为每个记录器设置一个非阻塞队列(有很多例子)然后只需调用log(logLevel,logOrigin,logMessage)并将其发送到每个logwriter,这将是一个在每个logwriter的线程上运行的非阻塞队列。
每个记录器应该是它自己的线程,而不是每个日志,而且您只需要一个记录器,因为它只是一个简单的东西,可以在应用程序的任何位置将东西放入您的记录器队列中。
答案 2 :(得分:1)
您可以使用log4j 2 JDK Logging Adapter启用log4j的日志记录,而log4j 2提供了非凡的异步日志记录机制,并提供了大量配置选项。
必要的虚拟机参数
x
您可以找到有关log4j 2 async logging here和tomcat配置详细信息here
的更多信息async loggign的示例Log4j2.xml
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector