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

com.wavefront.opentracing.reporting.WavefrontSpanReporter Maven / Gradle / Ivy

Go to download

Implements OpenTracing API for collecting and sending tracing data to Wavefront from Java applications.

The newest version!
package com.wavefront.opentracing.reporting;

import com.wavefront.internal.reporter.WavefrontInternalReporter;
import com.wavefront.internal_reporter_java.io.dropwizard.metrics5.DeltaCounter;
import com.wavefront.internal_reporter_java.io.dropwizard.metrics5.MetricName;
import com.wavefront.opentracing.Reference;
import com.wavefront.opentracing.WavefrontSpan;
import com.wavefront.opentracing.WavefrontSpanContext;
import com.wavefront.sdk.common.WavefrontSender;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import static com.wavefront.opentracing.common.Constants.DEFAULT_SOURCE;

/**
 * The reporter which reports tracing spans to Wavefront via WavefrontSender.
 *
 * @author Vikram Raman ([email protected])
 */
public class WavefrontSpanReporter implements Reporter, Runnable {
  private static final Logger logger = Logger.getLogger(WavefrontSpanReporter.class.getName());

  private final WavefrontSender wavefrontSender;
  private final String source;
  private final LinkedBlockingQueue spanBuffer;
  private final Thread sendingThread;
  private final Random random;
  private final float logPercent;
  private final boolean reportSpanLogs;

  /**
   * Users create a WavefrontSpanReporter and provide it to the tracer, which upon initialization
   * sets this internal metrics reporter. Though unlikely, marked as volatile for thread safety.
   */
  private volatile WavefrontInternalReporter metricsReporter;
  private DeltaCounter spansDropped;
  private DeltaCounter spansReceived;
  private DeltaCounter reportErrors;

  private volatile boolean stop = false;

  public static final class Builder {
    private String source;
    private int maxQueueSize = 50000;
    private float logPercent = 0.1f;
    private boolean reportSpanLogs = true;

    public Builder() {
      this.source = getDefaultSource();
    }

    private static String getDefaultSource() {
      try {
        return InetAddress.getLocalHost().getHostName();
      } catch (UnknownHostException ex) {
        return DEFAULT_SOURCE;
      }
    }

    /**
     * Set the source for this reporter.
     *
     * @param source the source for all spans
     * @return {@code this}
     */
    public Builder withSource(String source) {
      this.source = source;
      return this;
    }

    /**
     * Set max queue size of in-memory buffer. Incoming spans are dropped if buffer is full.
     *
     * @param maxQueueSize Max queue size of in-memory buffer
     * @return {@code this}
     * @throws IllegalArgumentException if the queue size is not greater than 0
     */
    public Builder withMaxQueueSize(int maxQueueSize) {
      if (maxQueueSize <= 0) {
        throw new IllegalArgumentException("invalid max queue size");
      }
      this.maxQueueSize = maxQueueSize;
      return this;
    }

    /**
     * Set the percent of log messages to be logged. Defaults to 10%.
     *
     * @param percent a value between 0.0 and 1.0
     * @return {@code this}
     * @throws IllegalArgumentException if the percent is not between 0.0 and 1.0
     */
    public Builder withLoggingPercent(float percent) {
      if (percent < 0.0 || percent > 1.0) {
        throw new IllegalArgumentException("invalid logging percent");
      }
      this.logPercent = percent;
      return this;
    }

    /**
     * Disable the reporting of span logs.
     *
     * @return {@code this}
     */
    public Builder disableSpanLogReporting() {
      this.reportSpanLogs = false;
      return this;
    }

    /**
     * Builds a {@link WavefrontSpanReporter} for sending opentracing spans to a
     * WavefrontSender that can send those spans either be a via proxy or direct ingestion.
     *
     * @return {@link WavefrontSpanReporter}
     */
    public WavefrontSpanReporter build(WavefrontSender wavefrontSender) {
      return new WavefrontSpanReporter(wavefrontSender, this.source, this.maxQueueSize,
          this.logPercent, this.reportSpanLogs);
    }
  }

