001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018// Contibutors: Aaron Greenhouse <aarong@cs.cmu.edu> 019// Thomas Tuft Muller <ttm@online.no> 020package org.apache.log4j; 021 022import java.text.MessageFormat; 023import java.util.ArrayList; 024import java.util.Enumeration; 025import java.util.HashMap; 026import java.util.Iterator; 027import java.util.List; 028import java.util.Map; 029 030import org.apache.log4j.helpers.AppenderAttachableImpl; 031import org.apache.log4j.spi.AppenderAttachable; 032import org.apache.log4j.spi.LoggingEvent; 033 034 035/** 036 * The AsyncAppender lets users log events asynchronously. 037 * <p/> 038 * <p/> 039 * The AsyncAppender will collect the events sent to it and then dispatch them 040 * to all the appenders that are attached to it. You can attach multiple 041 * appenders to an AsyncAppender. 042 * </p> 043 * <p/> 044 * <p/> 045 * The AsyncAppender uses a separate thread to serve the events in its buffer. 046 * </p> 047 * <p/> 048 * <b>Important note:</b> The <code>AsyncAppender</code> can only be script 049 * configured using the {@link org.apache.log4j.xml.DOMConfigurator}. 050 * </p> 051 * 052 * @author Ceki Gülcü 053 * @author Curt Arnold 054 * @since 0.9.1 055 */ 056public class AsyncAppender extends AppenderSkeleton 057 implements AppenderAttachable { 058 /** 059 * The default buffer size is set to 128 events. 060 */ 061 public static final int DEFAULT_BUFFER_SIZE = 128; 062 063 /** 064 * Event buffer, also used as monitor to protect itself and 065 * discardMap from simulatenous modifications. 066 */ 067 private final List buffer = new ArrayList(); 068 069 /** 070 * Map of DiscardSummary objects keyed by logger name. 071 */ 072 private final Map discardMap = new HashMap(); 073 074 /** 075 * Buffer size. 076 */ 077 private int bufferSize = DEFAULT_BUFFER_SIZE; 078 079 /** Nested appenders. */ 080 AppenderAttachableImpl aai; 081 082 /** 083 * Nested appenders. 084 */ 085 private final AppenderAttachableImpl appenders; 086 087 /** 088 * Dispatcher. 089 */ 090 private final Thread dispatcher; 091 092 /** 093 * Should location info be included in dispatched messages. 094 */ 095 private boolean locationInfo = false; 096 097 /** 098 * Does appender block when buffer is full. 099 */ 100 private boolean blocking = true; 101 102 /** 103 * Create new instance. 104 */ 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 */ 130 public void addAppender(final Appender newAppender) { 131 synchronized (appenders) { 132 appenders.addAppender(newAppender); 133 } 134 } 135 136 /** 137 * {@inheritDoc} 138 */ 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 */ 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 */ 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 */ 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 */ 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 */ 305 public boolean isAttached(final Appender appender) { 306 synchronized (appenders) { 307 return appenders.isAttached(appender); 308 } 309 } 310 311 /** 312 * {@inheritDoc} 313 */ 314 public boolean requiresLayout() { 315 return false; 316 } 317 318 /** 319 * Removes and closes all attached appenders. 320 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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}