1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 // Contibutors: Aaron Greenhouse <aarong@cs.cmu.edu> 19 // Thomas Tuft Muller <ttm@online.no> 20 package org.apache.log4j; 21 22 import java.text.MessageFormat; 23 import java.util.ArrayList; 24 import java.util.Enumeration; 25 import java.util.HashMap; 26 import java.util.Iterator; 27 import java.util.List; 28 import java.util.Map; 29 30 import org.apache.log4j.helpers.AppenderAttachableImpl; 31 import org.apache.log4j.spi.AppenderAttachable; 32 import org.apache.log4j.spi.LoggingEvent; 33 34 35 /** 36 * The AsyncAppender lets users log events asynchronously. 37 * <p/> 38 * <p/> 39 * The AsyncAppender will collect the events sent to it and then dispatch them 40 * to all the appenders that are attached to it. You can attach multiple 41 * appenders to an AsyncAppender. 42 * </p> 43 * <p/> 44 * <p/> 45 * The AsyncAppender uses a separate thread to serve the events in its buffer. 46 * </p> 47 * <p/> 48 * <b>Important note:</b> The <code>AsyncAppender</code> can only be script 49 * configured using the {@link org.apache.log4j.xml.DOMConfigurator}. 50 * </p> 51 * 52 * @author Ceki Gülcü 53 * @author Curt Arnold 54 * @since 0.9.1 55 */ 56 public class AsyncAppender extends AppenderSkeleton 57 implements AppenderAttachable { 58 /** 59 * The default buffer size is set to 128 events. 60 */ 61 public static final int DEFAULT_BUFFER_SIZE = 128; 62 63 /** 64 * Event buffer, also used as monitor to protect itself and 65 * discardMap from simulatenous modifications. 66 */ 67 private final List buffer = new ArrayList(); 68 69 /** 70 * Map of DiscardSummary objects keyed by logger name. 71 */ 72 private final Map discardMap = new HashMap(); 73 74 /** 75 * Buffer size. 76 */ 77 private int bufferSize = DEFAULT_BUFFER_SIZE; 78 79 /** Nested appenders. */ 80 AppenderAttachableImpl aai; 81 82 /** 83 * Nested appenders. 84 */ 85 private final AppenderAttachableImpl appenders; 86 87 /** 88 * Dispatcher. 89 */ 90 private final Thread dispatcher; 91 92 /** 93 * Should location info be included in dispatched messages. 94 */ 95 private boolean locationInfo = false; 96 97 /** 98 * Does appender block when buffer is full. 99 */ 100 private boolean blocking = true; 101 102 /** 103 * Create new instance. 104 */ AsyncAppender()105 public AsyncAppender() { 106 appenders = new AppenderAttachableImpl(); 107 108 // 109 // only set for compatibility 110 aai = appenders; 111 112 dispatcher = 113 new Thread(new Dispatcher(this, buffer, discardMap, appenders)); 114 115 // It is the user's responsibility to close appenders before 116 // exiting. 117 dispatcher.setDaemon(true); 118 119 // set the dispatcher priority to lowest possible value 120 // dispatcher.setPriority(Thread.MIN_PRIORITY); 121 dispatcher.setName("AsyncAppender-Dispatcher-" + dispatcher.getName()); 122 dispatcher.start(); 123 } 124 125 /** 126 * Add appender. 127 * 128 * @param newAppender appender to add, may not be null. 129 */ addAppender(final Appender newAppender)130 public void addAppender(final Appender newAppender) { 131 synchronized (appenders) { 132 appenders.addAppender(newAppender); 133 } 134 } 135 136 /** 137 * {@inheritDoc} 138 */ append(final LoggingEvent event)139 public void append(final LoggingEvent event) { 140 // 141 // if dispatcher thread has died then 142 // append subsequent events synchronously 143 // See bug 23021 144 if ((dispatcher == null) || !dispatcher.isAlive() || (bufferSize <= 0)) { 145 synchronized (appenders) { 146 appenders.appendLoopOnAppenders(event); 147 } 148 149 return; 150 } 151 152 // Set the NDC and thread name for the calling thread as these 153 // LoggingEvent fields were not set at event creation time. 154 event.getNDC(); 155 event.getThreadName(); 156 // Get a copy of this thread's MDC. 157 event.getMDCCopy(); 158 if (locationInfo) { 159 event.getLocationInformation(); 160 } 161 event.getRenderedMessage(); 162 event.getThrowableStrRep(); 163 164 synchronized (buffer) { 165 while (true) { 166 int previousSize = buffer.size(); 167 168 if (previousSize < bufferSize) { 169 buffer.add(event); 170 171 // 172 // if buffer had been empty 173 // signal all threads waiting on buffer 174 // to check their conditions. 175 // 176 if (previousSize == 0) { 177 buffer.notifyAll(); 178 } 179 180 break; 181 } 182 183 // 184 // Following code is only reachable if buffer is full 185 // 186 // 187 // if blocking and thread is not already interrupted 188 // and not the dispatcher then 189 // wait for a buffer notification 190 boolean discard = true; 191 if (blocking 192 && !Thread.interrupted() 193 && Thread.currentThread() != dispatcher) { 194 try { 195 buffer.wait(); 196 discard = false; 197 } catch (InterruptedException e) { 198 // 199 // reset interrupt status so 200 // calling code can see interrupt on 201 // their next wait or sleep. 202 Thread.currentThread().interrupt(); 203 } 204 } 205 206 // 207 // if blocking is false or thread has been interrupted 208 // add event to discard map. 209 // 210 if (discard) { 211 String loggerName = event.getLoggerName(); 212 DiscardSummary summary = (DiscardSummary) discardMap.get(loggerName); 213 214 if (summary == null) { 215 summary = new DiscardSummary(event); 216 discardMap.put(loggerName, summary); 217 } else { 218 summary.add(event); 219 } 220 221 break; 222 } 223 } 224 } 225 } 226 227 /** 228 * Close this <code>AsyncAppender</code> by interrupting the dispatcher 229 * thread which will process all pending events before exiting. 230 */ close()231 public void close() { 232 /** 233 * Set closed flag and notify all threads to check their conditions. 234 * Should result in dispatcher terminating. 235 */ 236 synchronized (buffer) { 237 closed = true; 238 buffer.notifyAll(); 239 } 240 241 try { 242 dispatcher.join(); 243 } catch (InterruptedException e) { 244 Thread.currentThread().interrupt(); 245 org.apache.log4j.helpers.LogLog.error( 246 "Got an InterruptedException while waiting for the " 247 + "dispatcher to finish.", e); 248 } 249 250 // 251 // close all attached appenders. 252 // 253 synchronized (appenders) { 254 Enumeration iter = appenders.getAllAppenders(); 255 256 if (iter != null) { 257 while (iter.hasMoreElements()) { 258 Object next = iter.nextElement(); 259 260 if (next instanceof Appender) { 261 ((Appender) next).close(); 262 } 263 } 264 } 265 } 266 } 267 268 /** 269 * Get iterator over attached appenders. 270 * @return iterator or null if no attached appenders. 271 */ getAllAppenders()272 public Enumeration getAllAppenders() { 273 synchronized (appenders) { 274 return appenders.getAllAppenders(); 275 } 276 } 277 278 /** 279 * Get appender by name. 280 * 281 * @param name name, may not be null. 282 * @return matching appender or null. 283 */ getAppender(final String name)284 public Appender getAppender(final String name) { 285 synchronized (appenders) { 286 return appenders.getAppender(name); 287 } 288 } 289 290 /** 291 * Gets whether the location of the logging request call 292 * should be captured. 293 * 294 * @return the current value of the <b>LocationInfo</b> option. 295 */ getLocationInfo()296 public boolean getLocationInfo() { 297 return locationInfo; 298 } 299 300 /** 301 * Determines if specified appender is attached. 302 * @param appender appender. 303 * @return true if attached. 304 */ isAttached(final Appender appender)305 public boolean isAttached(final Appender appender) { 306 synchronized (appenders) { 307 return appenders.isAttached(appender); 308 } 309 } 310 311 /** 312 * {@inheritDoc} 313 */ requiresLayout()314 public boolean requiresLayout() { 315 return false; 316 } 317 318 /** 319 * Removes and closes all attached appenders. 320 */ removeAllAppenders()321 public void removeAllAppenders() { 322 synchronized (appenders) { 323 appenders.removeAllAppenders(); 324 } 325 } 326 327 /** 328 * Removes an appender. 329 * @param appender appender to remove. 330 */ removeAppender(final Appender appender)331 public void removeAppender(final Appender appender) { 332 synchronized (appenders) { 333 appenders.removeAppender(appender); 334 } 335 } 336 337 /** 338 * Remove appender by name. 339 * @param name name. 340 */ removeAppender(final String name)341 public void removeAppender(final String name) { 342 synchronized (appenders) { 343 appenders.removeAppender(name); 344 } 345 } 346 347 /** 348 * The <b>LocationInfo</b> option takes a boolean value. By default, it is 349 * set to false which means there will be no effort to extract the location 350 * information related to the event. As a result, the event that will be 351 * ultimately logged will likely to contain the wrong location information 352 * (if present in the log format). 353 * <p/> 354 * <p/> 355 * Location information extraction is comparatively very slow and should be 356 * avoided unless performance is not a concern. 357 * </p> 358 * @param flag true if location information should be extracted. 359 */ setLocationInfo(final boolean flag)360 public void setLocationInfo(final boolean flag) { 361 locationInfo = flag; 362 } 363 364 /** 365 * Sets the number of messages allowed in the event buffer 366 * before the calling thread is blocked (if blocking is true) 367 * or until messages are summarized and discarded. Changing 368 * the size will not affect messages already in the buffer. 369 * 370 * @param size buffer size, must be positive. 371 */ setBufferSize(final int size)372 public void setBufferSize(final int size) { 373 // 374 // log4j 1.2 would throw exception if size was negative 375 // and deadlock if size was zero. 376 // 377 if (size < 0) { 378 throw new java.lang.NegativeArraySizeException("size"); 379 } 380 381 synchronized (buffer) { 382 // 383 // don't let size be zero. 384 // 385 bufferSize = (size < 1) ? 1 : size; 386 buffer.notifyAll(); 387 } 388 } 389 390 /** 391 * Gets the current buffer size. 392 * @return the current value of the <b>BufferSize</b> option. 393 */ getBufferSize()394 public int getBufferSize() { 395 return bufferSize; 396 } 397 398 /** 399 * Sets whether appender should wait if there is no 400 * space available in the event buffer or immediately return. 401 * 402 * @since 1.2.14 403 * @param value true if appender should wait until available space in buffer. 404 */ setBlocking(final boolean value)405 public void setBlocking(final boolean value) { 406 synchronized (buffer) { 407 blocking = value; 408 buffer.notifyAll(); 409 } 410 } 411 412 /** 413 * Gets whether appender should block calling thread when buffer is full. 414 * If false, messages will be counted by logger and a summary 415 * message appended after the contents of the buffer have been appended. 416 * 417 * @since 1.2.14 418 * @return true if calling thread will be blocked when buffer is full. 419 */ getBlocking()420 public boolean getBlocking() { 421 return blocking; 422 } 423 424 /** 425 * Summary of discarded logging events for a logger. 426 */ 427 private static final class DiscardSummary { 428 /** 429 * First event of the highest severity. 430 */ 431 private LoggingEvent maxEvent; 432 433 /** 434 * Total count of messages discarded. 435 */ 436 private int count; 437 438 /** 439 * Create new instance. 440 * 441 * @param event event, may not be null. 442 */ DiscardSummary(final LoggingEvent event)443 public DiscardSummary(final LoggingEvent event) { 444 maxEvent = event; 445 count = 1; 446 } 447 448 /** 449 * Add discarded event to summary. 450 * 451 * @param event event, may not be null. 452 */ add(final LoggingEvent event)453 public void add(final LoggingEvent event) { 454 if (event.getLevel().toInt() > maxEvent.getLevel().toInt()) { 455 maxEvent = event; 456 } 457 458 count++; 459 } 460 461 /** 462 * Create event with summary information. 463 * 464 * @return new event. 465 */ createEvent()466 public LoggingEvent createEvent() { 467 String msg = 468 MessageFormat.format( 469 "Discarded {0} messages due to full event buffer including: {1}", 470 new Object[] { new Integer(count), maxEvent.getMessage() }); 471 472 return new LoggingEvent( 473 "org.apache.log4j.AsyncAppender.DONT_REPORT_LOCATION", 474 Logger.getLogger(maxEvent.getLoggerName()), 475 maxEvent.getLevel(), 476 msg, 477 null); 478 } 479 } 480 481 /** 482 * Event dispatcher. 483 */ 484 private static class Dispatcher implements Runnable { 485 /** 486 * Parent AsyncAppender. 487 */ 488 private final AsyncAppender parent; 489 490 /** 491 * Event buffer. 492 */ 493 private final List buffer; 494 495 /** 496 * Map of DiscardSummary keyed by logger name. 497 */ 498 private final Map discardMap; 499 500 /** 501 * Wrapped appenders. 502 */ 503 private final AppenderAttachableImpl appenders; 504 505 /** 506 * Create new instance of dispatcher. 507 * 508 * @param parent parent AsyncAppender, may not be null. 509 * @param buffer event buffer, may not be null. 510 * @param discardMap discard map, may not be null. 511 * @param appenders appenders, may not be null. 512 */ Dispatcher( final AsyncAppender parent, final List buffer, final Map discardMap, final AppenderAttachableImpl appenders)513 public Dispatcher( 514 final AsyncAppender parent, final List buffer, final Map discardMap, 515 final AppenderAttachableImpl appenders) { 516 517 this.parent = parent; 518 this.buffer = buffer; 519 this.appenders = appenders; 520 this.discardMap = discardMap; 521 } 522 523 /** 524 * {@inheritDoc} 525 */ run()526 public void run() { 527 boolean isActive = true; 528 529 // 530 // if interrupted (unlikely), end thread 531 // 532 try { 533 // 534 // loop until the AsyncAppender is closed. 535 // 536 while (isActive) { 537 LoggingEvent[] events = null; 538 539 // 540 // extract pending events while synchronized 541 // on buffer 542 // 543 synchronized (buffer) { 544 int bufferSize = buffer.size(); 545 isActive = !parent.closed; 546 547 while ((bufferSize == 0) && isActive) { 548 buffer.wait(); 549 bufferSize = buffer.size(); 550 isActive = !parent.closed; 551 } 552 553 if (bufferSize > 0) { 554 events = new LoggingEvent[bufferSize + discardMap.size()]; 555 buffer.toArray(events); 556 557 // 558 // add events due to buffer overflow 559 // 560 int index = bufferSize; 561 562 for ( 563 Iterator iter = discardMap.values().iterator(); 564 iter.hasNext();) { 565 events[index++] = ((DiscardSummary) iter.next()).createEvent(); 566 } 567 568 // 569 // clear buffer and discard map 570 // 571 buffer.clear(); 572 discardMap.clear(); 573 574 // 575 // allow blocked appends to continue 576 buffer.notifyAll(); 577 } 578 } 579 580 // 581 // process events after lock on buffer is released. 582 // 583 if (events != null) { 584 for (int i = 0; i < events.length; i++) { 585 synchronized (appenders) { 586 appenders.appendLoopOnAppenders(events[i]); 587 } 588 } 589 } 590 } 591 } catch (InterruptedException ex) { 592 Thread.currentThread().interrupt(); 593 } 594 } 595 } 596 } 597