001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.modeshape.common.logging; 017 018import static org.junit.Assert.assertEquals; 019import static org.junit.Assert.assertSame; 020import static org.junit.Assert.fail; 021import java.io.StringWriter; 022import java.util.ArrayList; 023import java.util.Enumeration; 024import java.util.HashMap; 025import java.util.LinkedList; 026import java.util.List; 027import java.util.Map; 028import org.apache.log4j.Appender; 029import org.apache.log4j.Level; 030import org.apache.log4j.SimpleLayout; 031import org.apache.log4j.WriterAppender; 032import org.apache.log4j.spi.LoggingEvent; 033import org.apache.log4j.spi.ThrowableInformation; 034import org.modeshape.common.i18n.I18n; 035import org.junit.After; 036import org.junit.Before; 037import org.junit.BeforeClass; 038import org.junit.Test; 039 040/** 041 * Test the {@link org.modeshape.common.logging.Logger} class, ensuring that it uses Log4J appropriately. The {@link org.modeshape.common.logging.Logger} class uses the SLF4J generalized 042 * logging framework, which can sit on top of multiple logging frameworks, including Log4J. Therefore, this test assumes that 043 * SLF4J works correctly for all logging frameworks, then the {@link org.modeshape.common.logging.Logger} class can be tested by using it and checking the 044 * resulting Log4J output. 045 * <p> 046 * To ensure that the Log4J configuration used in the remaining tests (in <code>src/test/resources/log4j.properties</code>) 047 * does not interfere with this test, the underlying Log4J logger is obtained before each test and programmatically reconfigured 048 * and, after each test, is then restored to it's previous state. This reconfiguration involves identifying and removing all of 049 * the {@link Appender Log4J Appender} on the tree of Log4J loggers, and substituting a special {@link LogRecorder Appender} that 050 * records the log messages in memory. During the test, this in-memory list of log messages is checked by the test case using 051 * standard assertions to verify the proper order and content of the log messages. After each of the tests, all of the original 052 * Adapters are restored to the appropriate Log4J loggers. 053 * </p> 054 */ 055public class LoggerTest { 056 057 public static I18n errorMessageWithNoParameters; 058 public static I18n warningMessageWithNoParameters; 059 public static I18n infoMessageWithNoParameters; 060 public static I18n errorMessageWithTwoParameters; 061 public static I18n warningMessageWithTwoParameters; 062 public static I18n infoMessageWithTwoParameters; 063 public static I18n errorMessageWithException; 064 public static I18n warningMessageWithException; 065 public static I18n infoMessageWithException; 066 public static I18n errorMessageWithNullException; 067 public static I18n warningMessageWithNullException; 068 public static I18n infoMessageWithNullException; 069 public static I18n someMessage; 070 071 private LogRecorder log; 072 private Logger logger; 073 private org.apache.log4j.Logger log4jLogger; 074 private Map<String, List<Appender>> existingAppendersByLoggerName = new HashMap<String, List<Appender>>(); 075 076 @BeforeClass 077 public static void beforeAll() { 078 // Initialize the I18n static fields ... 079 I18n.initialize(LoggerTest.class); 080 } 081 082 @Before 083 public void beforeEach() { 084 logger = Logger.getLogger(LoggerTest.class); 085 086 // Find all of the existing appenders on all of the loggers, and 087 // remove them all (keeping track of which appender they're on) 088 log4jLogger = org.apache.log4j.Logger.getLogger(logger.getName()); 089 org.apache.log4j.Logger theLogger = log4jLogger; 090 while (theLogger != null) { 091 List<Appender> appenders = new ArrayList<Appender>(); 092 Enumeration<?> previousAppenders = theLogger.getAllAppenders(); 093 while (previousAppenders.hasMoreElements()) { 094 appenders.add((Appender)previousAppenders.nextElement()); 095 } 096 existingAppendersByLoggerName.put(theLogger.getName(), appenders); 097 theLogger.removeAllAppenders(); 098 theLogger = (org.apache.log4j.Logger)theLogger.getParent(); 099 } 100 101 // Set up the appender from which we can easily grab the content of the log during the tests. 102 // This assumes we're using Log4J. Also, the Log4J properties should specify that the 103 // logger for this particular class. 104 log = new LogRecorder(); 105 log4jLogger = org.apache.log4j.Logger.getLogger(logger.getName()); 106 log4jLogger.addAppender(this.log); 107 log4jLogger.setLevel(Level.ALL); 108 } 109 110 @After 111 public void afterEach() { 112 // Put all of the existing appenders onto the correct logger, and remove the testing appender ... 113 for (Map.Entry<String, List<Appender>> entry : this.existingAppendersByLoggerName.entrySet()) { 114 String loggerName = entry.getKey(); 115 List<Appender> appenders = entry.getValue(); 116 org.apache.log4j.Logger theLogger = org.apache.log4j.Logger.getLogger(loggerName); 117 theLogger.removeAllAppenders(); // removes the testing appender, if on this logger 118 for (Appender appender : appenders) { 119 theLogger.addAppender(appender); 120 } 121 } 122 } 123 124 @Test 125 public void shouldLogAppropriateMessagesIfSetToAllLevel() { 126 log4jLogger.setLevel(Level.ALL); 127 logger.error(errorMessageWithNoParameters); 128 logger.warn(warningMessageWithNoParameters); 129 logger.info(infoMessageWithNoParameters); 130 logger.debug("This is a debug message with no parameters"); 131 logger.trace("This is a trace message with no parameters"); 132 133 log.removeFirst(Logger.Level.ERROR, "This is an error message with no parameters"); 134 log.removeFirst(Logger.Level.WARNING, "This is a warning message with no parameters"); 135 log.removeFirst(Logger.Level.INFO, "This is an info message with no parameters"); 136 log.removeFirst(Logger.Level.DEBUG, "This is a debug message with no parameters"); 137 log.removeFirst(Logger.Level.TRACE, "This is a trace message with no parameters"); 138 assertEquals(false, log.hasEvents()); 139 } 140 141 @Test 142 public void shouldLogAppropriateMessagesIfLog4jSetToTraceLevel() { 143 log4jLogger.setLevel(Level.TRACE); 144 logger.error(errorMessageWithNoParameters); 145 logger.warn(warningMessageWithNoParameters); 146 logger.info(infoMessageWithNoParameters); 147 logger.debug("This is a debug message with no parameters"); 148 logger.trace("This is a trace message with no parameters"); 149 150 log.removeFirst(Logger.Level.ERROR, "This is an error message with no parameters"); 151 log.removeFirst(Logger.Level.WARNING, "This is a warning message with no parameters"); 152 log.removeFirst(Logger.Level.INFO, "This is an info message with no parameters"); 153 log.removeFirst(Logger.Level.DEBUG, "This is a debug message with no parameters"); 154 log.removeFirst(Logger.Level.TRACE, "This is a trace message with no parameters"); 155 assertEquals(false, log.hasEvents()); 156 } 157 158 @Test 159 public void shouldLogAppropriateMessagesIfLog4jSetToDebugLevel() { 160 log4jLogger.setLevel(Level.DEBUG); 161 logger.error(errorMessageWithNoParameters); 162 logger.warn(warningMessageWithNoParameters); 163 logger.info(infoMessageWithNoParameters); 164 logger.debug("This is a debug message with no parameters"); 165 logger.trace("This is a trace message with no parameters"); 166 167 log.removeFirst(Logger.Level.ERROR, "This is an error message with no parameters"); 168 log.removeFirst(Logger.Level.WARNING, "This is a warning message with no parameters"); 169 log.removeFirst(Logger.Level.INFO, "This is an info message with no parameters"); 170 log.removeFirst(Logger.Level.DEBUG, "This is a debug message with no parameters"); 171 assertEquals(false, log.hasEvents()); 172 } 173 174 @Test 175 public void shouldLogAppropriateMessagesIfLog4jSetToInfoLevel() { 176 log4jLogger.setLevel(Level.INFO); 177 logger.error(errorMessageWithNoParameters); 178 logger.warn(warningMessageWithNoParameters); 179 logger.info(infoMessageWithNoParameters); 180 logger.debug("This is a debug message with no parameters"); 181 logger.trace("This is a trace message with no parameters"); 182 183 log.removeFirst(Logger.Level.ERROR, "This is an error message with no parameters"); 184 log.removeFirst(Logger.Level.WARNING, "This is a warning message with no parameters"); 185 log.removeFirst(Logger.Level.INFO, "This is an info message with no parameters"); 186 assertEquals(false, log.hasEvents()); 187 } 188 189 @Test 190 public void shouldLogAppropriateMessagesIfLog4jSetToWarningLevel() { 191 log4jLogger.setLevel(Level.WARN); 192 logger.error(errorMessageWithNoParameters); 193 logger.warn(warningMessageWithNoParameters); 194 logger.info(infoMessageWithNoParameters); 195 logger.debug("This is a debug message with no parameters"); 196 logger.trace("This is a trace message with no parameters"); 197 198 log.removeFirst(Logger.Level.ERROR, "This is an error message with no parameters"); 199 log.removeFirst(Logger.Level.WARNING, "This is a warning message with no parameters"); 200 assertEquals(false, log.hasEvents()); 201 } 202 203 @Test 204 public void shouldLogAppropriateMessagesIfLog4jSetToErrorLevel() { 205 log4jLogger.setLevel(Level.ERROR); 206 logger.error(errorMessageWithNoParameters); 207 logger.warn(warningMessageWithNoParameters); 208 logger.info(infoMessageWithNoParameters); 209 logger.debug("This is a debug message with no parameters"); 210 logger.trace("This is a trace message with no parameters"); 211 212 log.removeFirst(Logger.Level.ERROR, "This is an error message with no parameters"); 213 assertEquals(false, log.hasEvents()); 214 } 215 216 @Test 217 public void shouldLogNoMessagesIfLog4jSetToOffLevel() { 218 log4jLogger.setLevel(Level.OFF); 219 logger.error(errorMessageWithNoParameters); 220 logger.warn(warningMessageWithNoParameters); 221 logger.info(infoMessageWithNoParameters); 222 logger.debug("This is a debug message with no parameters"); 223 logger.trace("This is a trace message with no parameters"); 224 225 assertEquals(false, log.hasEvents()); 226 } 227 228 @Test 229 public void shouldNotAcceptMessageWithNonNullAndNullParameters() { 230 logger.error(errorMessageWithTwoParameters, "first", null); 231 logger.warn(warningMessageWithTwoParameters, "first", null); 232 logger.info(infoMessageWithTwoParameters, "first", null); 233 logger.debug("This is a debug message with a {0} parameter and the {1} parameter", "first", null); 234 logger.trace("This is a trace message with a {0} parameter and the {1} parameter", "first", null); 235 236 log.removeFirst(Logger.Level.ERROR, "This is an error message with a first parameter and the null parameter"); 237 log.removeFirst(Logger.Level.WARNING, "This is a warning message with a first parameter and the null parameter"); 238 log.removeFirst(Logger.Level.INFO, "This is an info message with a first parameter and the null parameter"); 239 log.removeFirst(Logger.Level.DEBUG, "This is a debug message with a first parameter and the null parameter"); 240 log.removeFirst(Logger.Level.TRACE, "This is a trace message with a first parameter and the null parameter"); 241 assertEquals(false, log.hasEvents()); 242 } 243 244 @Test( expected = IllegalArgumentException.class ) 245 public void shouldNotAcceptErrorMessageWithTooFewParameters() { 246 logger.error(errorMessageWithTwoParameters, (Object[])null); 247 } 248 249 @Test( expected = IllegalArgumentException.class ) 250 public void shouldNotAcceptWarningMessageWithTooFewParameters() { 251 logger.warn(warningMessageWithTwoParameters, (Object[])null); 252 } 253 254 @Test( expected = IllegalArgumentException.class ) 255 public void shouldNotAcceptInfoMessageWithTooFewParameters() { 256 logger.info(infoMessageWithTwoParameters, (Object[])null); 257 } 258 259 @Test( expected = IllegalArgumentException.class ) 260 public void shouldNotAcceptDebugMessageWithTooFewParameters() { 261 logger.debug("This is a debug message with a {0} parameter and the {1} parameter", (Object[])null); 262 } 263 264 @Test( expected = IllegalArgumentException.class ) 265 public void shouldNotAcceptTraceMessageWithTooFewParameters() { 266 logger.trace("This is a trace message with a {0} parameter and the {1} parameter", (Object[])null); 267 } 268 269 @Test 270 public void shouldAcceptMessageWithNoParameters() { 271 logger.error(errorMessageWithNoParameters); 272 logger.warn(warningMessageWithNoParameters); 273 logger.info(infoMessageWithNoParameters); 274 logger.debug("This is a debug message with no parameters"); 275 logger.trace("This is a trace message with no parameters"); 276 277 log.removeFirst(Logger.Level.ERROR, "This is an error message with no parameters"); 278 log.removeFirst(Logger.Level.WARNING, "This is a warning message with no parameters"); 279 log.removeFirst(Logger.Level.INFO, "This is an info message with no parameters"); 280 log.removeFirst(Logger.Level.DEBUG, "This is a debug message with no parameters"); 281 log.removeFirst(Logger.Level.TRACE, "This is a trace message with no parameters"); 282 assertEquals(false, log.hasEvents()); 283 } 284 285 @Test 286 public void shouldAcceptMessageWithObjectAndPrimitiveParameters() { 287 logger.error(errorMessageWithTwoParameters, "first", 2); 288 logger.warn(warningMessageWithTwoParameters, "first", 2); 289 logger.info(infoMessageWithTwoParameters, "first", 2); 290 logger.debug("This is a debug message with a {0} parameter and the {1} parameter", "first", 2); 291 logger.trace("This is a trace message with a {0} parameter and the {1} parameter", "first", 2); 292 293 log.removeFirst(Logger.Level.ERROR, "This is an error message with a first parameter and the 2 parameter"); 294 log.removeFirst(Logger.Level.WARNING, "This is a warning message with a first parameter and the 2 parameter"); 295 log.removeFirst(Logger.Level.INFO, "This is an info message with a first parameter and the 2 parameter"); 296 log.removeFirst(Logger.Level.DEBUG, "This is a debug message with a first parameter and the 2 parameter"); 297 log.removeFirst(Logger.Level.TRACE, "This is a trace message with a first parameter and the 2 parameter"); 298 assertEquals(false, log.hasEvents()); 299 } 300 301 @Test 302 public void shouldAcceptMessageAndThrowable() { 303 Throwable t = new RuntimeException("This is the runtime exception message"); 304 logger.error(t, errorMessageWithException); 305 logger.warn(t, warningMessageWithException); 306 logger.info(t, infoMessageWithException); 307 logger.debug(t, "This is a debug message with an exception"); 308 logger.trace(t, "This is a trace message with an exception"); 309 310 log.removeFirst(Logger.Level.ERROR, "This is an error message with an exception", RuntimeException.class); 311 log.removeFirst(Logger.Level.WARNING, "This is a warning message with an exception", RuntimeException.class); 312 log.removeFirst(Logger.Level.INFO, "This is an info message with an exception", RuntimeException.class); 313 log.removeFirst(Logger.Level.DEBUG, "This is a debug message with an exception", RuntimeException.class); 314 log.removeFirst(Logger.Level.TRACE, "This is a trace message with an exception", RuntimeException.class); 315 assertEquals(false, log.hasEvents()); 316 } 317 318 @Test 319 public void shouldAcceptMessageAndNullThrowable() { 320 Throwable t = null; 321 logger.error(t, errorMessageWithNullException); 322 logger.warn(t, warningMessageWithNullException); 323 logger.info(t, infoMessageWithNullException); 324 logger.debug(t, "This is a debug message with a null exception"); 325 logger.trace(t, "This is a trace message with a null exception"); 326 327 log.removeFirst(Logger.Level.ERROR, "This is an error message with a null exception"); 328 log.removeFirst(Logger.Level.WARNING, "This is a warning message with a null exception"); 329 log.removeFirst(Logger.Level.INFO, "This is an info message with a null exception"); 330 log.removeFirst(Logger.Level.DEBUG, "This is a debug message with a null exception"); 331 log.removeFirst(Logger.Level.TRACE, "This is a trace message with a null exception"); 332 assertEquals(false, log.hasEvents()); 333 } 334 335 public void shouldQuietlyAcceptNullMessage() { 336 logger.error(null); 337 logger.warn(null); 338 logger.info(null); 339 logger.debug(null); 340 logger.trace(null); 341 342 assertEquals(false, log.hasEvents()); 343 } 344 345 @Test 346 public void shouldAcceptNullMessageAndThrowable() { 347 Throwable t = new RuntimeException("This is the runtime exception message in LoggerTest"); 348 logger.error(t, null); 349 logger.warn(t, null); 350 logger.info(t, null); 351 logger.debug(t, null); 352 logger.trace(t, null); 353 354 log.removeFirst(Logger.Level.ERROR, null, RuntimeException.class); 355 log.removeFirst(Logger.Level.WARNING, null, RuntimeException.class); 356 log.removeFirst(Logger.Level.INFO, null, RuntimeException.class); 357 log.removeFirst(Logger.Level.DEBUG, null, RuntimeException.class); 358 log.removeFirst(Logger.Level.TRACE, null, RuntimeException.class); 359 assertEquals(false, log.hasEvents()); 360 } 361 362 @Test 363 public void shouldAcceptNullThrowableInError() { 364 logger.error((Throwable)null, someMessage); 365 logger.warn((Throwable)null, someMessage); 366 logger.info((Throwable)null, someMessage); 367 logger.debug((Throwable)null, "some message"); 368 logger.trace((Throwable)null, "some message"); 369 370 log.removeFirst(Logger.Level.ERROR, "some message"); 371 log.removeFirst(Logger.Level.WARNING, "some message"); 372 log.removeFirst(Logger.Level.INFO, "some message"); 373 log.removeFirst(Logger.Level.DEBUG, "some message"); 374 log.removeFirst(Logger.Level.TRACE, "some message"); 375 } 376 377 @Test 378 public void shouldSupportAskingWhetherLoggingLevelsAreEnabled() { 379 logger.isErrorEnabled(); 380 logger.isWarnEnabled(); 381 logger.isInfoEnabled(); 382 logger.isDebugEnabled(); 383 logger.isTraceEnabled(); 384 } 385 386 /** 387 * A special Log4J Appender that records log messages and whose content can be 388 * {@link #removeFirst(org.modeshape.common.logging.Logger.Level, String, Class) validated} to ensure that the log contains 389 * messages in the proper order and with the proper content. 390 */ 391 public class LogRecorder extends WriterAppender { 392 393 private final LinkedList<LoggingEvent> events = new LinkedList<LoggingEvent>(); 394 private int lineNumber; 395 396 public LogRecorder( StringWriter writer ) { 397 super(new SimpleLayout(), writer); 398 } 399 400 public LogRecorder() { 401 this(new StringWriter()); 402 } 403 404 @Override 405 protected void subAppend( LoggingEvent event ) { 406 super.subAppend(event); 407 this.events.add(event); 408 } 409 410 public LoggingEvent removeFirst() { 411 if (hasEvents()) { 412 ++lineNumber; 413 return this.events.removeFirst(); 414 } 415 return null; 416 } 417 418 public boolean hasEvents() { 419 return this.events.size() != 0; 420 } 421 422 /** 423 * Remove the message that is currently at the front of the log, and verify that it contains the supplied information. 424 * 425 * @param expectedLevel the level that the next log message should have 426 * @param expectedMessageExpression the message that the next log message should have, or a regular expression that would 427 * match the log message 428 * @param expectedExceptionClass the exception class that was expected, or null if there should not be an exception 429 */ 430 public void removeFirst( Logger.Level expectedLevel, 431 String expectedMessageExpression, 432 Class<? extends Throwable> expectedExceptionClass ) { 433 if (!hasEvents()) { 434 fail("Expected log message but found none: " + expectedLevel + " - " + expectedMessageExpression); 435 } 436 LoggingEvent event = removeFirst(); 437 438 // Check the log message ... 439 if (expectedMessageExpression != null && event.getMessage() == null) { 440 fail("Log line " + lineNumber + " was missing expected message: " + expectedMessageExpression); 441 } else if (expectedMessageExpression == null && event.getMessage() != null) { 442 fail("Log line " + lineNumber + " had unexpected message: " + event.getMessage()); 443 } else if (expectedMessageExpression != null) { 444 String actual = event.getMessage().toString(); 445 // Treat as a regular expression, which works for both regular expressions and strings ... 446 if (!actual.matches(expectedMessageExpression)) { 447 fail("Log line " + lineNumber + " differed: \nwas :\t" + actual + "\nexpected:\t" 448 + expectedMessageExpression); 449 } 450 } // else they are both null 451 452 // Check the exception ... 453 ThrowableInformation throwableInfo = event.getThrowableInformation(); 454 if (expectedExceptionClass == null && throwableInfo != null) { 455 fail("Log line " + lineNumber + " had unexpected exception: " 456 + event.getThrowableInformation().getThrowableStrRep()); 457 } else if (expectedExceptionClass != null && throwableInfo == null) { 458 fail("Log line " + lineNumber + " was missing expected exception of type " 459 + expectedExceptionClass.getCanonicalName()); 460 } else if (expectedExceptionClass != null && throwableInfo != null) { 461 Throwable actualException = throwableInfo.getThrowable(); 462 assertSame(expectedExceptionClass, actualException.getClass()); 463 } // else they are both null 464 } 465 466 public void removeFirst( Logger.Level expectedLevel, 467 String expectedMessageExpression ) { 468 removeFirst(expectedLevel, expectedMessageExpression, null); 469 } 470 } 471 472}