/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.core.test; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Properties; import junit.framework.AssertionFailedError; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; import org.apache.log4j.PropertyConfigurator; import org.apache.log4j.spi.LoggingEvent; /** * <p>MockLogAppender class. If you do not specify the log level specifically, the level * will default to DEBUG and you can control the level by setting the <code>mock.logLevel</code> * system property.</p> * * Used in unit tests to check that the level of logging produced by a test was suitable. * In some cases, the test will be that no messages were logged at a higher priority than * specified, e.g. Error messages logged when only Notice were expected. * * Some other tests may wish to ensure that an Error or Warn message was indeed logged as expected * * Remember: "Greater" in regards to level relates to priority; the higher the level, the less should * usually be logged (e.g. Errors (highest) should be only the highest priority messages about really bad things, * as compared to Debug (lowest) which is any old rubbish that might be interesting to a developer) * * @author brozow * @version $Id: $ */ public class MockLogAppender extends AppenderSkeleton { private static List<LoggingEvent> s_events = null; private static Level s_logLevel = Level.ALL; /** * <p>Constructor for MockLogAppender.</p> */ public MockLogAppender() { super(); resetEvents(); resetLogLevel(); } /** {@inheritDoc} */ public synchronized void doAppend(final LoggingEvent event) { super.doAppend(event); receivedLogLevel(event.getLevel()); } /** {@inheritDoc} */ protected void append(final LoggingEvent event) { s_events.add(event); } /** * <p>close</p> */ public void close() { } /** * <p>requiresLayout</p> * * @return a boolean. */ public boolean requiresLayout() { return false; } /** * <p>resetEvents</p> */ public static void resetEvents() { s_events = Collections.synchronizedList(new LinkedList<LoggingEvent>()); } /** * <p>getEvents</p> * * @return an array of {@link org.apache.log4j.spi.LoggingEvent} objects. */ public static LoggingEvent[] getEvents() { return (LoggingEvent[]) s_events.toArray(new LoggingEvent[0]); } /** * <p>getEventsGreaterOrEqual</p> * * @param level a {@link org.apache.log4j.Level} object. * @return an array of {@link org.apache.log4j.spi.LoggingEvent} objects. */ public static LoggingEvent[] getEventsGreaterOrEqual(final Level level) { LinkedList<LoggingEvent> matching = new LinkedList<LoggingEvent>(); synchronized (s_events) { for (final LoggingEvent event : s_events) { if (event.getLevel().isGreaterOrEqual(level)) { matching.add(event); } } } return matching.toArray(new LoggingEvent[0]); } /** * <p>getEventsAtLevel</p> * * Returns events that were logged at the specified level * * @param level a {@link org.apache.log4j.Level} object. * @return an array of {@link org.apache.log4j.spi.LoggingEvent} objects. */ public static LoggingEvent[] getEventsAtLevel(final Level level) { LinkedList<LoggingEvent> matching = new LinkedList<LoggingEvent>(); synchronized (s_events) { for (final LoggingEvent event : s_events) { if (event.getLevel().isGreaterOrEqual(level)) { matching.add(event); } } } return matching.toArray(new LoggingEvent[0]); } /** * <p>setupLogging</p> */ public static void setupLogging() { setupLogging(new Properties()); } /** * <p>setupLogging</p> * * @param config a {@link java.util.Properties} object. */ public static void setupLogging(final Properties config) { setupLogging(true, config); } /** * <p>setupLogging</p> * * @param toConsole a boolean. */ public static void setupLogging(final boolean toConsole) { setupLogging(toConsole, new Properties()); } /** * <p>setupLogging</p> * * @param toConsole a boolean. * @param props a {@link java.util.Properties} object. */ public static void setupLogging(final boolean toConsole, final Properties props) { final String level = System.getProperty("mock.logLevel", "DEBUG"); setupLogging(toConsole, level, props); } /** * <p>setupLogging</p> * * @param toConsole a boolean. * @param level a {@link java.lang.String} object. */ public static void setupLogging(final boolean toConsole, final String level) { setupLogging(toConsole, level, new Properties()); } /** * <p>setupLogging</p> * * @param toConsole a boolean. * @param level a {@link java.lang.String} object. * @param config a {@link java.util.Properties} object. */ public static void setupLogging(final boolean toConsole, final String level, final Properties config) { resetLogLevel(); final Properties logConfig = new Properties(config); final String consoleAppender = (toConsole ? ", CONSOLE" : ""); setProperty(logConfig, "log4j.appender.CONSOLE", "org.apache.log4j.ConsoleAppender"); setProperty(logConfig, "log4j.appender.CONSOLE.layout", "org.apache.log4j.PatternLayout"); setProperty(logConfig, "log4j.appender.CONSOLE.layout.ConversionPattern", "%d %-5p [%t] %c: %m%n"); setProperty(logConfig, "log4j.appender.MOCK", MockLogAppender.class.getName()); setProperty(logConfig, "log4j.appender.MOCK.layout", "org.apache.log4j.PatternLayout"); setProperty(logConfig, "log4j.appender.MOCK.layout.ConversionPattern", "%-5p [%t] %c: %m%n"); setProperty(logConfig, "log4j.rootCategory", level + consoleAppender + ", MOCK"); setProperty(logConfig, "log4j.logger.org.apache.commons.httpclient.HttpMethodBase", "ERROR"); setProperty(logConfig, "log4j.logger.org.exolab.castor", "INFO"); setProperty(logConfig, "log4j.logger.org.snmp4j", "ERROR"); setProperty(logConfig, "log4j.logger.org.snmp4j.agent", "ERROR"); setProperty(logConfig, "log4j.logger.com.mchange.v2.c3p0.impl", "WARN"); setProperty(logConfig, "log4j.logger.org.hibernate.cfg.AnnotationBinder", "ERROR" + consoleAppender + ", MOCK"); PropertyConfigurator.configure(logConfig); } private static void setProperty(final Properties logConfig, final String key, final String value) { if (!logConfig.containsKey(key)) { logConfig.put(key, value); } } /** * <p>isLoggingSetup</p> * * @return a boolean. */ public static boolean isLoggingSetup() { return s_events != null; } /** * <p>receivedLogLevel</p> * * @param level a {@link org.apache.log4j.Level} object. */ public static void receivedLogLevel(final Level level) { if (level.isGreaterOrEqual(s_logLevel)) { s_logLevel = level; } } /** * <p>resetLogLevel</p> */ public static void resetLogLevel() { s_logLevel = Level.ALL; } /** * <p>noWarningsOrHigherLogged</p> * * @return a boolean. */ public static boolean noWarningsOrHigherLogged() { return Level.INFO.isGreaterOrEqual(s_logLevel); } /** * <p>assertNotGreaterOrEqual</p> * * @param level a {@link org.apache.log4j.Level} object. * @throws junit.framework.AssertionFailedError if any. */ public static void assertNotGreaterOrEqual(final Level level) throws AssertionFailedError { if (!isLoggingSetup()) { throw new AssertionFailedError("MockLogAppender has not been initialized"); } try { Thread.sleep(500); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } final LoggingEvent[] events = getEventsGreaterOrEqual(level); if (events.length == 0) { return; } StringBuffer message = new StringBuffer("Log messages at or greater than the log level ").append(level).append(" received:"); for (final LoggingEvent event : events) { message.append("\n\t[").append(event.getLevel()).append("] ") .append(event.getLoggerName()).append(": ") .append(event.getMessage()); } throw new AssertionFailedError(message.toString()); } /** * <p>assertNoWarningsOrGreater</p> * * @throws junit.framework.AssertionFailedError if any. */ public static void assertNoWarningsOrGreater() throws AssertionFailedError { assertNotGreaterOrEqual(Level.WARN); } /** * <p>assertLogAtLevel</p> * Asserts that a message was logged at the requested level. * * Useful for testing code that *should* have logged an error message * (or a notice or some other special case) * * @param level a {@link org.apache.log4j.Level} object. * @throws junit.framework.AssertionFailedError if any. */ public static void assertLogAtLevel(final Level level) throws AssertionFailedError { if (!isLoggingSetup()) { throw new AssertionFailedError("MockLogAppender has not been initialized"); } try { Thread.sleep(500); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } final LoggingEvent[] events = getEventsAtLevel(level); if (events.length == 0) { throw new AssertionFailedError("No messages were received at level " + level); } } }