我有2个FileHandlers可以写出两个单独的文件,并且发生的I / O数量使我的应用程序速度大大降低:
哪个可行,有点。
...
这有点愚蠢,值得继续这样,围绕java.util.Logger而不是与其一起工作吗? java.util.Logger的有用部分之一是每个类都有一个单独的记录器实例,并且能够更好地控制消息...
有什么建议?
代码很长,但是我想从上面的描述中很容易理解,如果不告诉我,我会上传到某个地方。
最佳答案
如果I / O确实是瓶颈,并且您不需要文件旋转和文件锁定,则创建一个Handler,将LOGRecord +“跟踪消息”中的全格式输出字符串/字节缓冲区排队。然后将完全格式化的输出字符串/字节缓冲区移交/排队到线程中以执行I / O。
否则,如果您需要使用FileHandler并想将LogRecord +跟踪传递给publish方法,则可以将FileHandler子类化,然后在LogRecord和跟踪之间创建自定义格式器可见的映射。有几种方法可以做到:
4.现在,我意识到我无法使用记录器提供的“log”方法,因为它试图在当前线程上调用方法来格式化和打印消息。
默认情况下,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);
worker.setContextClassLoader(getClass().getClassLoader());
worker.start();
}
return worker;
}
}
LogRecord文档中有建议,甚至original authors fail to follow中也有MemoryHandler。内容如下:
因此,如果日志记录处理程序想要将LogRecord传递给另一个线程,或通过RMI传输它,并且如果希望随后获取方法名称或类名称信息,则应调用getSourceClassName或getSourceMethodName中的一个来强制将值设置为填写。
因此,如果要将LogRecords缓冲在队列中,则必须在将记录添加到队列之前调用getSourceClassName或getSourceMethodName。否则,您的日志将记录错误的源类和源方法名称。