| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.juli; |
| |
| import java.util.concurrent.LinkedBlockingDeque; |
| import java.util.concurrent.RejectedExecutionException; |
| import java.util.concurrent.RejectedExecutionHandler; |
| import java.util.concurrent.ThreadPoolExecutor; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.logging.LogRecord; |
| |
| /** |
| * A {@link FileHandler} implementation that uses a queue of log entries. |
| * <p> |
| * Configuration properties are inherited from the {@link FileHandler} class. This class does not add its own |
| * configuration properties for the logging configuration, but relies on the following system properties instead: |
| * </p> |
| * <ul> |
| * <li><code>org.apache.juli.AsyncOverflowDropType</code> Default value: <code>1</code></li> |
| * <li><code>org.apache.juli.AsyncMaxRecordCount</code> Default value: <code>10000</code></li> |
| * </ul> |
| * <p> |
| * See the System Properties page in the configuration reference of Tomcat. |
| * </p> |
| */ |
| public class AsyncFileHandler extends FileHandler { |
| |
| static final String THREAD_PREFIX = "AsyncFileHandlerWriter-"; |
| |
| public static final int OVERFLOW_DROP_LAST = 1; |
| public static final int OVERFLOW_DROP_FIRST = 2; |
| public static final int OVERFLOW_DROP_FLUSH = 3; |
| public static final int OVERFLOW_DROP_CURRENT = 4; |
| |
| public static final int DEFAULT_OVERFLOW_DROP_TYPE = 1; |
| public static final int DEFAULT_MAX_RECORDS = 10000; |
| |
| public static final int OVERFLOW_DROP_TYPE = Integer.parseInt( |
| System.getProperty("org.apache.juli.AsyncOverflowDropType", Integer.toString(DEFAULT_OVERFLOW_DROP_TYPE))); |
| public static final int MAX_RECORDS = Integer |
| .parseInt(System.getProperty("org.apache.juli.AsyncMaxRecordCount", Integer.toString(DEFAULT_MAX_RECORDS))); |
| |
| private static final LoggerExecutorService LOGGER_SERVICE = new LoggerExecutorService(OVERFLOW_DROP_TYPE, |
| MAX_RECORDS); |
| |
| private final Object closeLock = new Object(); |
| protected volatile boolean closed = false; |
| private final LoggerExecutorService loggerService; |
| |
| public AsyncFileHandler() { |
| this(null, null, null, DEFAULT_MAX_DAYS); |
| } |
| |
| public AsyncFileHandler(String directory, String prefix, String suffix) { |
| this(directory, prefix, suffix, DEFAULT_MAX_DAYS); |
| } |
| |
| public AsyncFileHandler(String directory, String prefix, String suffix, int maxDays) { |
| this(directory, prefix, suffix, maxDays, LOGGER_SERVICE); |
| } |
| |
| AsyncFileHandler(String directory, String prefix, String suffix, int maxDays, LoggerExecutorService loggerService) { |
| super(directory, prefix, suffix, maxDays); |
| this.loggerService = loggerService; |
| open(); |
| } |
| |
| @Override |
| public void close() { |
| if (closed) { |
| return; |
| } |
| synchronized (closeLock) { |
| if (closed) { |
| return; |
| } |
| closed = true; |
| } |
| loggerService.deregisterHandler(); |
| super.close(); |
| } |
| |
| @Override |
| protected void open() { |
| if (!closed) { |
| return; |
| } |
| synchronized (closeLock) { |
| if (!closed) { |
| return; |
| } |
| closed = false; |
| } |
| loggerService.registerHandler(); |
| super.open(); |
| } |
| |
| @Override |
| public void publish(final LogRecord record) { |
| if (!isLoggable(record)) { |
| return; |
| } |
| // fill source entries, before we hand the record over to another |
| // thread with another class loader |
| record.getSourceMethodName(); |
| loggerService.execute(new Runnable() { |
| |
| @Override |
| public void run() { |
| /* |
| * During Tomcat shutdown, the Handlers are closed before the executor queue is flushed therefore the |
| * closed flag is ignored if the executor is shutting down. |
| */ |
| if (!closed || loggerService.isTerminating()) { |
| publishInternal(record); |
| } |
| } |
| }); |
| } |
| |
| protected void publishInternal(LogRecord record) { |
| super.publish(record); |
| } |
| |
| |
| static class LoggerExecutorService extends ThreadPoolExecutor { |
| |
| private static final ThreadFactory THREAD_FACTORY = new ThreadFactory(THREAD_PREFIX); |
| |
| /* |
| * Implementation note: Use of this count could be extended to start/stop the LoggerExecutorService but that |
| * would require careful locking as the current size of the queue also needs to be taken into account and there |
| * are lost of edge cases when rapidly starting and stopping handlers. |
| */ |
| private final AtomicInteger handlerCount = new AtomicInteger(); |
| |
| LoggerExecutorService(final int overflowDropType, final int maxRecords) { |
| super(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>(maxRecords), THREAD_FACTORY); |
| switch (overflowDropType) { |
| case OVERFLOW_DROP_LAST: |
| default: |
| setRejectedExecutionHandler(new DropLastPolicy()); |
| break; |
| case OVERFLOW_DROP_FIRST: |
| setRejectedExecutionHandler(new DiscardOldestPolicy()); |
| break; |
| case OVERFLOW_DROP_FLUSH: |
| setRejectedExecutionHandler(new DropFlushPolicy()); |
| break; |
| case OVERFLOW_DROP_CURRENT: |
| setRejectedExecutionHandler(new DiscardPolicy()); |
| } |
| } |
| |
| @Override |
| public LinkedBlockingDeque<Runnable> getQueue() { |
| return (LinkedBlockingDeque<Runnable>) super.getQueue(); |
| } |
| |
| public void registerHandler() { |
| handlerCount.incrementAndGet(); |
| } |
| |
| public void deregisterHandler() { |
| int newCount = handlerCount.decrementAndGet(); |
| if (newCount == 0) { |
| try { |
| Thread dummyHook = new Thread(); |
| Runtime.getRuntime().addShutdownHook(dummyHook); |
| Runtime.getRuntime().removeShutdownHook(dummyHook); |
| } catch (IllegalStateException ise) { |
| // JVM is shutting down. |
| // Allow up to 10s for for the queue to be emptied |
| shutdown(); |
| try { |
| awaitTermination(10, TimeUnit.SECONDS); |
| } catch (InterruptedException e) { |
| // Ignore |
| } |
| shutdownNow(); |
| } |
| } |
| } |
| } |
| |
| |
| private static class DropFlushPolicy implements RejectedExecutionHandler { |
| |
| @Override |
| public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { |
| while (true) { |
| if (executor.isShutdown()) { |
| break; |
| } |
| try { |
| if (executor.getQueue().offer(r, 1000, TimeUnit.MILLISECONDS)) { |
| break; |
| } |
| } catch (InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| throw new RejectedExecutionException("Interrupted", e); |
| } |
| } |
| } |
| } |
| |
| private static class DropLastPolicy implements RejectedExecutionHandler { |
| |
| @Override |
| public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { |
| if (!executor.isShutdown()) { |
| ((LoggerExecutorService) executor).getQueue().pollLast(); |
| executor.execute(r); |
| } |
| } |
| } |
| } |