All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wavefront.common.logger.SamplingLogger Maven / Gradle / Ivy

There is a newer version: 2023-22.3
Show newest version
package com.wavefront.common.logger;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.wavefront.data.ReportableEntityType;

import javax.annotation.Nullable;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

/**
 * A sampling logger that can be enabled and disabled dynamically
 * by setting its level to {@code Level.FINEST}.
 *
 * @author [email protected]
 */
public class SamplingLogger extends DelegatingLogger {
  private static final Random RANDOM = new Random();

  private final ReportableEntityType entityType;
  private final double samplingRate;
  private final boolean alwaysActive;
  private final Consumer statusChangeConsumer;
  private final AtomicBoolean loggingActive = new AtomicBoolean(false);

  /**
   * @param entityType   entity type (used in info messages only).
   * @param delegate     delegate logger name.
   * @param samplingRate sampling rate for logging [0..1].
   * @param alwaysActive whether this logger is always active regardless of currently set log level.
   */
  public SamplingLogger(ReportableEntityType entityType,
                        Logger delegate,
                        double samplingRate,
                        boolean alwaysActive,
                        @Nullable Consumer statusChangeConsumer) {
    super(delegate);
    Preconditions.checkArgument(samplingRate >= 0, "Sampling rate should be positive!");
    Preconditions.checkArgument(samplingRate <= 1, "Sampling rate should not be be > 1!");
    this.entityType = entityType;
    this.samplingRate = samplingRate;
    this.alwaysActive = alwaysActive;
    this.statusChangeConsumer = statusChangeConsumer;
    refreshLoggerState();
    new Timer("Timer-sampling-logger-" + delegate.getName()).scheduleAtFixedRate(new TimerTask() {
      @Override
      public void run() {
        refreshLoggerState();
      }
    }, 1000, 1000);
  }

  @Override
  public boolean isLoggable(Level level) {
    if (level == Level.FINEST) {
      return (alwaysActive || loggingActive.get()) &&
          (samplingRate >= 1.0d || (samplingRate > 0.0d && RANDOM.nextDouble() < samplingRate));
    } else {
      return delegate.isLoggable(level);
    }
  }

  /**
   * Checks the logger state and writes the message in the log if appropriate.
   * We log valid points only if the system property wavefront.proxy.logpoints is true
   * (for legacy reasons) or the delegate logger's log level is set to "ALL"
   * (i.e. if Level.FINEST is considered loggable). This is done to prevent introducing
   * additional overhead into the critical path, as well as prevent accidentally logging points
   * into the main log. Additionally, honor sample rate limit, if set.
   *
   * @param level   log level.
   * @param message string to write to log.
   */
  @Override
  public void log(Level level, String message) {
      if ((alwaysActive || loggingActive.get()) &&
        (samplingRate >= 1.0d || (samplingRate > 0.0d && RANDOM.nextDouble() < samplingRate))) {
      log(new LogRecord(level, message));
    }
  }

  @VisibleForTesting
  void refreshLoggerState() {
    boolean finestLoggable = delegate.isLoggable(Level.FINEST);
    if (loggingActive.compareAndSet(!finestLoggable, finestLoggable)) {
      if (statusChangeConsumer != null) {
        String status = loggingActive.get() ?
            "enabled with " + (samplingRate * 100) + "% sampling" :
            "disabled";
        statusChangeConsumer.accept("Valid " + entityType.toString() + " logging is now " + status);
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy