// Path: core/src/main/java/com/github/phantomthief/tuple/Tuple.java
// public static <A, B> TwoTuple<A, B> tuple(final A a, final B b) {
//     return new TwoTuple<>(a, b);
// }
// 
// Path: core/src/main/java/com/github/phantomthief/tuple/ThreeTuple.java
// public class ThreeTuple<A, B, C> extends TwoTuple<A, B> {
// 
//     public final C third;
// 
//     /**
//      * use {@link Tuple#tuple(Object, Object, Object)} instead
//      */
//     @Deprecated
//     public ThreeTuple(final A a, final B b, final C c) {
//         super(a, b);
//         third = c;
//     }
// 
//     public C getThird() {
//         return third;
//     }
// 
//     @Override
//     public String toString() {
//         return "(" + first + ", " + second + ", " + third + ")";
//     }
// 
//     @Override
//     public int hashCode() {
//         final int prime = 31;
//         int result = super.hashCode();
//         result = prime * result + (third == null ? 0 : third.hashCode());
//         return result;
//     }
// 
//     @Override
//     public boolean equals(final Object obj) {
//         if (this == obj) {
//             return true;
//         }
//         if (!super.equals(obj)) {
//             return false;
//         }
//         if (getClass() != obj.getClass()) {
//             return false;
//         }
//         final ThreeTuple<?, ?, ?> other = (ThreeTuple<?, ?, ?>) obj;
//         if (third == null) {
//             if (other.third != null) {
//                 return false;
//             }
//         } else if (!third.equals(other.third)) {
//             return false;
//         }
//         return true;
//     }
// 
// }


import static com.github.phantomthief.tuple.Tuple.tuple;
import static org.slf4j.spi.LocationAwareLogger.DEBUG_INT;
import static org.slf4j.spi.LocationAwareLogger.ERROR_INT;
import static org.slf4j.spi.LocationAwareLogger.INFO_INT;
import static org.slf4j.spi.LocationAwareLogger.TRACE_INT;
import static org.slf4j.spi.LocationAwareLogger.WARN_INT;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nullable;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.spi.LocationAwareLogger;
import com.github.phantomthief.tuple.ThreeTuple;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
package com.github.phantomthief.util;






/**
 * 使用 {@link SimpleRateLimiter} 来控制打 log 输出的频率，避免出现 log flood 占用过高的 CPU <p>
 * 对于需要用一个 logger 打不同类型的日志，又不希望不同日志输出频率互相干扰（比如高频日志把低频日志淹没）<p>
 * 可以使用 {@link #perMessageRateLogger(Logger)} 方式构建 logger，这时候，不同日志的区分方式是以 log.info(msg, args...);<p>
 * 中第一个参数 msg 来区分，相同的 msg 会被认为是相同类型的日志，从而共享相同的频次限制；<p>
 *
 * 使用方法:
 * <pre>
 * {@code
 *
 * class MyObject {
 *   private static final Logger logger = LoggerFactory.getLogger(MyObject.class); // normal one
 *   private static final Logger rateLogger = RateLogger.rateLogger(logger); // wrap as a rate one
 *
 *   void foo() {
 *     rateLogger.info("my message"); // use as normal logger.
 *   }
 * }
 * }
 * </pre>
 *
 * @author w.vela
 * Created on 2017-02-24.
 */
public class RateLogger implements Logger {

    /**
     * fully qualified class name, for short
     */
    private static final String FQCN = RateLogger.class.getName();

