blob: bba9bab232273452f9c5cbe5ad6c9fe9e8b502cc [file] [log] [blame]
/*
* Copyright 2018, OpenCensus Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.opencensus.contrib.spring.sleuth.v1x;
import io.opencensus.common.ExperimentalApi;
import java.util.Random;
import java.util.concurrent.Callable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.sleuth.Sampler;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.SpanReporter;
import org.springframework.cloud.sleuth.TraceKeys;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.async.SpanContinuingTraceCallable;
import org.springframework.cloud.sleuth.instrument.async.SpanContinuingTraceRunnable;
import org.springframework.cloud.sleuth.log.SpanLogger;
import org.springframework.cloud.sleuth.util.ExceptionUtils;
import org.springframework.cloud.sleuth.util.SpanNameUtil;
/*>>>
import org.checkerframework.checker.nullness.qual.Nullable;
*/
/**
* Sleuth Tracer that keeps a synchronized OpenCensus Span. This class is based on Sleuth's {@code
* DefaultTracer}.
*
* @since 0.16
*/
@ExperimentalApi
public class OpenCensusSleuthTracer implements Tracer {
private static final Log log = LogFactory.getLog(OpenCensusSleuthTracer.class);
private final Sampler defaultSampler;
private final Random random;
private final SpanNamer spanNamer;
private final SpanLogger spanLogger;
private final SpanReporter spanReporter;
private final TraceKeys traceKeys;
private final boolean traceId128;
/** Basic constructor holding components for implementing Sleuth's {@link Tracer} interface. */
public OpenCensusSleuthTracer(
Sampler defaultSampler,
Random random,
SpanNamer spanNamer,
SpanLogger spanLogger,
SpanReporter spanReporter,
TraceKeys traceKeys) {
this(
defaultSampler,
random,
spanNamer,
spanLogger,
spanReporter,
traceKeys,
/* traceId128= */ false);
}
/** Basic constructor holding components for implementing Sleuth's {@link Tracer} interface. */
public OpenCensusSleuthTracer(
Sampler defaultSampler,
Random random,
SpanNamer spanNamer,
SpanLogger spanLogger,
SpanReporter spanReporter,
TraceKeys traceKeys,
boolean traceId128) {
this.defaultSampler = defaultSampler;
this.random = random;
this.spanNamer = spanNamer;
this.spanLogger = spanLogger;
this.spanReporter = spanReporter;
this.traceId128 = traceId128;
this.traceKeys = traceKeys != null ? traceKeys : new TraceKeys();
}
@Override
@javax.annotation.Nullable
public Span createSpan(String name, /*@Nullable*/ Span parent) {
if (parent == null) {
return createSpan(name);
}
return continueSpan(createChild(parent, name));
}
@Override
@javax.annotation.Nullable
public Span createSpan(String name) {
return this.createSpan(name, this.defaultSampler);
}
@Override
@javax.annotation.Nullable
public Span createSpan(String name, /*@Nullable*/ Sampler sampler) {
String shortenedName = SpanNameUtil.shorten(name);
Span span;
if (isTracing()) {
span = createChild(getCurrentSpan(), shortenedName);
} else {
long id = createId();
span =
Span.builder()
.name(shortenedName)
.traceIdHigh(this.traceId128 ? createTraceIdHigh() : 0L)
.traceId(id)
.spanId(id)
.build();
if (sampler == null) {
sampler = this.defaultSampler;
}
span = sampledSpan(span, sampler);
this.spanLogger.logStartedSpan(null, span);
}
return continueSpan(span);
}
@Override
@javax.annotation.Nullable
public Span detach(/*@Nullable*/ Span span) {
if (span == null) {
return null;
}
Span current = OpenCensusSleuthSpanContextHolder.getCurrentSpan();
if (current == null) {
if (log.isTraceEnabled()) {
log.trace(
"Span in the context is null so something has already detached the span. "
+ "Won't do anything about it");
}
return null;
}
if (!span.equals(current)) {
ExceptionUtils.warn(
"Tried to detach trace span but "
+ "it is not the current span: "
+ span
+ ". You may have forgotten to close or detach "
+ current);
} else {
OpenCensusSleuthSpanContextHolder.removeCurrentSpan();
}
return span.getSavedSpan();
}
@Override
@javax.annotation.Nullable
public Span close(/*@Nullable*/ Span span) {
if (span == null) {
return null;
}
final Span savedSpan = span.getSavedSpan();
Span current = OpenCensusSleuthSpanContextHolder.getCurrentSpan();
if (current == null || !span.equals(current)) {
ExceptionUtils.warn(
"Tried to close span but it is not the current span: "
+ span
+ ". You may have forgotten to close or detach "
+ current);
} else {
span.stop();
if (savedSpan != null && span.getParents().contains(savedSpan.getSpanId())) {
this.spanReporter.report(span);
this.spanLogger.logStoppedSpan(savedSpan, span);
} else {
if (!span.isRemote()) {
this.spanReporter.report(span);
this.spanLogger.logStoppedSpan(null, span);
}
}
OpenCensusSleuthSpanContextHolder.close(
new OpenCensusSleuthSpanContextHolder.SpanFunction() {
@Override
public void apply(Span closedSpan) {
// Note: hasn't this already been done?
OpenCensusSleuthTracer.this.spanLogger.logStoppedSpan(savedSpan, closedSpan);
}
});
}
return savedSpan;
}
Span createChild(/*@Nullable*/ Span parent, String name) {
String shortenedName = SpanNameUtil.shorten(name);
long id = createId();
if (parent == null) {
Span span =
Span.builder()
.name(shortenedName)
.traceIdHigh(this.traceId128 ? createTraceIdHigh() : 0L)
.traceId(id)
.spanId(id)
.build();
span = sampledSpan(span, this.defaultSampler);
this.spanLogger.logStartedSpan(null, span);
return span;
} else {
if (!isTracing()) {
OpenCensusSleuthSpanContextHolder.push(parent, /* autoClose= */ true);
}
Span span =
Span.builder()
.name(shortenedName)
.traceIdHigh(parent.getTraceIdHigh())
.traceId(parent.getTraceId())
.parent(parent.getSpanId())
.spanId(id)
.processId(parent.getProcessId())
.savedSpan(parent)
.exportable(parent.isExportable())
.baggage(parent.getBaggage())
.build();
this.spanLogger.logStartedSpan(parent, span);
return span;
}
}
private static Span sampledSpan(Span span, Sampler sampler) {
if (!sampler.isSampled(span)) {
// Copy everything, except set exportable to false
return Span.builder()
.begin(span.getBegin())
.traceIdHigh(span.getTraceIdHigh())
.traceId(span.getTraceId())
.spanId(span.getSpanId())
.name(span.getName())
.exportable(false)
.build();
}
return span;
}
// Encodes a timestamp into the upper 32-bits, so that it can be converted to an Amazon trace ID.
// For example, an Amazon trace ID is composed of the following:
// |-- 32 bits for epoch seconds -- | -- 96 bits for random data -- |
//
// To support this, Span#getTraceIdHigh() holds the epoch seconds and first 32 random bits: and
// Span#getTraceId() holds the remaining 64 random bits.
private long createTraceIdHigh() {
long epochSeconds = System.currentTimeMillis() / 1000;
int random = this.random.nextInt();
return (epochSeconds & 0xffffffffL) << 32 | (random & 0xffffffffL);
}
private long createId() {
return this.random.nextLong();
}
@Override
@javax.annotation.Nullable
public Span continueSpan(/*@Nullable*/ Span span) {
if (span != null) {
this.spanLogger.logContinuedSpan(span);
} else {
return null;
}
Span newSpan = createContinuedSpan(span, OpenCensusSleuthSpanContextHolder.getCurrentSpan());
OpenCensusSleuthSpanContextHolder.setCurrentSpan(newSpan);
return newSpan;
}
@SuppressWarnings("deprecation")
private static Span createContinuedSpan(Span span, /*@Nullable*/ Span saved) {
if (saved == null && span.getSavedSpan() != null) {
saved = span.getSavedSpan();
}
return new Span(span, saved);
}
@Override
@javax.annotation.Nullable
public Span getCurrentSpan() {
return OpenCensusSleuthSpanContextHolder.getCurrentSpan();
}
@Override
public boolean isTracing() {
return OpenCensusSleuthSpanContextHolder.isTracing();
}
@Override
public void addTag(String key, String value) {
Span s = getCurrentSpan();
if (s != null && s.isExportable()) {
s.tag(key, value);
}
}
/**
* Wrap the callable in a TraceCallable, if tracing.
*
* @return The callable provided, wrapped if tracing, 'callable' if not.
*/
@Override
public <V> Callable<V> wrap(Callable<V> callable) {
if (isTracing()) {
return new SpanContinuingTraceCallable<V>(this, this.traceKeys, this.spanNamer, callable);
}
return callable;
}
/**
* Wrap the runnable in a TraceRunnable, if tracing.
*
* @return The runnable provided, wrapped if tracing, 'runnable' if not.
*/
@Override
public Runnable wrap(Runnable runnable) {
if (isTracing()) {
return new SpanContinuingTraceRunnable(this, this.traceKeys, this.spanNamer, runnable);
}
return runnable;
}
}