| /* |
| * 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. |
| */ |
| |
| // Contibutors: Aaron Greenhouse <aarong@cs.cmu.edu> |
| // Thomas Tuft Muller <ttm@online.no> |
| package org.apache.log4j; |
| |
| import org.apache.log4j.helpers.AppenderAttachableImpl; |
| import org.apache.log4j.spi.AppenderAttachable; |
| import org.apache.log4j.spi.LoggingEvent; |
| |
| import java.text.MessageFormat; |
| |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| |
| /** |
| * The AsyncAppender lets users log events asynchronously. |
| * <p/> |
| * <p/> |
| * The AsyncAppender will collect the events sent to it and then dispatch them |
| * to all the appenders that are attached to it. You can attach multiple |
| * appenders to an AsyncAppender. |
| * </p> |
| * <p/> |
| * <p/> |
| * The AsyncAppender uses a separate thread to serve the events in its buffer. |
| * </p> |
| * <p/> |
| * <b>Important note:</b> The <code>AsyncAppender</code> can only be script |
| * configured using the {@link org.apache.log4j.xml.DOMConfigurator}. |
| * </p> |
| * |
| * @author Ceki Gülcü |
| * @author Curt Arnold |
| * @since 0.9.1 |
| */ |
| public class AsyncAppender extends AppenderSkeleton |
| implements AppenderAttachable { |
| /** |
| * The default buffer size is set to 128 events. |
| */ |
| public static final int DEFAULT_BUFFER_SIZE = 128; |
| |
| /** |
| * Event buffer, also used as monitor to protect itself and |
| * discardMap from simulatenous modifications. |
| */ |
| private final List buffer = new ArrayList(); |
| |
| /** |
| * Map of DiscardSummary objects keyed by logger name. |
| */ |
| private final Map discardMap = new HashMap(); |
| |
| /** |
| * Buffer size. |
| */ |
| private int bufferSize = DEFAULT_BUFFER_SIZE; |
| |
| /** Nested appenders. */ |
| AppenderAttachableImpl aai; |
| |
| /** |
| * Nested appenders. |
| */ |
| private final AppenderAttachableImpl appenders; |
| |
| /** |
| * Dispatcher. |
| */ |
| private final Thread dispatcher; |
| |
| /** |
| * Should location info be included in dispatched messages. |
| */ |
| private boolean locationInfo = false; |
| |
| /** |
| * Does appender block when buffer is full. |
| */ |
| private boolean blocking = true; |
| |
| /** |
| * Create new instance. |
| */ |
| public AsyncAppender() { |
| appenders = new AppenderAttachableImpl(); |
| |
| // |
| // only set for compatibility |
| aai = appenders; |
| |
| dispatcher = |
| new Thread(new Dispatcher(this, buffer, discardMap, appenders)); |
| |
| // It is the user's responsibility to close appenders before |
| // exiting. |
| dispatcher.setDaemon(true); |
| |
| // set the dispatcher priority to lowest possible value |
| // dispatcher.setPriority(Thread.MIN_PRIORITY); |
| dispatcher.setName("Dispatcher-" + dispatcher.getName()); |
| dispatcher.start(); |
| } |
| |
| /** |
| * Add appender. |
| * |
| * @param newAppender appender to add, may not be null. |
| */ |
| public void addAppender(final Appender newAppender) { |
| synchronized (appenders) { |
| appenders.addAppender(newAppender); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void append(final LoggingEvent event) { |
| // |
| // if dispatcher thread has died then |
| // append subsequent events synchronously |
| // See bug 23021 |
| if ((dispatcher == null) || !dispatcher.isAlive() || (bufferSize <= 0)) { |
| synchronized (appenders) { |
| appenders.appendLoopOnAppenders(event); |
| } |
| |
| return; |
| } |
| |
| // Set the NDC and thread name for the calling thread as these |
| // LoggingEvent fields were not set at event creation time. |
| event.getNDC(); |
| event.getThreadName(); |
| // Get a copy of this thread's MDC. |
| event.getMDCCopy(); |
| if (locationInfo) { |
| event.getLocationInformation(); |
| } |
| event.getRenderedMessage(); |
| |
| synchronized (buffer) { |
| while (true) { |
| int previousSize = buffer.size(); |
| |
| if (previousSize < bufferSize) { |
| buffer.add(event); |
| |
| // |
| // if buffer had been empty |
| // signal all threads waiting on buffer |
| // to check their conditions. |
| // |
| if (previousSize == 0) { |
| buffer.notifyAll(); |
| } |
| |
| break; |
| } |
| |
| // |
| // Following code is only reachable if buffer is full |
| // |
| // |
| // if blocking and thread is not already interrupted |
| // and not the dispatcher then |
| // wait for a buffer notification |
| boolean discard = true; |
| if (blocking |
| && !Thread.interrupted() |
| && Thread.currentThread() != dispatcher) { |
| try { |
| buffer.wait(); |
| discard = false; |
| } catch (InterruptedException e) { |
| // |
| // reset interrupt status so |
| // calling code can see interrupt on |
| // their next wait or sleep. |
| Thread.currentThread().interrupt(); |
| } |
| } |
| |
| // |
| // if blocking is false or thread has been interrupted |
| // add event to discard map. |
| // |
| if (discard) { |
| String loggerName = event.getLoggerName(); |
| DiscardSummary summary = (DiscardSummary) discardMap.get(loggerName); |
| |
| if (summary == null) { |
| summary = new DiscardSummary(event); |
| discardMap.put(loggerName, summary); |
| } else { |
| summary.add(event); |
| } |
| |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Close this <code>AsyncAppender</code> by interrupting the dispatcher |
| * thread which will process all pending events before exiting. |
| */ |
| public void close() { |
| /** |
| * Set closed flag and notify all threads to check their conditions. |
| * Should result in dispatcher terminating. |
| */ |
| synchronized (buffer) { |
| closed = true; |
| buffer.notifyAll(); |
| } |
| |
| try { |
| dispatcher.join(); |
| } catch (InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| org.apache.log4j.helpers.LogLog.error( |
| "Got an InterruptedException while waiting for the " |
| + "dispatcher to finish.", e); |
| } |
| |
| // |
| // close all attached appenders. |
| // |
| synchronized (appenders) { |
| Enumeration iter = appenders.getAllAppenders(); |
| |
| if (iter != null) { |
| while (iter.hasMoreElements()) { |
| Object next = iter.nextElement(); |
| |
| if (next instanceof Appender) { |
| ((Appender) next).close(); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Get iterator over attached appenders. |
| * @return iterator or null if no attached appenders. |
| */ |
| public Enumeration getAllAppenders() { |
| synchronized (appenders) { |
| return appenders.getAllAppenders(); |
| } |
| } |
| |
| /** |
| * Get appender by name. |
| * |
| * @param name name, may not be null. |
| * @return matching appender or null. |
| */ |
| public Appender getAppender(final String name) { |
| synchronized (appenders) { |
| return appenders.getAppender(name); |
| } |
| } |
| |
| /** |
| * Gets whether the location of the logging request call |
| * should be captured. |
| * |
| * @return the current value of the <b>LocationInfo</b> option. |
| */ |
| public boolean getLocationInfo() { |
| return locationInfo; |
| } |
| |
| /** |
| * Determines if specified appender is attached. |
| * @param appender appender. |
| * @return true if attached. |
| */ |
| public boolean isAttached(final Appender appender) { |
| synchronized (appenders) { |
| return appenders.isAttached(appender); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public boolean requiresLayout() { |
| return false; |
| } |
| |
| /** |
| * Removes and closes all attached appenders. |
| */ |
| public void removeAllAppenders() { |
| synchronized (appenders) { |
| appenders.removeAllAppenders(); |
| } |
| } |
| |
| /** |
| * Removes an appender. |
| * @param appender appender to remove. |
| */ |
| public void removeAppender(final Appender appender) { |
| synchronized (appenders) { |
| appenders.removeAppender(appender); |
| } |
| } |
| |
| /** |
| * Remove appender by name. |
| * @param name name. |
| */ |
| public void removeAppender(final String name) { |
| synchronized (appenders) { |
| appenders.removeAppender(name); |
| } |
| } |
| |
| /** |
| * The <b>LocationInfo</b> option takes a boolean value. By default, it is |
| * set to false which means there will be no effort to extract the location |
| * information related to the event. As a result, the event that will be |
| * ultimately logged will likely to contain the wrong location information |
| * (if present in the log format). |
| * <p/> |
| * <p/> |
| * Location information extraction is comparatively very slow and should be |
| * avoided unless performance is not a concern. |
| * </p> |
| * @param flag true if location information should be extracted. |
| */ |
| public void setLocationInfo(final boolean flag) { |
| locationInfo = flag; |
| } |
| |
| /** |
| * Sets the number of messages allowed in the event buffer |
| * before the calling thread is blocked (if blocking is true) |
| * or until messages are summarized and discarded. Changing |
| * the size will not affect messages already in the buffer. |
| * |
| * @param size buffer size, must be positive. |
| */ |
| public void setBufferSize(final int size) { |
| // |
| // log4j 1.2 would throw exception if size was negative |
| // and deadlock if size was zero. |
| // |
| if (size < 0) { |
| throw new java.lang.NegativeArraySizeException("size"); |
| } |
| |
| synchronized (buffer) { |
| // |
| // don't let size be zero. |
| // |
| bufferSize = (size < 1) ? 1 : size; |
| buffer.notifyAll(); |
| } |
| } |
| |
| /** |
| * Gets the current buffer size. |
| * @return the current value of the <b>BufferSize</b> option. |
| */ |
| public int getBufferSize() { |
| return bufferSize; |
| } |
| |
| /** |
| * Sets whether appender should wait if there is no |
| * space available in the event buffer or immediately return. |
| * |
| * @since 1.2.14 |
| * @param value true if appender should wait until available space in buffer. |
| */ |
| public void setBlocking(final boolean value) { |
| synchronized (buffer) { |
| blocking = value; |
| buffer.notifyAll(); |
| } |
| } |
| |
| /** |
| * Gets whether appender should block calling thread when buffer is full. |
| * If false, messages will be counted by logger and a summary |
| * message appended after the contents of the buffer have been appended. |
| * |
| * @since 1.2.14 |
| * @return true if calling thread will be blocked when buffer is full. |
| */ |
| public boolean getBlocking() { |
| return blocking; |
| } |
| |
| /** |
| * Summary of discarded logging events for a logger. |
| */ |
| private static final class DiscardSummary { |
| /** |
| * First event of the highest severity. |
| */ |
| private LoggingEvent maxEvent; |
| |
| /** |
| * Total count of messages discarded. |
| */ |
| private int count; |
| |
| /** |
| * Create new instance. |
| * |
| * @param event event, may not be null. |
| */ |
| public DiscardSummary(final LoggingEvent event) { |
| maxEvent = event; |
| count = 1; |
| } |
| |
| /** |
| * Add discarded event to summary. |
| * |
| * @param event event, may not be null. |
| */ |
| public void add(final LoggingEvent event) { |
| if (event.getLevel().toInt() > maxEvent.getLevel().toInt()) { |
| maxEvent = event; |
| } |
| |
| count++; |
| } |
| |
| /** |
| * Create event with summary information. |
| * |
| * @return new event. |
| */ |
| public LoggingEvent createEvent() { |
| String msg = |
| MessageFormat.format( |
| "Discarded {0} messages due to full event buffer including: {1}", |
| new Object[] { new Integer(count), maxEvent.getMessage() }); |
| |
| return new LoggingEvent( |
| "org.apache.log4j.AsyncAppender.DONT_REPORT_LOCATION", |
| Logger.getLogger(maxEvent.getLoggerName()), |
| maxEvent.getLevel(), |
| msg, |
| null); |
| } |
| } |
| |
| /** |
| * Event dispatcher. |
| */ |
| private static class Dispatcher implements Runnable { |
| /** |
| * Parent AsyncAppender. |
| */ |
| private final AsyncAppender parent; |
| |
| /** |
| * Event buffer. |
| */ |
| private final List buffer; |
| |
| /** |
| * Map of DiscardSummary keyed by logger name. |
| */ |
| private final Map discardMap; |
| |
| /** |
| * Wrapped appenders. |
| */ |
| private final AppenderAttachableImpl appenders; |
| |
| /** |
| * Create new instance of dispatcher. |
| * |
| * @param parent parent AsyncAppender, may not be null. |
| * @param buffer event buffer, may not be null. |
| * @param discardMap discard map, may not be null. |
| * @param appenders appenders, may not be null. |
| */ |
| public Dispatcher( |
| final AsyncAppender parent, final List buffer, final Map discardMap, |
| final AppenderAttachableImpl appenders) { |
| |
| this.parent = parent; |
| this.buffer = buffer; |
| this.appenders = appenders; |
| this.discardMap = discardMap; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void run() { |
| boolean isActive = true; |
| |
| // |
| // if interrupted (unlikely), end thread |
| // |
| try { |
| // |
| // loop until the AsyncAppender is closed. |
| // |
| while (isActive) { |
| LoggingEvent[] events = null; |
| |
| // |
| // extract pending events while synchronized |
| // on buffer |
| // |
| synchronized (buffer) { |
| int bufferSize = buffer.size(); |
| isActive = !parent.closed; |
| |
| while ((bufferSize == 0) && isActive) { |
| buffer.wait(); |
| bufferSize = buffer.size(); |
| isActive = !parent.closed; |
| } |
| |
| if (bufferSize > 0) { |
| events = new LoggingEvent[bufferSize + discardMap.size()]; |
| buffer.toArray(events); |
| |
| // |
| // add events due to buffer overflow |
| // |
| int index = bufferSize; |
| |
| for ( |
| Iterator iter = discardMap.values().iterator(); |
| iter.hasNext();) { |
| events[index++] = ((DiscardSummary) iter.next()).createEvent(); |
| } |
| |
| // |
| // clear buffer and discard map |
| // |
| buffer.clear(); |
| discardMap.clear(); |
| |
| // |
| // allow blocked appends to continue |
| buffer.notifyAll(); |
| } |
| } |
| |
| // |
| // process events after lock on buffer is released. |
| // |
| if (events != null) { |
| for (int i = 0; i < events.length; i++) { |
| synchronized (appenders) { |
| appenders.appendLoopOnAppenders(events[i]); |
| } |
| } |
| } |
| } |
| } catch (InterruptedException ex) { |
| Thread.currentThread().interrupt(); |
| } |
| } |
| } |
| } |