package alma.acs.logging; import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.Set; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; import junit.framework.TestCase; import alma.acs.logging.config.LogConfig; import alma.acs.logging.formatters.ConsoleLogFormatter; import alma.acs.logging.level.AcsLogLevelDefinition; import alma.acs.util.IsoDateFormat; public class LogThrottleTest extends TestCase { // private LogConfig logConfig; // private TestThrottleCallback throttleCallback; // private LogThrottle throttle; protected void setUp() throws Exception { super.setUp(); } /** * Tests the LogThrottle class in isolation. * Many logs are simulated, over more than a second, to test also the re-opening of the throttle. * Covers configuration without throttle, "total throttle" suppressing all logs, and "normal" throttling. */ public void testSimulatedLogs() throws Exception { LogConfig logConfig = new LogConfig(); TestThrottleCallback throttleCallback = new TestThrottleCallback(); LogThrottle throttle = new LogThrottle(logConfig, throttleCallback); assertEquals(-1, logConfig.getMaxLogsPerSecond()); System.out.println("Will simulate logging (above throttle limit), for little more than 2 seconds, which should suppress & clear logs twice."); int maxLogsPerSecond = 10; logConfig.setMaxLogsPerSecond(maxLogsPerSecond); assertEquals(maxLogsPerSecond, logConfig.getMaxLogsPerSecond()); simulateLogging(throttle, 2100, 20, 2*maxLogsPerSecond); Thread.sleep(1000); assertEquals(2, throttleCallback.getAndResetClearanceCounter()); System.out.println("Will simulate logging without throttle set"); logConfig.setMaxLogsPerSecond(-1); simulateLogging(throttle, 2100, 20, 60); // normally ~100, but some machines are slow. Thread.sleep(1000); assertEquals(0, throttleCallback.getAndResetClearanceCounter()); System.out.println("Will simulate logging with degenerated throttle (all logs rejected)"); logConfig.setMaxLogsPerSecond(0); simulateLogging(throttle, 2100, 10, 0); Thread.sleep(1000); assertEquals(0, throttleCallback.getAndResetClearanceCounter()); } /** * Broken out from {@link #testSimulatedLogs()}. */ private void simulateLogging(LogThrottle throttle, long durationMillis, long sleepMillis, int expectedNumberPassingLocalLogs) throws InterruptedException { long startTime = System.currentTimeMillis(); System.out.println(IsoDateFormat.formatDate(new Date(startTime)) + " Starting with simulated logs..."); int numberPassingLocalLogs = 0; while (System.currentTimeMillis() <= startTime + durationMillis) { if (throttle.checkPublishLogRecordLocal()) { numberPassingLocalLogs++; } // currently the test does not keep track of the "publish remote" call results throttle.checkPublishLogRecordRemote(); Thread.sleep(sleepMillis); } assertTrue("A total of at least " + expectedNumberPassingLocalLogs + " should have passed the throttle, but was " + numberPassingLocalLogs, expectedNumberPassingLocalLogs <= numberPassingLocalLogs); } /** * Tests the LogThrottle class as part of the ClientLogManager - handler - formatter hierarchy. * Installs mock objects to count the passing logs that go to stdout and remote. * To verify that the throttle only applies to logs that are not filtered out by log level settings, * for the remote log stream only log levels >= CRITICAL are logged. * The test then verifies that suppression of local logs does not yet suppress the fewer remote logs, * while eventually also the remote logs get suppressed. */ public void testRealLogs() { for (int i = 1; i < 5; i++) { _testRealLogs(i); } } public void _testRealLogs(int numLoops) { TestClientLogManager clientLogManager = new TestClientLogManager(); // instead of usual static method getAcsLogManager() Logger logger = clientLogManager.getLoggerForApplication(getName(), true); LogConfig logConfig = clientLogManager.getLogConfig(); AcsLogLevelDefinition remoteLevel = AcsLogLevelDefinition.CRITICAL; logConfig.setDefaultMinLogLevel(remoteLevel); logConfig.setDefaultMinLogLevelLocal(AcsLogLevelDefinition.TRACE); // the set of all levels used for ACS java logging (omitting JDK levels that have the same numerical value as a matching ACS level) Set<Level> levels = new HashSet<Level>(); for (AcsLogLevelDefinition levelEnum : AcsLogLevelDefinition.values()) { if (levelEnum != AcsLogLevelDefinition.OFF) { levels.add(AcsLogLevel.fromAcsCoreLevel(levelEnum)); } } levels.addAll(Arrays.asList(new Level[] {Level.FINEST, Level.FINER, Level.FINE, Level.INFO, Level.WARNING, Level.SEVERE})); int numLoggableRemoteLevels = 1; AcsLogLevelDefinition tmpLevel = remoteLevel; while (tmpLevel.getNextHigherLevel() != null) { tmpLevel = tmpLevel.getNextHigherLevel(); numLoggableRemoteLevels++; } int expectedNumberOfLocalLogs = levels.size(); int expectedNumberOfRemoteLogs = Math.min(expectedNumberOfLocalLogs, numLoggableRemoteLevels * numLoops); // set throttle so that every log level should be logged once logConfig.setMaxLogsPerSecond(expectedNumberOfLocalLogs); System.out.println("Will log a burst of " + levels.size() * numLoops + " messages of various levels, setting the throttle to " + expectedNumberOfLocalLogs + " logs per second."); System.out.println("For stdout logs (level = TRACE) we expect " + expectedNumberOfLocalLogs + " logs, while for remote logs " + "(level = " + remoteLevel.name() + ") we expect " + expectedNumberOfRemoteLogs + " logs."); for (int i = 0; i < numLoops; i++) { for (Level level : levels) { logger.log(level, "test message with level " + level.getName()); } } // verify the number of actual local logs assertEquals("Each log level should have produced only one local log.", expectedNumberOfLocalLogs, clientLogManager.getNumberLocalLogs()); // The way this is constructed, the throttle should be active if numLoops > 1. // Since no alarms are configured, we expect one warning log about the failure to raise a throttle alarm. int expectedAlarmErrorLogs = (numLoops > 1 ? 1 : 0); assertEquals("Bad number of throttle alarm error log(s)", expectedAlarmErrorLogs, clientLogManager.getNumberLocalThrottleAlarmErrorLogs()); int numUserRemoteLogs = 0; for (LogRecord logRecord : clientLogManager.testLogQueue.logRecords) { if (!logRecord.getMessage().startsWith("Changed processName=")) { numUserRemoteLogs++; } } assertEquals("Wrong number of remote logs", expectedNumberOfRemoteLogs, numUserRemoteLogs); } /** * Throttle action callback object, that counts the callbacks. */ private static class TestThrottleCallback implements LogThrottle.ThrottleCallback { private volatile int numSuppressed; private volatile int numClearance; public void clearedLogSuppression() { System.out.println(IsoDateFormat.formatCurrentDate() + " ClearedLogSuppression, after having suppressed " + numSuppressed + " logs."); numSuppressed = 0; numClearance++; } public void suppressedLog(boolean remoteLog) { if (numSuppressed == 0) { System.out.println(IsoDateFormat.formatCurrentDate() + " Suppression starts with remote=" + remoteLog); } else { // System.out.println(IsoDateFormat.formatCurrentDate() + " suppressed a log..."); } numSuppressed++; } int getAndResetClearanceCounter() { int ret = numClearance; numClearance = 0; return ret; } } /** * An instrumented version of ClientLogManager, which uses special log formatters to count the actual number of * local and remote logs. */ private static class TestClientLogManager extends ClientLogManager { private volatile int numLocalLogs; private volatile int numLocalThrottleAlarmErrorLogs; TestLogQueue testLogQueue; int getNumberLocalLogs() { return numLocalLogs; } int getNumberLocalThrottleAlarmErrorLogs() { return numLocalThrottleAlarmErrorLogs; } @Override protected StdOutConsoleHandler createStdOutConsoleHandlerWithFormatter(LogConfig logConfig, String loggerName, LogThrottle throttle) { StdOutConsoleHandler localHandler = new StdOutConsoleHandler(logConfig, loggerName, throttle); localHandler.setFormatter(new ConsoleLogFormatter() { @Override public String format(LogRecord record) { if (record.getMessage().equals("Cannot raise alarm about log throttle action because alarm callback or process name is not yet available.")) { numLocalThrottleAlarmErrorLogs++; } else if (!record.getMessage().startsWith("Changed processName=")) { numLocalLogs++; } return super.format(record); } }); return localHandler; } @Override protected DispatchingLogQueue createDispatchingLogQueue() { testLogQueue = new TestLogQueue(); // will not actually do remote logging, just counts the invocations return testLogQueue; } } }