
com.wavefront.opentracing.WavefrontSpan Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of wavefront-opentracing-sdk-java Show documentation
Show all versions of wavefront-opentracing-sdk-java Show documentation
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