blob: df3d2e41e8fa7b2fd75134062c9ac36ac23bcb63 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.cassandra.utils;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.cliffc.high_scale_lib.NonBlockingHashMap;
import org.slf4j.Logger;
import com.google.common.annotations.VisibleForTesting;
/**
* Logging that limits each log statement to firing based on time since the statement last fired.
*
* Every logger has a unique timer per statement. Minimum time between logging is set for each statement
* the first time it is used and a subsequent attempt to request that statement with a different minimum time will
* result in the original time being used. No warning is provided if there is a mismatch.
*
* If the statement is cached and used to log directly then only a volatile read will be required in the common case.
* If the Logger is cached then there is a single concurrent hash map lookup + the volatile read.
* If neither the logger nor the statement is cached then it is two concurrent hash map lookups + the volatile read.
*
*/
public class NoSpamLogger
{
/**
* Levels for programmatically specifying the severity of a log statement
*/
public enum Level
{
INFO, WARN, ERROR;
}
@VisibleForTesting
static interface Clock
{
long nanoTime();
}
@VisibleForTesting
static Clock CLOCK = new Clock()
{
public long nanoTime()
{
return System.nanoTime();
}
};
public class NoSpamLogStatement extends AtomicLong
{
private static final long serialVersionUID = 1L;
private final String statement;
private final long minIntervalNanos;
public NoSpamLogStatement(String statement, long minIntervalNanos)
{
this.statement = statement;
this.minIntervalNanos = minIntervalNanos;
}
private boolean shouldLog(long nowNanos)
{
long expected = get();
return nowNanos - expected >= minIntervalNanos && compareAndSet(expected, nowNanos);
}
public boolean log(Level l, long nowNanos, Object... objects)
{
if (!shouldLog(nowNanos)) return false;
switch (l)
{
case INFO:
wrapped.info(statement, objects);
break;
case WARN:
wrapped.warn(statement, objects);
break;
case ERROR:
wrapped.error(statement, objects);
break;
default:
throw new AssertionError();
}
return true;
}
public boolean info(long nowNanos, Object... objects)
{
return NoSpamLogStatement.this.log(Level.INFO, nowNanos, objects);
}
public boolean info(Object... objects)
{
return NoSpamLogStatement.this.info(CLOCK.nanoTime(), objects);
}
public boolean warn(long nowNanos, Object... objects)
{
return NoSpamLogStatement.this.log(Level.WARN, nowNanos, objects);
}
public boolean warn(Object... objects)
{
return NoSpamLogStatement.this.warn(CLOCK.nanoTime(), objects);
}
public boolean error(long nowNanos, Object... objects)
{
return NoSpamLogStatement.this.log(Level.ERROR, nowNanos, objects);
}
public boolean error(Object... objects)
{
return NoSpamLogStatement.this.error(CLOCK.nanoTime(), objects);
}
}
private static final NonBlockingHashMap<Logger, NoSpamLogger> wrappedLoggers = new NonBlockingHashMap<>();
@VisibleForTesting
static void clearWrappedLoggersForTest()
{
wrappedLoggers.clear();
}
public static NoSpamLogger getLogger(Logger logger, long minInterval, TimeUnit unit)
{
NoSpamLogger wrapped = wrappedLoggers.get(logger);
if (wrapped == null)
{
wrapped = new NoSpamLogger(logger, minInterval, unit);
NoSpamLogger temp = wrappedLoggers.putIfAbsent(logger, wrapped);
if (temp != null)
wrapped = temp;
}
return wrapped;
}
public static boolean log(Logger logger, Level level, long minInterval, TimeUnit unit, String message, Object... objects)
{
return log(logger, level, message, minInterval, unit, CLOCK.nanoTime(), message, objects);
}
public static boolean log(Logger logger, Level level, String key, long minInterval, TimeUnit unit, String message, Object... objects)
{
return log(logger, level, key, minInterval, unit, CLOCK.nanoTime(), message, objects);
}
public static boolean log(Logger logger, Level level, String key, long minInterval, TimeUnit unit, long nowNanos, String message, Object... objects)
{
NoSpamLogger wrapped = getLogger(logger, minInterval, unit);
NoSpamLogStatement statement = wrapped.getStatement(key, message);
return statement.log(level, nowNanos, objects);
}
public static NoSpamLogStatement getStatement(Logger logger, String message, long minInterval, TimeUnit unit)
{
NoSpamLogger wrapped = getLogger(logger, minInterval, unit);
return wrapped.getStatement(message);
}
private final Logger wrapped;
private final long minIntervalNanos;
private final NonBlockingHashMap<String, NoSpamLogStatement> lastMessage = new NonBlockingHashMap<>();
private NoSpamLogger(Logger wrapped, long minInterval, TimeUnit timeUnit)
{
this.wrapped = wrapped;
minIntervalNanos = timeUnit.toNanos(minInterval);
}
public boolean info(long nowNanos, String s, Object... objects)
{
return NoSpamLogger.this.log( Level.INFO, s, nowNanos, objects);
}
public boolean info(String s, Object... objects)
{
return NoSpamLogger.this.info(CLOCK.nanoTime(), s, objects);
}
public boolean warn(long nowNanos, String s, Object... objects)
{
return NoSpamLogger.this.log( Level.WARN, s, nowNanos, objects);
}
public boolean warn(String s, Object... objects)
{
return NoSpamLogger.this.warn(CLOCK.nanoTime(), s, objects);
}
public boolean error(long nowNanos, String s, Object... objects)
{
return NoSpamLogger.this.log( Level.ERROR, s, nowNanos, objects);
}
public boolean error(String s, Object... objects)
{
return NoSpamLogger.this.error(CLOCK.nanoTime(), s, objects);
}
public boolean log(Level l, String s, long nowNanos, Object... objects) {
return NoSpamLogger.this.getStatement(s, minIntervalNanos).log(l, nowNanos, objects);
}
public NoSpamLogStatement getStatement(String s)
{
return NoSpamLogger.this.getStatement(s, minIntervalNanos);
}
public NoSpamLogStatement getStatement(String key, String s)
{
return NoSpamLogger.this.getStatement(key, s, minIntervalNanos);
}
public NoSpamLogStatement getStatement(String s, long minInterval, TimeUnit unit) {
return NoSpamLogger.this.getStatement(s, unit.toNanos(minInterval));
}
public NoSpamLogStatement getStatement(String s, long minIntervalNanos)
{
return getStatement(s, s, minIntervalNanos);
}
public NoSpamLogStatement getStatement(String key, String s, long minIntervalNanos)
{
NoSpamLogStatement statement = lastMessage.get(key);
if (statement == null)
{
statement = new NoSpamLogStatement(s, minIntervalNanos);
NoSpamLogStatement temp = lastMessage.putIfAbsent(key, statement);
if (temp != null)
statement = temp;
}
return statement;
}
}