    private static final double DEFAULT_PERMITS_PER_SECOND = 1;
    private static final int MAX_PER_FORMAT_CACHE_SIZE = 100;
    // 直接使用Map来cache RateLogger。Logger的数量是有限的，LogBack也是使用了Map来Cache，所以没必要用一个支持evict的Cache。
// Path: core/src/main/java/com/github/phantomthief/tuple/Tuple.java
// public static <A, B> TwoTuple<A, B> tuple(final A a, final B b) {
//     return new TwoTuple<>(a, b);
// }
// 
// Path: core/src/main/java/com/github/phantomthief/tuple/ThreeTuple.java
// public class ThreeTuple<A, B, C> extends TwoTuple<A, B> {
// 
//     public final C third;
// 
//     /**
//      * use {@link Tuple#tuple(Object, Object, Object)} instead
//      */
//     @Deprecated
//     public ThreeTuple(final A a, final B b, final C c) {
//         super(a, b);
//         third = c;
//     }
// 
//     public C getThird() {
//         return third;
//     }
// 
//     @Override
//     public String toString() {
//         return "(" + first + ", " + second + ", " + third + ")";
//     }
// 
//     @Override
//     public int hashCode() {
//         final int prime = 31;
//         int result = super.hashCode();
//         result = prime * result + (third == null ? 0 : third.hashCode());
//         return result;
//     }
// 
//     @Override
//     public boolean equals(final Object obj) {
//         if (this == obj) {
//             return true;
//         }
//         if (!super.equals(obj)) {
//             return false;
//         }
//         if (getClass() != obj.getClass()) {
//             return false;
//         }
//         final ThreeTuple<?, ?, ?> other = (ThreeTuple<?, ?, ?>) obj;
//         if (third == null) {
//             if (other.third != null) {
//                 return false;
//             }
//         } else if (!third.equals(other.third)) {
//             return false;
//         }
//         return true;
//     }
// 
// }


// Path: core/src/main/java/com/github/phantomthief/util/RateLogger.java
import static com.github.phantomthief.tuple.Tuple.tuple;
import static org.slf4j.spi.LocationAwareLogger.DEBUG_INT;
import static org.slf4j.spi.LocationAwareLogger.ERROR_INT;
import static org.slf4j.spi.LocationAwareLogger.INFO_INT;
import static org.slf4j.spi.LocationAwareLogger.TRACE_INT;
import static org.slf4j.spi.LocationAwareLogger.WARN_INT;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nullable;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.spi.LocationAwareLogger;
import com.github.phantomthief.tuple.ThreeTuple;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

package com.github.phantomthief.util;






/**
 * 使用 {@link SimpleRateLimiter} 来控制打 log 输出的频率，避免出现 log flood 占用过高的 CPU <p>
 * 对于需要用一个 logger 打不同类型的日志，又不希望不同日志输出频率互相干扰（比如高频日志把低频日志淹没）<p>
 * 可以使用 {@link #perMessageRateLogger(Logger)} 方式构建 logger，这时候，不同日志的区分方式是以 log.info(msg, args...);<p>
 * 中第一个参数 msg 来区分，相同的 msg 会被认为是相同类型的日志，从而共享相同的频次限制；<p>
 *
 * 使用方法:
 * <pre>
 * {@code
 *
 * class MyObject {
 *   private static final Logger logger = LoggerFactory.getLogger(MyObject.class); // normal one
 *   private static final Logger rateLogger = RateLogger.rateLogger(logger); // wrap as a rate one
 *
 *   void foo() {
 *     rateLogger.info("my message"); // use as normal logger.
 *   }
 * }
 * }
 * </pre>
 *
 * @author w.vela
 * Created on 2017-02-24.
 */
public class RateLogger implements Logger {

    /**
     * fully qualified class name, for short
     */
    private static final String FQCN = RateLogger.class.getName();

    private static final double DEFAULT_PERMITS_PER_SECOND = 1;
    private static final int MAX_PER_FORMAT_CACHE_SIZE = 100;
    // 直接使用Map来cache RateLogger。Logger的数量是有限的，LogBack也是使用了Map来Cache，所以没必要用一个支持evict的Cache。
    private static final ConcurrentMap<ThreeTuple<String, Double, Boolean>, RateLogger> CACHE = new ConcurrentHashMap<>();