diff --git a/common/source/java/ch/systemsx/cisd/common/logging/ConditionalNotificationLogger.java b/common/source/java/ch/systemsx/cisd/common/logging/ConditionalNotificationLogger.java new file mode 100644 index 0000000000000000000000000000000000000000..6d7dde6aeca8e42d5df406517aa0bdc1c1ba7cda --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/logging/ConditionalNotificationLogger.java @@ -0,0 +1,96 @@ +/* + * Copyright 2008 ETH Zuerich, CISD + * + * Licensed 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. + */ + +package ch.systemsx.cisd.common.logging; + +import org.apache.log4j.Logger; + +/** + * An <code>ISimpleLogger</code> implementation which sends an email (via logger) when the number + * of log errors reaches a given number. + * <p> + * Note that this class sends one and only one notification email. To reset the state of this class, + * use {@link #reset(String)}. + * </p> + * + * @author Christian Ribeaud + */ +public final class ConditionalNotificationLogger implements ISimpleLogger +{ + private final Logger notificationLog; + + private final Logger operationLog; + + private final int ignoredErrorCountBeforeNotification; + + private int errorCount; + + private boolean notified; + + /** + * @param ignoredErrorCountBeforeNotification the number of errors that are ignored before + * sending a notification email. + */ + public ConditionalNotificationLogger(final Class<?> clazz, + final int ignoredErrorCountBeforeNotification) + { + assert clazz != null : "Unspecified class"; + assert ignoredErrorCountBeforeNotification > -1 : "Negative ignored error " + + "count before notification"; + this.ignoredErrorCountBeforeNotification = ignoredErrorCountBeforeNotification; + operationLog = LogFactory.getLogger(LogCategory.OPERATION, clazz); + notificationLog = LogFactory.getLogger(LogCategory.NOTIFY, clazz); + } + + /** + * Resets counting of errors and <code>notified</code> flag. + * <p> + * As side effect, it sends a notification log to inform that we are "green" again (if and only + * if an email has already been sent to inform the "bad" state and if log info is enabled). + * </p> + * + * @param message the info log message. + */ + public final void reset(final String message) + { + if (notified && notificationLog.isInfoEnabled()) + { + notificationLog.info(message); + } + errorCount = 0; + notified = false; + } + + // + // ISimpleLogger + // + + public final void log(final LogLevel level, final String message) + { + if (LogLevel.ERROR.equals(level)) + { + if (errorCount < ignoredErrorCountBeforeNotification) + { + operationLog.warn(message); + } else if (notified == false) + { + notificationLog.error(message); + notified = true; + } + errorCount++; + } + } +} \ No newline at end of file diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTask.java b/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTask.java index 296c522320140acf07229cf54cab30089be91dea..b47fa208f41f1e234333e42d46fcd10caafb6a74 100644 --- a/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTask.java +++ b/common/source/java/ch/systemsx/cisd/common/utilities/DirectoryScanningTimerTask.java @@ -22,10 +22,10 @@ import java.util.TimerTask; import org.apache.log4j.Logger; +import ch.systemsx.cisd.common.logging.ConditionalNotificationLogger; import ch.systemsx.cisd.common.logging.ISimpleLogger; import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; -import ch.systemsx.cisd.common.logging.LogLevel; /** * A {@link TimerTask} that scans a source directory for entries that are accepted by some @@ -58,8 +58,6 @@ public final class DirectoryScanningTimerTask extends TimerTask */ private final int ignoredErrorCount; - private int errorCountReadingDirectory; - private final IDirectoryScanningHandler directoryScanningHandler; /** @@ -214,54 +212,18 @@ public final class DirectoryScanningTimerTask extends TimerTask private final StoreItem[] listStoreItems() { - // Avoid mailbox flooding. - final boolean logNotifyError = (errorCountReadingDirectory == ignoredErrorCount); - final boolean logOperationError = (errorCountReadingDirectory < ignoredErrorCount); - final ISimpleLogger errorLogger = - logNotifyError ? createSimpleErrorLogger(LogCategory.NOTIFY) - : (logOperationError ? createSimpleErrorLogger(LogCategory.OPERATION) - : null); - final StoreItem[] storeItems = sourceDirectory.tryListSortedReadyToProcess(errorLogger); - if (errorCountReadingDirectory > ignoredErrorCount && storeItems != null) + final ConditionalNotificationLogger notificationLogger = + new ConditionalNotificationLogger(getClass(), ignoredErrorCount); + final StoreItem[] storeItems = + sourceDirectory.tryListSortedReadyToProcess(notificationLogger); + if (storeItems != null) { - if (notificationLog.isInfoEnabled()) - { - notificationLog.info(String.format("Directory '%s' is available again.", - sourceDirectory)); - } - } - if (storeItems == null) - { - ++errorCountReadingDirectory; - } else - { - errorCountReadingDirectory = 0; + notificationLogger.reset(String.format("Directory '%s' is available again.", + sourceDirectory)); } return (storeItems == null) ? StoreItem.EMPTY_ARRAY : storeItems; } - private final ISimpleLogger createSimpleErrorLogger(final LogCategory category) - { - return new ISimpleLogger() - { - - // - // ISimpleLogger - // - - public final void log(final LogLevel dummyLevel, final String message) - { - if (category == LogCategory.NOTIFY) - { - notificationLog.log(org.apache.log4j.Level.ERROR, message); - } else - { - operationLog.log(org.apache.log4j.Level.WARN, message); - } - } - }; - } - // // Helper classes // diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/logging/ConditionalNotificationLoggerTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/logging/ConditionalNotificationLoggerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..177fa605a46732f539dd1c1fd3bc95abe66be1e1 --- /dev/null +++ b/common/sourceTest/java/ch/systemsx/cisd/common/logging/ConditionalNotificationLoggerTest.java @@ -0,0 +1,132 @@ +/* + * Copyright 2008 ETH Zuerich, CISD + * + * Licensed 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. + */ + +package ch.systemsx.cisd.common.logging; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Level; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test cases for corresponding {@link ConditionalNotificationLogger} class. + * + * @author Christian Ribeaud + */ +public final class ConditionalNotificationLoggerTest +{ + + private final ConditionalNotificationLogger createConditionalNotificationLogger( + final int ignoredErrorCountBeforeNotification) + { + final ConditionalNotificationLogger logger = + new ConditionalNotificationLogger(getClass(), ignoredErrorCountBeforeNotification); + return logger; + } + + private BufferedAppender operationLogRecorder; + + private BufferedAppender notificationLogRecorder; + + @BeforeMethod + public final void setUp() + { + operationLogRecorder = new BufferedAppender("%m", Level.WARN); + notificationLogRecorder = new BufferedAppender("%m", Level.ERROR); + } + + @Test + public final void testConstructor() + { + boolean fail = true; + try + { + new ConditionalNotificationLogger(null, -1); + } catch (final AssertionError e) + { + fail = false; + } + try + { + new ConditionalNotificationLogger(getClass(), -1); + } catch (final AssertionError e) + { + fail = false; + } + assertFalse(fail); + } + + @DataProvider(name = "ignoredErrorCount") + public final Object[][] getIgnoredErrorCount() + { + return new Object[][] + { + { 0 }, + { 1 }, + { 2 }, }; + } + + @Test(dataProvider = "ignoredErrorCount") + public final void testLogWithErrorLevel(final int ignoredErrorCountBeforeNotification) + { + final ConditionalNotificationLogger logger = + createConditionalNotificationLogger(ignoredErrorCountBeforeNotification); + final String logMessage = "Some message"; + for (int i = 0; i <= ignoredErrorCountBeforeNotification; i++) + { + logger.log(LogLevel.ERROR, logMessage); + assertEquals(StringUtils.repeat(logMessage, i + 1), operationLogRecorder + .getLogContent()); + if (i >= ignoredErrorCountBeforeNotification) + { + assertEquals(logMessage, notificationLogRecorder.getLogContent()); + } + } + logger.log(null, logMessage); + assertEquals(logMessage, notificationLogRecorder.getLogContent()); + } + + @Test + public final void testLogWithNullLevel() + { + final ConditionalNotificationLogger logger = createConditionalNotificationLogger(0); + final String logMessage = "Some message"; + logger.log(null, logMessage); + assertEquals("", operationLogRecorder.getLogContent()); + assertEquals("", notificationLogRecorder.getLogContent()); + } + + @Test + public final void testReset() + { + final ConditionalNotificationLogger logger = createConditionalNotificationLogger(2); + final String logMessage = "Some message"; + logger.log(LogLevel.ERROR, logMessage); + logger.log(LogLevel.ERROR, logMessage); + logger.log(LogLevel.ERROR, logMessage); + assertEquals(logMessage, notificationLogRecorder.getLogContent()); + logger.log(LogLevel.ERROR, logMessage); + assertEquals(logMessage, notificationLogRecorder.getLogContent()); + logger.reset("Green again"); + notificationLogRecorder.resetLogContent(); + logger.log(LogLevel.ERROR, logMessage); + assertEquals("", notificationLogRecorder.getLogContent()); + } +}