  private WavefrontSpanReporter(WavefrontSender wavefrontSender, String source, int maxQueueSize,
                                float logPercent, boolean reportSpanLogs) {
    this.wavefrontSender = wavefrontSender;
    this.source = source;
    this.spanBuffer = new LinkedBlockingQueue<>(maxQueueSize);
    this.random = new Random();
    this.logPercent = logPercent;
    this.reportSpanLogs = reportSpanLogs;

    sendingThread = new Thread(this, "wavefrontSpanReporter");
    sendingThread.setDaemon(true);
    sendingThread.start();
  }

  @Override
  public void run() {
    while (!stop) {
      try {
        WavefrontSpan span = spanBuffer.take();
        send(span);
      } catch (InterruptedException ex) {
        if (logger.isLoggable(Level.INFO)) {
          logger.info("reporting thread interrupted");
        }
      } catch (Throwable ex) {
        logger.log(Level.WARNING, "Error processing buffer", ex);
      }
    }
  }

  @Override
  public void report(WavefrontSpan span) {
    if (metricsReporter != null) {
      spansReceived.inc();
    }
    if (!spanBuffer.offer(span)) {
      if (metricsReporter != null) {
        spansDropped.inc();
      }
      if (loggingAllowed()) {
        logger.warning("Buffer full, dropping span: " + span);
        if (metricsReporter != null) {
          logger.warning("Total spans dropped: " + spansDropped.getCount());
        }
      }
    }
  }

  private void send(WavefrontSpan span) {
    try {
      WavefrontSpanContext ctx = span.context();
      List parentRefs = span.getParents();
      List followsRefs = span.getFollows();

      List parents = parentRefs == null ? null : parentRefs.stream().
          map(Reference::getSpanContext).
          map(WavefrontSpanContext::getSpanId).
          collect(Collectors.toList());

      List follows = followsRefs == null ? null : followsRefs.stream().
          map(Reference::getSpanContext).
          map(WavefrontSpanContext::getSpanId).
          collect(Collectors.toList());

      wavefrontSender.sendSpan(span.getOperationName(), span.getStartTimeMicros() / 1000,
          span.getDurationMicroseconds() / 1000, source, ctx.getTraceId(), ctx.getSpanId(),
          parents, follows, span.getTagsAsList(), reportSpanLogs ? span.getSpanLogs() : null);
    } catch (IOException e) {
      if (loggingAllowed()) {
        logger.log(Level.WARNING, "error reporting span: " + span, e);
      }
      if (metricsReporter != null) {
        reportErrors.inc();
        spansDropped.inc();
      }
    }
  }

  private boolean loggingAllowed() {
    return random.nextFloat() <= logPercent;
  }

  public String getSource() {
    return source;
  }

  public WavefrontSender getWavefrontSender() {
    return wavefrontSender;
  }

  @Override
  public int getFailureCount() {
    return wavefrontSender.getFailureCount();
  }

  public void setMetricsReporter(WavefrontInternalReporter metricsReporter) {
    this.metricsReporter = metricsReporter;

    // init internal metrics
    metricsReporter.newGauge(new MetricName("reporter.queue.size", Collections.emptyMap()),
        () -> (() -> (double) spanBuffer.size())
    );
    metricsReporter.newGauge(new MetricName("reporter.queue.remaining_capacity",
        Collections.emptyMap()), () -> (() -> (double) spanBuffer.remainingCapacity()));
    spansReceived = metricsReporter.newDeltaCounter(new MetricName("reporter.spans.received",
        Collections.emptyMap()));
    spansDropped = metricsReporter.newDeltaCounter(new MetricName("reporter.spans.dropped",
        Collections.emptyMap()));
    reportErrors = metricsReporter.newDeltaCounter(new MetricName("reporter.errors",
        Collections.emptyMap()));
  }

  @Override
  public void close() throws IOException {
    stop = true;
    try {
      // wait for 5 secs max
      sendingThread.join(5000);
    } catch (InterruptedException ex) {
      // no-op
    }
    // flush buffer & close client
    wavefrontSender.close();
  }

  @Override
  public void flush() {
    try {
      wavefrontSender.flush();
    } catch (IOException e) {
      logger.warning("WavefrontSender flush() failed.");
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy