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

com.wavefront.agent.logsharvesting.LogsIngester Maven / Gradle / Ivy

package com.wavefront.agent.logsharvesting;

import com.github.benmanes.caffeine.cache.Ticker;
import com.google.common.annotations.VisibleForTesting;
import com.wavefront.agent.config.ConfigurationException;
import com.wavefront.agent.config.LogsIngestionConfig;
import com.wavefront.agent.config.MetricMatcher;
import com.wavefront.agent.handlers.ReportableEntityHandlerFactory;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.Counter;
import com.yammer.metrics.core.Metric;
import com.yammer.metrics.core.MetricName;
import com.yammer.metrics.core.MetricsRegistry;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import wavefront.report.TimeSeries;

/**
 * Consumes log messages sent to {@link #ingestLog(LogsMessage)}. Configures and starts the periodic
 * flush of consumed metric data to Wavefront.
 *
 * @author Mori Bellamy ([email protected])
 */
public class LogsIngester {
  protected static final Logger logger = Logger.getLogger(LogsIngester.class.getCanonicalName());
  private static final ReadProcessor readProcessor = new ReadProcessor();
  private final FlushProcessor flushProcessor;
  // A map from "true" to the currently loaded logs ingestion config.
  @VisibleForTesting final LogsIngestionConfigManager logsIngestionConfigManager;
  private final Counter unparsed, parsed;
  private final Supplier currentMillis;
  private final MetricsReporter metricsReporter;
  private EvictingMetricsRegistry evictingMetricsRegistry;

  /**
   * Create an instance using system clock.
   *
   * @param handlerFactory factory for point handlers and histogram handlers
   * @param logsIngestionConfigSupplier supplied configuration object for logs harvesting. May be
   *     reloaded. Must return "null" on any problems, as opposed to throwing.
   * @param prefix all harvested metrics start with this prefix
   */
  public LogsIngester(
      ReportableEntityHandlerFactory handlerFactory,
      Supplier logsIngestionConfigSupplier,
      String prefix)
      throws ConfigurationException {
    this(
        handlerFactory,
        logsIngestionConfigSupplier,
        prefix,
        System::currentTimeMillis,
        Ticker.systemTicker());
  }

  /**
   * Create an instance using provided clock and nano.
   *
   * @param handlerFactory factory for point handlers and histogram handlers
   * @param logsIngestionConfigSupplier supplied configuration object for logs harvesting. May be
   *     reloaded. Must return "null" on any problems, as opposed to throwing.
   * @param prefix all harvested metrics start with this prefix
   * @param currentMillis supplier of the current time in millis
   * @param ticker nanosecond-precision clock for Caffeine cache.
   * @throws ConfigurationException if the first config from logsIngestionConfigSupplier is null
   */
  @VisibleForTesting
  LogsIngester(
      ReportableEntityHandlerFactory handlerFactory,
      Supplier logsIngestionConfigSupplier,
      String prefix,
      Supplier currentMillis,
      Ticker ticker)
      throws ConfigurationException {
    logsIngestionConfigManager =
        new LogsIngestionConfigManager(
            logsIngestionConfigSupplier,
            removedMetricMatcher -> evictingMetricsRegistry.evict(removedMetricMatcher));
    LogsIngestionConfig logsIngestionConfig = logsIngestionConfigManager.getConfig();

    MetricsRegistry metricsRegistry = new MetricsRegistry();
    this.evictingMetricsRegistry =
        new EvictingMetricsRegistry(
            metricsRegistry,
            logsIngestionConfig.expiryMillis,
            true,
            logsIngestionConfig.useDeltaCounters,
            currentMillis,
            ticker);

    // Logs harvesting metrics.
    this.unparsed = Metrics.newCounter(new MetricName("logsharvesting", "", "unparsed"));
    this.parsed = Metrics.newCounter(new MetricName("logsharvesting", "", "parsed"));
    this.currentMillis = currentMillis;
    this.flushProcessor =
        new FlushProcessor(
            currentMillis,
            logsIngestionConfig.useWavefrontHistograms,
            logsIngestionConfig.reportEmptyHistogramStats);

    // Continually flush user metrics to Wavefront.
    this.metricsReporter =
        new MetricsReporter(
            metricsRegistry, flushProcessor, "FilebeatMetricsReporter", handlerFactory, prefix);
  }

  public void start() {
    long interval = this.logsIngestionConfigManager.getConfig().aggregationIntervalSeconds;
    this.metricsReporter.start(interval, TimeUnit.SECONDS);
    // check for expired cached items and trigger evictions every 2x aggregationIntervalSeconds
    // but no more than once a minute. This is a workaround for the issue that surfaces mostly
    // during testing, when there are no matching log messages at all for more than expiryMillis,
    // which means there is no cache access and no time-based evictions are performed.
    Executors.newSingleThreadScheduledExecutor()
        .scheduleWithFixedDelay(
            evictingMetricsRegistry::cleanUp,
            interval * 3 / 2,
            Math.max(60, interval * 2),
            TimeUnit.SECONDS);
  }

  public void flush() {
    this.metricsReporter.run();
  }

  @VisibleForTesting
  MetricsReporter getMetricsReporter() {
    return metricsReporter;
  }

  public void ingestLog(LogsMessage logsMessage) {
    LogsIngestionConfig logsIngestionConfig = logsIngestionConfigManager.getConfig();

    boolean success = false;
    for (MetricMatcher metricMatcher : logsIngestionConfig.counters) {
      success |= maybeIngestLog(evictingMetricsRegistry::getCounter, metricMatcher, logsMessage);
    }

    for (MetricMatcher metricMatcher : logsIngestionConfig.gauges) {
      success |= maybeIngestLog(evictingMetricsRegistry::getGauge, metricMatcher, logsMessage);
    }

    for (MetricMatcher metricMatcher : logsIngestionConfig.histograms) {
      success |= maybeIngestLog(evictingMetricsRegistry::getHistogram, metricMatcher, logsMessage);
    }

    if (success) {
      parsed.inc();
    } else {
      unparsed.inc();
    }
  }

  private boolean maybeIngestLog(
      BiFunction metricLoader,
      MetricMatcher metricMatcher,
      LogsMessage logsMessage) {
    Double[] output = {null};
    TimeSeries timeSeries = metricMatcher.timeSeries(logsMessage, output);
    if (timeSeries == null) return false;
    MetricName metricName = TimeSeriesUtils.toMetricName(timeSeries);
    Metric metric = metricLoader.apply(metricName, metricMatcher);
    try {
      metric.processWith(readProcessor, metricName, new ReadProcessorContext(output[0]));
    } catch (Exception e) {
      logger.log(Level.SEVERE, "Could not process metric " + metricName.toString(), e);
    }
    return true;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy