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

com.wavefront.opentracing.WavefrontSpan 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;

import com.wavefront.internal_reporter_java.io.dropwizard.metrics5.DeltaCounter;
import com.wavefront.internal_reporter_java.io.dropwizard.metrics5.MetricName;
import com.wavefront.sdk.common.Constants;
import com.wavefront.sdk.common.Pair;
import com.wavefront.sdk.entities.tracing.SpanLog;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;

import io.opentracing.Span;
import io.opentracing.log.Fields;
import io.opentracing.tag.Tag;
import io.opentracing.tag.Tags;

import static com.wavefront.sdk.common.Constants.COMPONENT_TAG_KEY;
import static com.wavefront.sdk.common.Constants.DEBUG_TAG_KEY;
import static com.wavefront.sdk.common.Constants.NULL_TAG_VAL;
import static java.util.stream.Collectors.toMap;

/**
 * Represents a thread-safe Wavefront trace span based on OpenTracing's {@link Span}.
 *
 * @author Vikram Raman ([email protected])
 */
@ThreadSafe
public class WavefrontSpan implements Span {

  private final WavefrontTracer tracer;
  private final long startTimeMicros;
  private final long startTimeNanos;
  @Nullable
  private final List> tags;
  private final List parents;
  private final List follows;
  @Nullable
  private final DeltaCounter spansDiscarded;

  @Nullable
  private Map> singleValuedTags;
  private String operationName;
  private long durationMicroseconds;
  private WavefrontSpanContext spanContext;
  private Boolean forceSampling = null;
  private boolean finished = false;
  private boolean isError = false;
  @Nullable
  private List spanLogs;

  // Store it as a member variable so that we can efficiently retrieve the component tag.
  private String componentTagValue = NULL_TAG_VAL;

  private static Set SINGLE_VALUED_TAG_KEYS = new HashSet<>(Arrays.asList(
      Constants.APPLICATION_TAG_KEY, Constants.SERVICE_TAG_KEY, Constants.CLUSTER_TAG_KEY,
      Constants.SHARD_TAG_KEY));

  WavefrontSpan(WavefrontTracer tracer, String operationName, WavefrontSpanContext spanContext,
                long startTimeMicros, long startTimeNanos, List parents,
                List follows, List> tags) {
    this.tracer = tracer;
    this.operationName = operationName;
    this.spanContext = spanContext;
    this.startTimeMicros = startTimeMicros;
    this.startTimeNanos = startTimeNanos;
    this.parents = parents;
    this.follows = follows;

    spansDiscarded = tracer.getWfInternalReporter() == null ? null :
        tracer.getWfInternalReporter().newDeltaCounter(
            new MetricName("spans.discarded", Collections.emptyMap()));

    List> globalTags = tracer.getTags();
    this.tags = (globalTags == null || globalTags.isEmpty()) && (tags == null || tags.isEmpty()) ?
      null : new ArrayList<>();
    this.singleValuedTags = null;
    this.spanLogs = null;
    if (globalTags != null) {
      for (Pair tag : globalTags) {
        setTagObject(tag._1, tag._2);
      }
    }
    if (tags != null) {
      for (Pair tag : tags) {
        setTagObject(tag._1, tag._2);
      }
    }
  }

  @Override
  public synchronized WavefrontSpanContext context() {
    return spanContext;
  }

  @Override
  public WavefrontSpan setTag(String key, String value) {
    return setTagObject(key, value);
  }

  @Override
  public WavefrontSpan setTag(String key, boolean value) {
    return setTagObject(key, value);
  }

  @Override
  public WavefrontSpan setTag(String key, Number value) {
    return setTagObject(key, value);
  }

  @Override
  public  Span setTag(Tag tag, T value) {
    return setTagObject(tag.getKey(), value);
  }

  private synchronized WavefrontSpan setTagObject(String key, Object value) {
    if (key != null && !key.isEmpty() && value != null && value.toString() != null &&
        !(value.toString().isEmpty())) {
      Pair tag = Pair.of(key, value.toString());

      // if tag should be single-valued, replace the previous value if it exists
      if (isSingleValuedTagKey(key)) {
        if (singleValuedTags == null) {
          singleValuedTags = new HashMap<>();
        }
        if (singleValuedTags.containsKey(key)) {
          tags.remove(singleValuedTags.get(key));
        }
        singleValuedTags.put(key, tag);
      }

      tags.add(tag);

      if (key.equals(COMPONENT_TAG_KEY)) {
        componentTagValue = value.toString();
      }

      // allow span to be reported if sampling.priority is > 0.
      if (Tags.SAMPLING_PRIORITY.getKey().equals(key) && value instanceof Number) {
        int priority = ((Number) value).intValue();
        forceSampling = priority > 0 ? Boolean.TRUE : Boolean.FALSE;
        spanContext = spanContext.withSamplingDecision(forceSampling);
      }

      // allow span to be reported if debug is set to true.
      if (forceSampling == null || !forceSampling) {
        if (key.equals(DEBUG_TAG_KEY) && value != null && value.toString().equals("true")) {
          forceSampling = Boolean.TRUE;
          spanContext = spanContext.withSamplingDecision(forceSampling);
        }
      }

      if (Tags.ERROR.getKey().equals(key)) {
        isError = true;
      }

      // allow span to be reported if error tag is set.
      if (forceSampling == null && Tags.ERROR.getKey().equals(key)) {
        if (value instanceof Boolean && (Boolean) value) {
          forceSampling = Boolean.TRUE;
          spanContext = spanContext.withSamplingDecision(forceSampling);
        }
      }
    }
    return this;
  }

  public boolean isError() {
    return isError;
  }

  @Override
  public WavefrontSpan log(Map map) {
    updateSpanLogsInternal(getCurrentTimeMicros(), map);
    return this;
  }

