/******************************************************************************* * ALMA - Atacama Large Millimeter Array * Copyright (c) ESO - European Southern Observatory, 2011 * (in the framework of the ALMA collaboration). * All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *******************************************************************************/ package alma.acs.logging; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import alma.acs.logging.config.LogConfig; import alma.acs.logging.config.LogConfigSubscriber; /** * Process level throttle for logs. * @author hsommer * @since ACS 9.0 (see http://jira.alma.cl/browse/COMP-4541) */ class LogThrottle { /** * Separate throttle for local (stdout) logs */ private final LogStreamThrottle localLogThrottle; /** * Separate throttle for remote (Log service) logs */ private final LogStreamThrottle remoteLogThrottle; private final ThrottleCallback throttleCallback; private final AtomicBoolean localLogsSuppressed = new AtomicBoolean(false); private final AtomicBoolean remoteLogsSuppressed = new AtomicBoolean(false); /** * Constructor that takes the LogConfig object to read the throttle setting. * @param logConfig */ LogThrottle(LogConfig logConfig, ThrottleCallback throttleCallback) { localLogThrottle = new LogStreamThrottle(logConfig); remoteLogThrottle = new LogStreamThrottle(logConfig); this.throttleCallback = throttleCallback; } /** * @return true if the log should pass, false if it should be suppressed due to throttling. */ boolean checkPublishLogRecordLocal() { boolean thisLogSuppressed = !localLogThrottle.checkPublishLogRecord(); boolean lastLocalLogSuppressed = localLogsSuppressed.getAndSet(thisLogSuppressed); // callbacks if (thisLogSuppressed) { throttleCallback.suppressedLog(false); } else if (lastLocalLogSuppressed && !remoteLogsSuppressed.get()) { // this local log (and also remote logs) are allowed, but previous local log failed throttleCallback.clearedLogSuppression(); } return !thisLogSuppressed; } /** * @return true if the log should pass, false if it should be suppressed due to throttling. */ boolean checkPublishLogRecordRemote() { boolean thisLogSuppressed = !remoteLogThrottle.checkPublishLogRecord(); boolean lastRemoteLogSuppressed = remoteLogsSuppressed.getAndSet(thisLogSuppressed); // callbacks if (thisLogSuppressed) { throttleCallback.suppressedLog(true); } else if (lastRemoteLogSuppressed && !localLogsSuppressed.get()) { // this remote log (and also local logs) are allowed, but previous remote log failed throttleCallback.clearedLogSuppression(); } return !thisLogSuppressed; } /** * The actual throttle functionality is kept in this class, which is the same * for the local and remote logging streams. */ private static class LogStreamThrottle implements LogConfigSubscriber { /** * The configured maximum logs per time interval. * For the time being we use this single value for both local and remote logging. */ private int maxLogsPerInterval; /** * Hardcoded to 1000 ms, thus matching the CDB configuration with attribute maxLogsPerSecond" */ private final long intervalLengthMillis = 1000; /** * Time in milliseconds when the current time interval for checking the number of logs has started. */ private long intervalBeginMillis; /** * Number of logs since {@link #intervalBeginMillis}. */ private final AtomicInteger logCounter = new AtomicInteger(0); /** * Constructor that takes the LogConfig object, from which it gets {@link #maxLogsPerInterval}, * also for updates in the future. * @param logConfig */ public LogStreamThrottle(LogConfig logConfig) { intervalBeginMillis = System.currentTimeMillis(); maxLogsPerInterval = -1; if (logConfig != null) { configureLogging(logConfig); logConfig.addSubscriber(this); // passing "this" should only be done when this object is fully constructed. } else { throw new NullPointerException("LogConfig must not be null"); } } @Override public void configureLogging(LogConfig logConfig) { maxLogsPerInterval = logConfig.getMaxLogsPerSecond(); } /** * Checks whether the log throttle allows logging a record. * No exception or other action beyond the returned boolean. * @return true if a record can be logged, false otherwise. */ boolean checkPublishLogRecord() { if (maxLogsPerInterval < 0) { // no throttle configured return true; } long time = System.currentTimeMillis(); if (time > intervalBeginMillis + intervalLengthMillis) { // starting new time interval synchronized (logCounter) { // We only care to keep intervalBeginMillis and logCounter consistent. // It could happen that another thread resets them again right afterwards, which is OK compared to more locking intervalBeginMillis = time; logCounter.set(0); } // whether we can log now that we are in a new time interval is still to be checked below, // to cover cases like maxLogsPerInterval=0, or a really tough race among loggers } // Allow logging if we've had less than the max number of logs in the current interval return logCounter.getAndIncrement() < maxLogsPerInterval; } } /** * Callback class that allows clients to be notified of log throttle action, * for example to raise and clear alarms. */ static interface ThrottleCallback { /** * Notification for every log that was suppressed by the throttle, * counting local and remote publishing of the same log twice (if both are enabled and suppressed). * <p> * May be called concurrently. * @param remoteLog true if the suppressed log was a remote log, and false if it was a local (stdout) log. */ public void suppressedLog(boolean remoteLog); /** * Notification that log suppression is over, for both remote and local logs. * <p> * May be called concurrently. */ public void clearedLogSuppression(); } }