diff --git a/opentracing-api/pom.xml b/opentracing-api/pom.xml index 29d0d810..dd8d8078 100644 --- a/opentracing-api/pom.xml +++ b/opentracing-api/pom.xml @@ -40,5 +40,20 @@ 1.10.19 test + + org.slf4j + slf4j-api + 1.7.23 + + + log4j + log4j + 1.2.17 + + + org.slf4j + slf4j-log4j12 + 1.7.23 + diff --git a/opentracing-api/src/main/java/io/opentracing/ActiveSpanManager.java b/opentracing-api/src/main/java/io/opentracing/ActiveSpanManager.java new file mode 100644 index 00000000..a351be36 --- /dev/null +++ b/opentracing-api/src/main/java/io/opentracing/ActiveSpanManager.java @@ -0,0 +1,23 @@ +package io.opentracing; + +public interface ActiveSpanManager { + // Basically a marker interface + public interface Snapshot { + Span span(); + } + + // Get the currently active Span (perhaps for this thread, etc) + Span active(); + + // Create a Snapshot encapsulating both the given Span and any state needed to activate/deactivate (see below) + Snapshot snapshot(Span span); + + // Make the Snapshot and the Span it contains "active" per active(). + // + // *Must* be paired with a subsequent call to deactivate(). + Span activate(Snapshot snapshot); + + // See activate() above. + void deactivate(Snapshot snapshot); + +} diff --git a/opentracing-api/src/main/java/io/opentracing/MDCActiveSpanManager.java b/opentracing-api/src/main/java/io/opentracing/MDCActiveSpanManager.java new file mode 100644 index 00000000..03819eda --- /dev/null +++ b/opentracing-api/src/main/java/io/opentracing/MDCActiveSpanManager.java @@ -0,0 +1,80 @@ +package io.opentracing; + +import org.slf4j.MDC; +import sun.awt.image.ImageWatched; + +import java.util.Map; + +/** + * XXX: comment + */ +public class MDCActiveSpanManager implements io.opentracing.ActiveSpanManager { + private final ThreadLocal tlsSnapshot = new ThreadLocal(); + + class MDCSnapshot implements ActiveSpanManager.Snapshot { + private final Map mdcContext; + private final Span span; + private MDCSnapshot toRestore = null; + + MDCSnapshot(Span span) { + this.mdcContext = MDC.getCopyOfContextMap(); + this.span = span; + } + + public Span span() { + return span; + } + + void setToRestore(MDCSnapshot toRestore) { + this.toRestore = toRestore; + } + + MDCSnapshot toRestore() { + return toRestore; + } + } + + @Override + public Span active() { + MDCSnapshot snapshot = tlsSnapshot.get(); + if (snapshot == null) { + return null; + } + return snapshot.span(); + } + + @Override + public MDCSnapshot snapshot(Span span) { + return new MDCSnapshot(span); + } + + @Override + public Span activate(Snapshot snapshot) { + if (!(snapshot instanceof MDCSnapshot)) { + throw new IllegalArgumentException("activate() expected MDCSnapshot"); + } + ((MDCSnapshot) snapshot).setToRestore(tlsSnapshot.get()); + tlsSnapshot.set((MDCSnapshot)snapshot); + return snapshot.span(); + } + + @Override + public void deactivate(Snapshot snapshot) { + if (!(snapshot instanceof MDCSnapshot)) { + throw new IllegalArgumentException("deactivate() expected MDCSnapshot"); + } + + if (tlsSnapshot.get() != snapshot) { + // do nothing + return; + } + MDCSnapshot nextActiveSnapshot = ((MDCSnapshot)snapshot).toRestore(); + while (nextActiveSnapshot != null) { + if (!nextActiveSnapshot.span().isFinished()) { + break; + } + nextActiveSnapshot = nextActiveSnapshot.toRestore(); + } + tlsSnapshot.set(nextActiveSnapshot); + } +} diff --git a/opentracing-api/src/main/java/io/opentracing/Span.java b/opentracing-api/src/main/java/io/opentracing/Span.java index 7a670c46..1394d28e 100644 --- a/opentracing-api/src/main/java/io/opentracing/Span.java +++ b/opentracing-api/src/main/java/io/opentracing/Span.java @@ -41,6 +41,9 @@ public interface Span extends Closeable { */ void finish(); + // XXX: comment + boolean isFinished(); + /** * Sets an explicit end timestamp and records the span. * diff --git a/opentracing-api/src/main/java/io/opentracing/Tracer.java b/opentracing-api/src/main/java/io/opentracing/Tracer.java index 26b3d001..e26d6347 100644 --- a/opentracing-api/src/main/java/io/opentracing/Tracer.java +++ b/opentracing-api/src/main/java/io/opentracing/Tracer.java @@ -41,6 +41,9 @@ public interface Tracer { */ SpanBuilder buildSpan(String operationName); + void setActiveSpanManager(ActiveSpanManager mgr); + ActiveSpanManager activeSpanManager(); + /** * Inject a SpanContext into a `carrier` of a given type, presumably for propagation across process boundaries. * @@ -88,7 +91,8 @@ public interface Tracer { */ SpanContext extract(Format format, C carrier); - + // XXX(bhs): could make this an abstract class. In any case, by default a SpanBuilder will have an asChildOf pointer + // to the ActiveSpanManager's active Span's SpanContext. interface SpanBuilder extends SpanContext { /** diff --git a/opentracing-impl/src/main/java/io/opentracing/concurrent/TracedCallable.java b/opentracing-impl/src/main/java/io/opentracing/concurrent/TracedCallable.java new file mode 100644 index 00000000..e2a5fdd5 --- /dev/null +++ b/opentracing-impl/src/main/java/io/opentracing/concurrent/TracedCallable.java @@ -0,0 +1,38 @@ +package io.opentracing.concurrent; + +import io.opentracing.ActiveSpanManager; +import io.opentracing.Span; +import io.opentracing.Tracer; +import io.opentracing.impl.GlobalTracer; + +import java.util.concurrent.Callable; + +public class TracedCallable implements Callable { + private ActiveSpanManager.Snapshot snapshot; + private ActiveSpanManager manager; + private Callable callable; + + public TracedCallable(Callable callable) { + this(callable, GlobalTracer.get().activeSpanManager()); + } + + public TracedCallable(Callable callable, ActiveSpanManager manager) { + this(callable, manager.active(), manager); + } + + public TracedCallable(Callable callable, Span span, ActiveSpanManager manager) { + if (callable == null) throw new NullPointerException("Callable is ."); + this.callable= callable; + this.manager = manager; + this.snapshot = manager.snapshot(span); + } + + public T call() throws Exception { + final Span span = manager.activate(snapshot); + try { + return callable.call(); + } finally { + manager.deactivate(snapshot); + } + } +} diff --git a/opentracing-impl/src/main/java/io/opentracing/concurrent/TracedExecutorService.java b/opentracing-impl/src/main/java/io/opentracing/concurrent/TracedExecutorService.java new file mode 100644 index 00000000..726f763a --- /dev/null +++ b/opentracing-impl/src/main/java/io/opentracing/concurrent/TracedExecutorService.java @@ -0,0 +1,102 @@ +package io.opentracing.concurrent; + +import io.opentracing.ActiveSpanManager; +import io.opentracing.Span; +import io.opentracing.Tracer; +import io.opentracing.impl.GlobalTracer; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.*; + +public class TracedExecutorService implements ExecutorService { + private ExecutorService executor; + private ActiveSpanManager manager; + + public TracedExecutorService(ExecutorService executor){ + this(executor, GlobalTracer.get().activeSpanManager()); + } + + public TracedExecutorService(ExecutorService executor, ActiveSpanManager manager) { + if (executor == null) throw new NullPointerException("Executor is ."); + if (manager == null) throw new NullPointerException("ActiveSpanManager is ."); + this.executor = executor; + this.manager = manager; + } + + @Override + public void execute(Runnable command) { + executor.execute(new TracedRunnable(command, manager)); + } + + @Override + public Future submit(Runnable task) { + return executor.submit(new TracedRunnable(task, manager)); + } + + @Override + public Future submit(Runnable task, T result) { + return executor.submit(new TracedRunnable(task, manager), result); + } + + @Override + public Future submit(Callable task) { + return executor.submit(new TracedCallable(task, manager)); + } + + @Override + public List> invokeAll(Collection> tasks) throws InterruptedException { + return executor.invokeAll(tasksWithTracing(tasks)); + } + + @Override + public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + return executor.invokeAll(tasksWithTracing(tasks), timeout, unit); + } + + @Override + public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { + return executor.invokeAny(tasksWithTracing(tasks)); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return executor.invokeAny(tasksWithTracing(tasks), timeout, unit); + } + + @Override + public void shutdown() { + executor.shutdown(); + } + + @Override + public List shutdownNow() { + return executor.shutdownNow(); + } + + @Override + public boolean isShutdown() { + return executor.isShutdown(); + } + + @Override + public boolean isTerminated() { + return executor.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return executor.awaitTermination(timeout, unit); + } + + private Collection> tasksWithTracing( + Collection> tasks) { + if (tasks == null) throw new NullPointerException("Collection of tasks is ."); + Collection> result = new ArrayList>(tasks.size()); + for (Callable task : tasks) result.add(new TracedCallable(task, manager)); + return result; + } +} diff --git a/opentracing-impl/src/main/java/io/opentracing/concurrent/TracedRunnable.java b/opentracing-impl/src/main/java/io/opentracing/concurrent/TracedRunnable.java new file mode 100644 index 00000000..068e8626 --- /dev/null +++ b/opentracing-impl/src/main/java/io/opentracing/concurrent/TracedRunnable.java @@ -0,0 +1,42 @@ +package io.opentracing.concurrent; + +import io.opentracing.ActiveSpanManager; +import io.opentracing.Span; +import io.opentracing.Tracer; +import io.opentracing.impl.GlobalTracer; + + +public class TracedRunnable implements Runnable { + private Runnable runnable; + private ActiveSpanManager manager; + private ActiveSpanManager.Snapshot snapshot; + + public TracedRunnable(Runnable runnable) { + this(runnable, GlobalTracer.get().activeSpanManager()); + } + + public TracedRunnable(Runnable runnable, Span span) { + this(runnable, span, GlobalTracer.get().activeSpanManager()); + } + + public TracedRunnable(Runnable runnable, ActiveSpanManager manager) { + this(runnable, manager.active(), manager); + } + + public TracedRunnable(Runnable runnable, Span span, ActiveSpanManager manager) { + if (runnable == null) throw new NullPointerException("Runnable is ."); + this.runnable = runnable; + this.manager = manager; + this.snapshot = manager.snapshot(span); + } + + @Override + public void run() { + final Span span = manager.activate(this.snapshot); + try { + runnable.run(); + } finally { + manager.deactivate(this.snapshot); + } + } +} diff --git a/opentracing-impl/src/main/java/io/opentracing/impl/AbstractSpan.java b/opentracing-impl/src/main/java/io/opentracing/impl/AbstractSpan.java index 4305684d..b3f08120 100644 --- a/opentracing-impl/src/main/java/io/opentracing/impl/AbstractSpan.java +++ b/opentracing-impl/src/main/java/io/opentracing/impl/AbstractSpan.java @@ -55,6 +55,11 @@ public void finish() { duration = Duration.between(start, Instant.now()); } + @Override + public boolean isFinished() { + return null != duration; + } + @Override public void finish(long finishMicros) { long finishEpochSeconds = TimeUnit.MICROSECONDS.toSeconds(finishMicros); diff --git a/opentracing-impl/src/main/java/io/opentracing/impl/AbstractTracer.java b/opentracing-impl/src/main/java/io/opentracing/impl/AbstractTracer.java index 9b0c8169..a3dca44e 100644 --- a/opentracing-impl/src/main/java/io/opentracing/impl/AbstractTracer.java +++ b/opentracing-impl/src/main/java/io/opentracing/impl/AbstractTracer.java @@ -13,6 +13,8 @@ */ package io.opentracing.impl; +import io.opentracing.ActiveSpanManager; +import io.opentracing.Span; import io.opentracing.SpanContext; import io.opentracing.Tracer; import io.opentracing.propagation.Extractor; @@ -28,6 +30,7 @@ abstract class AbstractTracer implements Tracer { static final boolean BAGGAGE_ENABLED = !Boolean.getBoolean("opentracing.propagation.dropBaggage"); private final PropagationRegistry registry = new PropagationRegistry(); + private ActiveSpanManager manager; protected AbstractTracer() { registry.register(Format.Builtin.TEXT_MAP, new TextMapInjectorImpl(this)); @@ -36,6 +39,16 @@ protected AbstractTracer() { abstract AbstractSpanBuilder createSpanBuilder(String operationName); + @Override + public void setActiveSpanManager(ActiveSpanManager manager) { + this.manager = manager; + } + + @Override + public ActiveSpanManager activeSpanManager() { + return this.manager; + } + @Override public SpanBuilder buildSpan(String operationName){ return createSpanBuilder(operationName); diff --git a/opentracing-impl/src/main/java/io/opentracing/impl/GlobalTracer.java b/opentracing-impl/src/main/java/io/opentracing/impl/GlobalTracer.java new file mode 100644 index 00000000..6d7ac692 --- /dev/null +++ b/opentracing-impl/src/main/java/io/opentracing/impl/GlobalTracer.java @@ -0,0 +1,160 @@ +package io.opentracing.impl; + +/** + * Copyright 2017 The OpenTracing 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. + */ + +import io.opentracing.*; +import io.opentracing.propagation.Format; + +import java.util.Iterator; +import java.util.ServiceLoader; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Forwards all methods to another tracer that can be configured in one of two ways: + *
    + *
  1. Explicitly, calling {@link #register(Tracer)} with a configured tracer, or:
  2. + *
  3. Automatically using the Java {@link ServiceLoader} SPI mechanism to load a {@link Tracer} from the classpath.
  4. + *
+ *

+ * When the tracer is needed it is lazily looked up using the following rules: + *

    + *
  1. The last-{@link #register(Tracer) registered} tracer always takes precedence.
  2. + *
  3. If no tracer was registered, one is looked up from the {@link ServiceLoader}.
    + * The {@linkplain GlobalTracer} will not attempt to choose between implementations:
  4. + *
  5. If no single tracer service is found, the {@link io.opentracing.NoopTracer NoopTracer} will be used.
  6. + *
+ */ +public final class GlobalTracer implements Tracer { + private static final Logger LOGGER = Logger.getLogger(GlobalTracer.class.getName()); + + /** + * Singleton instance. + *

+ * Since we cannot prevent people using {@linkplain #get() GlobalTracer.get()} as a constant, + * this guarantees that references obtained before, during or after initialization + * all behave as if obtained after initialization once properly initialized.
+ * As a minor additional benefit it makes it harder to circumvent the {@link Tracer} API. + */ + private static final GlobalTracer INSTANCE = new GlobalTracer(); + + /** + * The resolved {@link Tracer} to delegate to. + *

+ * This can be either an {@link #register(Tracer) explicitly registered} or + * the {@link #loadSingleSpiImplementation() automatically resolved} Tracer + * (or null during initialization). + */ + private final AtomicReference globalTracer = new AtomicReference(); + + private GlobalTracer() { + } + + private Tracer lazyTracer() { + Tracer tracer = globalTracer.get(); + if (tracer == null) { + final Tracer resolved = loadSingleSpiImplementation(); + while (tracer == null && resolved != null) { // handle rare race condition + globalTracer.compareAndSet(null, resolved); + tracer = globalTracer.get(); + } + LOGGER.log(Level.INFO, "Using GlobalTracer: {0}.", tracer); + } + return tracer; + } + + /** + * Returns the constant {@linkplain GlobalTracer}. + *

+ * All methods are forwarded to the currently configured tracer.
+ * Until a tracer is {@link #register(Tracer) explicitly configured}, + * one is looked up from the {@link ServiceLoader}, + * falling back to the {@link io.opentracing.NoopTracer NoopTracer}.
+ * A tracer can be re-configured at any time. + * For example, the tracer used to extract a span may be different than the one that injects it. + * + * @return The global tracer constant. + * @see #register(Tracer) + */ + public static Tracer get() { + return INSTANCE; + } + + /** + * Explicitly configures a {@link Tracer} to back the behaviour of the {@link #get() global tracer}. + *

+ * The previous global tracer is returned so it can be restored later if necessary. + * + * @param tracer Tracer to use as global tracer. + * @return The previous global tracer or null if there was none. + */ + public static Tracer register(final Tracer tracer) { + if (tracer instanceof GlobalTracer) { + LOGGER.log(Level.FINE, "Attempted to register the GlobalTracer as delegate of itself."); + return INSTANCE.globalTracer.get(); // no-op, return 'previous' tracer. + } + Tracer previous = INSTANCE.globalTracer.getAndSet(tracer); + LOGGER.log(Level.INFO, "Registered GlobalTracer {0} (previously {1}).", new Object[]{tracer, previous}); + return previous; + } + + @Override + public SpanBuilder buildSpan(String operationName) { + return lazyTracer().buildSpan(operationName); + } + + @Override + public void setActiveSpanManager(ActiveSpanManager mgr) { + lazyTracer().setActiveSpanManager(mgr); + } + + @Override + public ActiveSpanManager activeSpanManager() { + return lazyTracer().activeSpanManager(); + } + + @Override + public void inject(SpanContext spanContext, Format format, C carrier) { + lazyTracer().inject(spanContext, format, carrier); + } + + @Override + public SpanContext extract(Format format, C carrier) { + return lazyTracer().extract(format, carrier); + } + + /** + * Loads a single service implementation from {@link ServiceLoader}. + * + * @return The single service or a NoopTracer. + */ + private static Tracer loadSingleSpiImplementation() { + // Use the ServiceLoader to find the declared Tracer implementation. + Iterator spiImplementations = + ServiceLoader.load(Tracer.class, Tracer.class.getClassLoader()).iterator(); + if (spiImplementations.hasNext()) { + Tracer foundImplementation = spiImplementations.next(); + if (!spiImplementations.hasNext()) { + return foundImplementation; + } + LOGGER.log(Level.WARNING, "More than one Tracer service found. " + + "Falling back to NoopTracer implementation."); + } + return NoopTracerFactory.create(); + } + +} + diff --git a/opentracing-impl/src/main/java/io/opentracing/impl/NoopSpan.java b/opentracing-impl/src/main/java/io/opentracing/impl/NoopSpan.java index de0f832b..be8b5d7d 100644 --- a/opentracing-impl/src/main/java/io/opentracing/impl/NoopSpan.java +++ b/opentracing-impl/src/main/java/io/opentracing/impl/NoopSpan.java @@ -13,8 +13,7 @@ */ package io.opentracing.impl; -import io.opentracing.NoopSpanContext; -import io.opentracing.Span; +import io.opentracing.*; final class NoopSpan extends AbstractSpan implements io.opentracing.NoopSpan, NoopSpanContext { @@ -28,6 +27,11 @@ public NoopSpan(String operationName) { public void finish() { } + @Override + public boolean isFinished() { + return true; + } + @Override public void finish(long finishMicros) { } diff --git a/opentracing-impl/src/main/java/io/opentracing/impl/NoopSpanBuilder.java b/opentracing-impl/src/main/java/io/opentracing/impl/NoopSpanBuilder.java index 08976fc6..77b0e8a2 100644 --- a/opentracing-impl/src/main/java/io/opentracing/impl/NoopSpanBuilder.java +++ b/opentracing-impl/src/main/java/io/opentracing/impl/NoopSpanBuilder.java @@ -15,6 +15,8 @@ import io.opentracing.NoopSpanContext; +import java.util.Map; + final class NoopSpanBuilder extends AbstractSpanBuilder implements io.opentracing.NoopSpanBuilder, NoopSpanContext { static final NoopSpanBuilder INSTANCE = new NoopSpanBuilder("noop"); diff --git a/opentracing-impl/src/main/java/io/opentracing/impl/NoopTracer.java b/opentracing-impl/src/main/java/io/opentracing/impl/NoopTracer.java index e990d019..859bdf24 100644 --- a/opentracing-impl/src/main/java/io/opentracing/impl/NoopTracer.java +++ b/opentracing-impl/src/main/java/io/opentracing/impl/NoopTracer.java @@ -13,7 +13,7 @@ */ package io.opentracing.impl; -import io.opentracing.SpanContext; +import io.opentracing.*; import io.opentracing.propagation.Format; import java.util.Collections; import java.util.Map; @@ -22,6 +22,12 @@ final class NoopTracer extends AbstractTracer implements io.opentracing.NoopTrac private static final NoopTracer INSTANCE = new NoopTracer(); + @Override + public void setActiveSpanManager(ActiveSpanManager mgr) {} + + @Override + public ActiveSpanManager activeSpanManager() { return null; } + @Override public void inject(SpanContext spanContext, Format format, C carrier) {} @@ -40,5 +46,4 @@ Map getTraceState(SpanContext spanContext) { return Collections.emptyMap(); } - } diff --git a/opentracing-impl/src/main/java/io/opentracing/impl/TextMapExtractorImpl.java b/opentracing-impl/src/main/java/io/opentracing/impl/TextMapExtractorImpl.java index 4e80ab87..53b29c1a 100644 --- a/opentracing-impl/src/main/java/io/opentracing/impl/TextMapExtractorImpl.java +++ b/opentracing-impl/src/main/java/io/opentracing/impl/TextMapExtractorImpl.java @@ -41,4 +41,4 @@ public Tracer.SpanBuilder extract(TextMap carrier) { return builder; } -} \ No newline at end of file +} diff --git a/opentracing-impl/src/test/java/io/opentracing/impl/AbstractTracerTest.java b/opentracing-impl/src/test/java/io/opentracing/impl/AbstractTracerTest.java index c45a2fac..bd0aa7f3 100644 --- a/opentracing-impl/src/test/java/io/opentracing/impl/AbstractTracerTest.java +++ b/opentracing-impl/src/test/java/io/opentracing/impl/AbstractTracerTest.java @@ -133,6 +133,7 @@ public void propagatesBaggageFromSpanContext() { final class TestTracerImpl extends AbstractTracer { static final String OPERATION_NAME = "operation-name"; + Span activeSpan; @Override public AbstractSpanBuilder createSpanBuilder(String operationName) { diff --git a/opentracing-impl/src/test/java/io/opentracing/impl/TestSpanImpl.java b/opentracing-impl/src/test/java/io/opentracing/impl/TestSpanImpl.java index 634fbb34..0ef62481 100644 --- a/opentracing-impl/src/test/java/io/opentracing/impl/TestSpanImpl.java +++ b/opentracing-impl/src/test/java/io/opentracing/impl/TestSpanImpl.java @@ -20,4 +20,9 @@ public class TestSpanImpl extends AbstractSpan { TestSpanImpl(String operationName) { super(operationName); } + + @Override + public boolean isFinished() { + return true; + } } diff --git a/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java b/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java index 47a8469b..69eafab9 100644 --- a/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java +++ b/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java @@ -16,6 +16,7 @@ import java.util.*; import java.util.concurrent.atomic.AtomicLong; +import io.opentracing.ActiveSpanManager; import io.opentracing.Span; import io.opentracing.SpanContext; @@ -38,6 +39,7 @@ public final class MockSpan implements Span { private final Map tags; private final List logEntries = new ArrayList<>(); private String operationName; + private ActiveSpanManager.Snapshot snapshot; public String operationName() { return this.operationName; @@ -96,6 +98,9 @@ public void finish() { @Override public synchronized void finish(long finishMicros) { if (!finished) { + if (this.mockTracer.activeSpanManager() != null) { + this.mockTracer.activeSpanManager().deactivate(this.snapshot); + } this.finishMicros = finishMicros; this.mockTracer.appendFinishedSpan(this); this.finished = true; @@ -104,6 +109,11 @@ public synchronized void finish(long finishMicros) { } } + @Override + public synchronized boolean isFinished() { + return finished; + } + @Override public void close() { this.finish(); @@ -253,6 +263,11 @@ public long timestampMicros() { this.context = new MockContext(parent.traceId, nextId(), parent.baggage); this.parentId = parent.spanId; } + + if (tracer.activeSpanManager() != null) { + // XXX weird/awkward + this.snapshot = tracer.activeSpanManager().snapshot(this); + } } static long nextId() { diff --git a/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java b/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java index 27ca15ff..92f12991 100644 --- a/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java +++ b/opentracing-mock/src/main/java/io/opentracing/mock/MockTracer.java @@ -20,10 +20,7 @@ import java.util.List; import java.util.Map; -import io.opentracing.References; -import io.opentracing.Span; -import io.opentracing.SpanContext; -import io.opentracing.Tracer; +import io.opentracing.*; import io.opentracing.propagation.Format; import io.opentracing.propagation.TextMap; @@ -38,6 +35,7 @@ public class MockTracer implements Tracer { private List finishedSpans = new ArrayList<>(); private final Propagator propagator; + private ActiveSpanManager activeSpanManager; public MockTracer() { this(Propagator.PRINTER); @@ -146,7 +144,24 @@ public MockSpan.MockContext extract(Format format, C carrier) { @Override public Tracer.SpanBuilder buildSpan(String operationName) { - return new SpanBuilder(operationName); + SpanBuilder sb = new SpanBuilder(operationName); + if (this.activeSpanManager != null) { + Span active = this.activeSpanManager.active(); + if (active != null) { + sb.asChildOf(active.context()); + } + } + return sb; + } + + @Override + public void setActiveSpanManager(ActiveSpanManager mgr) { + this.activeSpanManager = mgr; + } + + @Override + public ActiveSpanManager activeSpanManager() { + return activeSpanManager; } @Override @@ -221,7 +236,12 @@ public Span start() { if (this.startMicros == 0) { this.startMicros = MockSpan.nowMicros(); } - return new MockSpan(MockTracer.this, this.operationName, this.startMicros, initialTags, this.firstParent); + Span rval = new MockSpan(MockTracer.this, this.operationName, this.startMicros, initialTags, this.firstParent); + if (MockTracer.this.activeSpanManager != null) { + MockTracer.this.activeSpanManager.activate( + MockTracer.this.activeSpanManager.snapshot(rval)); + } + return rval; } @Override diff --git a/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java b/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java index c24959c6..e956306a 100644 --- a/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java +++ b/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; +import io.opentracing.MDCActiveSpanManager; import org.junit.Assert; import org.junit.Test; @@ -30,7 +31,35 @@ import io.opentracing.propagation.TextMapExtractAdapter; import io.opentracing.propagation.TextMapInjectAdapter; +import org.slf4j.Logger; +import org.slf4j.MDC; + public class MockTracerTest { + @Test + public void testMDCHack() { + org.apache.log4j.BasicConfigurator.configure(); + Logger logger = org.slf4j.LoggerFactory.getLogger("hack"); + MDC.put("key1", "val1"); + Map ctxMap = MDC.getCopyOfContextMap(); + MDC.put("key2", "val2"); + MDC.setContextMap(ctxMap); + logger.info("testing: {}", MDC.getCopyOfContextMap().toString()); + + MockTracer tracer = new MockTracer(); + tracer.setActiveSpanManager(new MDCActiveSpanManager()); + + Span par = tracer.buildSpan("parent").start(); + Span childA = tracer.buildSpan("childA").start(); + childA.finish(); + par.finish(); + + List finishedSpans = tracer.finishedSpans(); + + for (MockSpan span : finishedSpans) { + logger.info("finished Span. {} :: {} ({})", span.context().traceId(), span.context().spanId(), span.parentId()); + } + } + @Test public void testRootSpan() { // Create and finish a root Span. diff --git a/opentracing-noop/src/main/java/io/opentracing/NoopSpan.java b/opentracing-noop/src/main/java/io/opentracing/NoopSpan.java index 8e971804..5fe3804d 100644 --- a/opentracing-noop/src/main/java/io/opentracing/NoopSpan.java +++ b/opentracing-noop/src/main/java/io/opentracing/NoopSpan.java @@ -21,7 +21,6 @@ public interface NoopSpan extends Span { final class NoopSpanImpl implements NoopSpan { - @Override public SpanContext context() { return NoopSpanContextImpl.INSTANCE; } @@ -31,6 +30,9 @@ public void finish() {} @Override public void finish(long finishMicros) {} + @Override + public boolean isFinished() { return true; } + @Override public void close() { finish(); } diff --git a/opentracing-noop/src/main/java/io/opentracing/NoopSpanSnapshot.java b/opentracing-noop/src/main/java/io/opentracing/NoopSpanSnapshot.java new file mode 100644 index 00000000..a484a72d --- /dev/null +++ b/opentracing-noop/src/main/java/io/opentracing/NoopSpanSnapshot.java @@ -0,0 +1,9 @@ +package io.opentracing; + +public interface NoopSpanSnapshot { + static final NoopSpanSnapshotImpl INSTANCE = new NoopSpanSnapshotImpl(); +} + +final class NoopSpanSnapshotImpl implements NoopSpanSnapshot { + +} \ No newline at end of file diff --git a/opentracing-noop/src/main/java/io/opentracing/NoopTracer.java b/opentracing-noop/src/main/java/io/opentracing/NoopTracer.java index 40d17a9a..dbe02a61 100644 --- a/opentracing-noop/src/main/java/io/opentracing/NoopTracer.java +++ b/opentracing-noop/src/main/java/io/opentracing/NoopTracer.java @@ -24,6 +24,12 @@ final class NoopTracerImpl implements NoopTracer { @Override public SpanBuilder buildSpan(String operationName) { return NoopSpanBuilderImpl.INSTANCE; } + @Override + public void setActiveSpanManager(ActiveSpanManager mgr) {} + + @Override + public ActiveSpanManager activeSpanManager() { return null; } + @Override public void inject(SpanContext spanContext, Format format, C carrier) {} diff --git a/pom.xml b/pom.xml index 142bcd68..973f1021 100644 --- a/pom.xml +++ b/pom.xml @@ -238,7 +238,7 @@ true - + maven-release-plugin ${maven-release-plugin.version}