  @Override
  public WavefrontSpan log(long currentTimeMicros, Map map) {
    updateSpanLogsInternal(currentTimeMicros, map);
    return this;
  }

  @Override
  public WavefrontSpan log(String s) {
    updateSpanLogsInternal(getCurrentTimeMicros(), Collections.singletonMap(Fields.EVENT, s));
    return this;
  }

  @Override
  public WavefrontSpan log(long currentTimeMicros, String s) {
    updateSpanLogsInternal(currentTimeMicros, Collections.singletonMap(Fields.EVENT, s));
    return this;
  }

  private synchronized WavefrontSpan updateSpanLogsInternal(
      long currentTimeMicros, Map fields) {
    if (spanLogs == null) {
      spanLogs = new ArrayList<>();
    }
    if (fields != null) {
      Map finalFields = fields.entrySet().stream().collect(
          toMap(Map.Entry::getKey, entry -> Objects.toString(entry.getValue(), "")));
      spanLogs.add(new SpanLog(currentTimeMicros, finalFields));
    }
    return this;
  }

  @Override
  public synchronized WavefrontSpan setBaggageItem(String key, String value) {
    spanContext = spanContext.withBaggageItem(key, value);
    return this;
  }

  @Override
  @Nullable
  public synchronized String getBaggageItem(String key) {
    return this.spanContext.getBaggageItem(key);
  }

  @Override
  public synchronized WavefrontSpan setOperationName(String s) {
    operationName = s;
    return this;
  }

  @Override
  public void finish() {
    if (startTimeNanos != 0) {
      long duration = System.nanoTime() - startTimeNanos;
      doFinish(TimeUnit.NANOSECONDS.toMicros(duration));
    } else {
      // Ideally finish(finishTimeMicros) should be called if user provided startTimeMicros
      finish(tracer.currentTimeMicros());
    }
  }

  @Override
  public void finish(long finishTimeMicros) {
    doFinish(finishTimeMicros-startTimeMicros);
  }

  private void doFinish(long durationMicros) {
    synchronized (this) {
      if (finished) {
        return;
      }
      this.durationMicroseconds = durationMicros;
      finished = true;
    }

    // perform another sampling for duration based samplers
    if (forceSampling == null && (!spanContext.isSampled() || !spanContext.getSamplingDecision())) {
      boolean decision = tracer.sample(operationName,
          spanContext.getTraceId().getLeastSignificantBits(), durationMicros/1000,
          spanContext.isSampled() ? spanContext.getSamplingDecision() : true);
      spanContext = decision ? spanContext.withSamplingDecision(decision) : spanContext;
    }
    // only report spans if the sampling decision allows it
    if (spanContext.isSampled() && spanContext.getSamplingDecision()) {
      tracer.reportSpan(this);
    } else if (spansDiscarded != null) {
      spansDiscarded.inc();
    }
    // irrespective of sampling, report wavefront-generated metrics/histograms to Wavefront
    tracer.reportWavefrontGeneratedData(this);
  }

  public synchronized String getOperationName() {
    return operationName;
  }

  public long getStartTimeMicros() {
    return startTimeMicros;
  }

  public synchronized long getDurationMicroseconds() {
    return durationMicroseconds;
  }

  /**
   * Gets the list of multi-valued tags.
   *
   * @return The list of tags.
   */
  public synchronized List> getTagsAsList() {
    if (tags == null) {
      return Collections.emptyList();
    }
    return Collections.unmodifiableList(tags);
  }

  /**
   * Gets the map of multi-valued tags.
   *
   * @return The map of tags.
   */
  public synchronized Map> getTagsAsMap() {
    if (tags == null) {
      return Collections.emptyMap();
    }
    return Collections.unmodifiableMap(
        tags.stream().collect(
            Collectors.groupingBy(
                p -> p._1,
                Collectors.mapping(p -> p._2, Collectors.toList())
            )
        )
    );
  }

  /**
   * Gets the list of span logs.
   *
   * @return The list of span logs.
   */
  public synchronized List getSpanLogs() {
    if (spanLogs == null) {
      return Collections.emptyList();
    }
    return Collections.unmodifiableList(spanLogs);
  }

  /**
   * Returns the tag value for the given single-valued tag key. Returns null if no such tag exists.
   *
   * @param key The single-valued tag key.
   * @return The tag value.
   */
  @Nullable
  public synchronized String getSingleValuedTagValue(String key) {
    if (singleValuedTags == null || !singleValuedTags.containsKey(key)) {
      return null;
    }
    return singleValuedTags.get(key)._2;
  }

  public List getParents() {
    if (parents == null) {
      return Collections.emptyList();
    }
    return Collections.unmodifiableList(parents);
  }

  public List getFollows() {
    if (follows == null) {
      return Collections.emptyList();
    }
    return Collections.unmodifiableList(follows);
  }

  public String getComponentTagValue() {
    return componentTagValue;
  }

  @Override
  public String toString() {
    return "WavefrontSpan{" +
        "operationName='" + operationName + '\'' +
        ", startTimeMicros=" + startTimeMicros +
        ", durationMicroseconds=" + durationMicroseconds +
        ", tags=" + tags +
        ", spanContext=" + spanContext +
        ", parents=" + parents +
        ", follows=" + follows +
        '}';
  }

  /**
   * Provides current system time in micros.
   */
  private long getCurrentTimeMicros() {
    return System.currentTimeMillis() * 1000;
  }

  /**
   * Returns a boolean indicated whether the given tag key must be single-valued or not.
   *
   * @param key The tag key.
   * @return true if the key must be single-valued, false otherwise.
   */
  public static boolean isSingleValuedTagKey(String key) {
    return SINGLE_VALUED_TAG_KEYS.contains(key);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy