diff --git a/opentracing-api/pom.xml b/opentracing-api/pom.xml index f747157f..0c44e104 100644 --- a/opentracing-api/pom.xml +++ b/opentracing-api/pom.xml @@ -29,8 +29,8 @@ ${project.basedir}/.. - 1.6 - java16 + 1.7 + java17 @@ -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/SpanManager.java b/opentracing-api/src/main/java/io/opentracing/SpanManager.java new file mode 100644 index 00000000..7bf45355 --- /dev/null +++ b/opentracing-api/src/main/java/io/opentracing/SpanManager.java @@ -0,0 +1,78 @@ +package io.opentracing; + +/** + * SpanManager allows an existing (possibly thread-local-aware) execution context provider to also manage the current + * active OpenTracing span. + */ +public interface SpanManager { + + /** + * A SpanClosure can be used *once* to make a Span active within a SpanManager, then deactivate it once the + * "closure" (or period of Span activity) has finished. + * + * Most users do not directly interact with SpanClosure, activate(), or deactivate(), but rather use + * SpanManager-aware Runnables/Callables/Executors. Those higher-level primitives need not be defined within the + * OpenTracing core API. + * + * @see SpanManager#captureActive() + */ + interface SpanClosure extends AutoCloseable { + + /** + * Make the Span encapsulated by this SpanClosure active and return it. + * + * NOTE: It is an error to call activate() more than once on a single SpanClosure instance. + * + * @see SpanManager#captureActive() + * @return the newly-activated Span + */ + Span activate(); + + /** + * @return the encapsulated Span, or null if there isn't one. + */ + Span span(); + + /** + * End this active period for the Span previously returned by activate(). Finish the span iff finish=true. + * + * NOTE: It is an error to call deactivate() more than once on a single SpanClosure instance. + */ + void deactivate(boolean finishSpan); + + } + + /** + * @return the currently active Span for this SpanManager, or null if no such Span could be found + */ + Span active(); + + /** + * Capture any SpanManager-specific context (e.g., MDC context) along with the active Span (even if null) and + * encapsulate it in a SpanClosure for activation in the future, perhaps in a different thread or on a different + * executor. + * + * If the active Span is null, the implementation must still return a valid SpanClosure; when the closure activates, + * it will clear any active Span. + * + * @see SpanManager.SpanClosure + * + * @return a SpanClosure that represents the active Span and any other SpanManager-specific context, even if the + * active Span is null. + */ + SpanClosure captureActive(); + + /** + * Explicitly capture the given Span and any active state (e.g., MDC state) about the current execution context. + * + * @param span + * @return a SpanClosure that represents the active Span and any other SpanManager-specific context, even if the + * active Span is null. + */ + SpanClosure capture(Span span); + + /** + * Tell the SpanManager that a particular Span has finished (and update any structures accordingly). + */ + void onFinish(Span 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..74206c0e 100644 --- a/opentracing-api/src/main/java/io/opentracing/Tracer.java +++ b/opentracing-api/src/main/java/io/opentracing/Tracer.java @@ -23,6 +23,9 @@ public interface Tracer { /** * Return a new SpanBuilder for a Span with the given `operationName`. * + *

If there is an active Span according to the activeSpanManager(), + * buildSpan will automatically have an asChildOf() reference to same. + * *

You can override the operationName later via {@link Span#setOperationName(String)}. * *

A contrived example: @@ -41,6 +44,8 @@ public interface Tracer { */ SpanBuilder buildSpan(String operationName); + SpanManager activeSpanManager(); + /** * Inject a SpanContext into a `carrier` of a given type, presumably for propagation across process boundaries. * @@ -128,5 +133,7 @@ interface SpanBuilder extends SpanContext { /** Returns the started Span. */ Span start(); + SpanManager.SpanClosure startAndActivate(); + } } diff --git a/opentracing-api/src/main/java/io/opentracing/propagation/Format.java b/opentracing-api/src/main/java/io/opentracing/propagation/Format.java index 29d71d6c..894ed906 100644 --- a/opentracing-api/src/main/java/io/opentracing/propagation/Format.java +++ b/opentracing-api/src/main/java/io/opentracing/propagation/Format.java @@ -1,5 +1,5 @@ /** - * Copyright 2016 The OpenTracing Authors + * Copyright 2016-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 @@ -35,6 +35,12 @@ */ public interface Format { final class Builtin implements Format { + private final String name; + + private Builtin(String name) { + this.name = name; + } + /** * The TEXT_MAP format allows for arbitrary String->String map encoding of SpanContext state for Tracer.inject * and Tracer.extract. @@ -46,7 +52,7 @@ final class Builtin implements Format { * @see Format * @see Builtin#HTTP_HEADERS */ - public final static Format TEXT_MAP = new Builtin(); + public final static Format TEXT_MAP = new Builtin("TEXT_MAP"); /** * The HTTP_HEADERS format allows for HTTP-header-compatible String->String map encoding of SpanContext state @@ -60,7 +66,7 @@ final class Builtin implements Format { * @see Format * @see Builtin#TEXT_MAP */ - public final static Format HTTP_HEADERS = new Builtin(); + public final static Format HTTP_HEADERS = new Builtin("HTTP_HEADERS"); /** * The BINARY format allows for unconstrained binary encoding of SpanContext state for Tracer.inject and @@ -70,6 +76,14 @@ final class Builtin implements Format { * @see io.opentracing.Tracer#extract(Format, Object) * @see Format */ - public final static Format BINARY = new Builtin(); + public final static Format BINARY = new Builtin("BINARY"); + + /** + * @return Short name for built-in formats as they tend to show up in exception messages. + */ + @Override + public String toString() { + return Builtin.class.getSimpleName() + "." + name; + } } } diff --git a/opentracing-api/src/main/java/io/opentracing/tag/Tags.java b/opentracing-api/src/main/java/io/opentracing/tag/Tags.java index 9da1118a..aa18777d 100644 --- a/opentracing-api/src/main/java/io/opentracing/tag/Tags.java +++ b/opentracing-api/src/main/java/io/opentracing/tag/Tags.java @@ -22,11 +22,12 @@ */ public final class Tags { - private Tags(){} + private Tags() { + } /** - * A constant for setting the span kind to indicate that it represents a server span. - */ + * A constant for setting the span kind to indicate that it represents a server span. + */ public static final String SPAN_KIND_SERVER = "server"; /** @@ -35,32 +36,32 @@ private Tags(){} public static final String SPAN_KIND_CLIENT = "client"; /** - * HTTP_URL records the url of the incoming request. + * HTTP_URL records the url of the incoming request. */ public static final StringTag HTTP_URL = new StringTag("http.url"); /** - * HTTP_STATUS records the http status code of the response. + * HTTP_STATUS records the http status code of the response. */ public static final IntTag HTTP_STATUS = new IntTag("http.status_code"); /** - * HTTP_METHOD records the http method. Case-insensitive. + * HTTP_METHOD records the http method. Case-insensitive. */ public static final StringTag HTTP_METHOD = new StringTag("http.method"); /** - * PEER_HOST_IPV4 records IPv4 host address of the peer. + * PEER_HOST_IPV4 records IPv4 host address of the peer. */ public static final IntTag PEER_HOST_IPV4 = new IntTag("peer.ipv4"); /** - * PEER_HOST_IPV6 records the IPv6 host address of the peer. + * PEER_HOST_IPV6 records the IPv6 host address of the peer. */ public static final StringTag PEER_HOST_IPV6 = new StringTag("peer.ipv6"); /** - * PEER_SERVICE records the service name of the peer. + * PEER_SERVICE records the service name of the peer. */ public static final StringTag PEER_SERVICE = new StringTag("peer.service"); @@ -70,27 +71,50 @@ private Tags(){} public static final StringTag PEER_HOSTNAME = new StringTag("peer.hostname"); /** - * PEER_PORT records the port number of the peer. + * PEER_PORT records the port number of the peer. */ public static final ShortTag PEER_PORT = new ShortTag("peer.port"); /** - * SAMPLING_PRIORITY determines the priority of sampling this Span. + * SAMPLING_PRIORITY determines the priority of sampling this Span. */ public static final ShortTag SAMPLING_PRIORITY = new ShortTag("sampling.priority"); /** - * SPAN_KIND hints at the relationship between spans, e.g. client/server. + * SPAN_KIND hints at the relationship between spans, e.g. client/server. */ public static final StringTag SPAN_KIND = new StringTag("span.kind"); /** - * COMPONENT is a low-cardinality identifier of the module, library, or package that is instrumented. + * COMPONENT is a low-cardinality identifier of the module, library, or package that is instrumented. */ - public static final StringTag COMPONENT = new StringTag("component"); + public static final StringTag COMPONENT = new StringTag("component"); /** * ERROR indicates whether a Span ended in an error state. */ public static final BooleanTag ERROR = new BooleanTag("error"); + + /** + * DB_TYPE indicates the type of Database. + * For any SQL database, "sql". For others, the lower-case database category, e.g. "cassandra", "hbase", or "redis" + */ + public static final StringTag DB_TYPE = new StringTag("db.type"); + + /** + * DB_INSTANCE indicates the instance name of Database. + * If the jdbc.url="jdbc:mysql://127.0.0.1:3306/customers", instance name is "customers". + */ + public static final StringTag DB_INSTANCE = new StringTag("db.instance"); + + /** + * DB_USER indicates the user name of Database, e.g. "readonly_user" or "reporting_user" + */ + public static final StringTag DB_USER = new StringTag("db.user"); + + /** + * DB_STATEMENT records a database statement for the given database type. + * For db.type="SQL", "SELECT * FROM wuser_table". For db.type="redis", "SET mykey "WuValue". + */ + public static final StringTag DB_STATEMENT = new StringTag("db.statement"); } diff --git a/opentracing-api/src/test/java/io/opentracing/propagation/BuiltinFormatTest.java b/opentracing-api/src/test/java/io/opentracing/propagation/BuiltinFormatTest.java new file mode 100644 index 00000000..200ff196 --- /dev/null +++ b/opentracing-api/src/test/java/io/opentracing/propagation/BuiltinFormatTest.java @@ -0,0 +1,37 @@ +/** + * Copyright 2016-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. + */ +package io.opentracing.propagation; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class BuiltinFormatTest { + + @Test + public void test_HTTP_HEADERS_toString() { + assertEquals("Builtin.HTTP_HEADERS", Format.Builtin.HTTP_HEADERS.toString()); + } + + @Test + public void test_TEXT_MAP_toString() { + assertEquals("Builtin.TEXT_MAP", Format.Builtin.TEXT_MAP.toString()); + } + + @Test + public void test_BINARY_toString() { + assertEquals("Builtin.BINARY", Format.Builtin.BINARY.toString()); + } + +} 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..49fc5e59 100644 --- a/opentracing-impl/src/main/java/io/opentracing/impl/AbstractSpan.java +++ b/opentracing-impl/src/main/java/io/opentracing/impl/AbstractSpan.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; abstract class AbstractSpan implements Span, SpanContext { @@ -34,6 +35,7 @@ abstract class AbstractSpan implements Span, SpanContext { private Duration duration; private final Map tags = new HashMap<>(); private final List logs = new ArrayList<>(); + private final AtomicLong refCount = new AtomicLong(0); // XXX AbstractSpan(String operationName ) { this(operationName, Instant.now()); diff --git a/opentracing-impl/src/main/java/io/opentracing/impl/AbstractSpanBuilder.java b/opentracing-impl/src/main/java/io/opentracing/impl/AbstractSpanBuilder.java index 4c04b29a..70fcca0f 100644 --- a/opentracing-impl/src/main/java/io/opentracing/impl/AbstractSpanBuilder.java +++ b/opentracing-impl/src/main/java/io/opentracing/impl/AbstractSpanBuilder.java @@ -13,10 +13,8 @@ */ package io.opentracing.impl; -import io.opentracing.References; -import io.opentracing.Span; -import io.opentracing.SpanContext; -import io.opentracing.Tracer; +import io.opentracing.*; + import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; @@ -122,6 +120,11 @@ public final Span start() { return span; } + @Override + public SpanManager.SpanClosure startAndActivate() { + return null; // XXX: not correct... we'd need access to the AbstractTracer's SpanManager. + } + private void withBaggageFrom(SpanContext from) { for (Entry baggageItem : from.baggageItems()) { this.withBaggageItem(baggageItem.getKey(), baggageItem.getValue()); 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..0a5aa59b 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,7 @@ */ package io.opentracing.impl; +import io.opentracing.SpanManager; import io.opentracing.SpanContext; import io.opentracing.Tracer; import io.opentracing.propagation.Extractor; @@ -28,14 +29,26 @@ abstract class AbstractTracer implements Tracer { static final boolean BAGGAGE_ENABLED = !Boolean.getBoolean("opentracing.propagation.dropBaggage"); private final PropagationRegistry registry = new PropagationRegistry(); + private SpanManager manager; + protected AbstractTracer() { + this(null); // SpanManager is optional for this spike + } + + protected AbstractTracer(SpanManager manager) { + this.manager = manager; registry.register(Format.Builtin.TEXT_MAP, new TextMapInjectorImpl(this)); registry.register(Format.Builtin.TEXT_MAP, new TextMapExtractorImpl(this)); } abstract AbstractSpanBuilder createSpanBuilder(String operationName); + @Override + public SpanManager 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..bbcdcf56 --- /dev/null +++ b/opentracing-impl/src/main/java/io/opentracing/impl/GlobalTracer.java @@ -0,0 +1,155 @@ +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 SpanManager 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..2820dc0b 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 { 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..95012645 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,9 @@ final class NoopTracer extends AbstractTracer implements io.opentracing.NoopTrac private static final NoopTracer INSTANCE = new NoopTracer(); + @Override + public SpanManager activeSpanManager() { return null; } + @Override public void inject(SpanContext spanContext, Format format, C carrier) {} @@ -40,5 +43,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..0db93dc8 100644 --- a/opentracing-impl/src/test/java/io/opentracing/impl/TestSpanImpl.java +++ b/opentracing-impl/src/test/java/io/opentracing/impl/TestSpanImpl.java @@ -16,7 +16,6 @@ import io.opentracing.impl.AbstractSpan; public class TestSpanImpl extends AbstractSpan { - TestSpanImpl(String operationName) { super(operationName); } diff --git a/opentracing-mdc-demo/pom.xml b/opentracing-mdc-demo/pom.xml new file mode 100644 index 00000000..3b5ccd53 --- /dev/null +++ b/opentracing-mdc-demo/pom.xml @@ -0,0 +1,45 @@ + + + + 4.0.0 + + + io.opentracing + parent + 0.20.8-SNAPSHOT + + + opentracing-mdc-demo + OpenTracing-mdc-demo + OpenTracing MDC Demo + + + ${project.basedir}/.. + + + + + ${project.groupId} + opentracing-api + + + ${project.groupId} + opentracing-mock + + + + diff --git a/opentracing-mdc-demo/src/main/java/io/opentracing/mdcdemo/MDCDemo.java b/opentracing-mdc-demo/src/main/java/io/opentracing/mdcdemo/MDCDemo.java new file mode 100644 index 00000000..59b080b3 --- /dev/null +++ b/opentracing-mdc-demo/src/main/java/io/opentracing/mdcdemo/MDCDemo.java @@ -0,0 +1,120 @@ +package io.opentracing.mdcdemo; + +import io.opentracing.Span; +import io.opentracing.SpanManager; +import io.opentracing.Tracer; +import io.opentracing.mock.MockSpan; +import io.opentracing.mock.MockTracer; +import org.slf4j.Logger; +import org.slf4j.MDC; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; + +public class MDCDemo { + Tracer tracer; + + private MDCDemo(Tracer tracer) { + this.tracer = tracer; + } + + public void trivialSpan() { + Span span = this.tracer.buildSpan("trivial").start(); + span.finish(); + } + + public void trivialChild() { + Span parent = this.tracer.buildSpan("trivialParent").start(); + // The child will automatically know about the parent. + Span child = this.tracer.buildSpan("trivialChild").start(); + child.finish(); + parent.finish(); + } + + public void asyncSpans() { + final Tracer tracer = this.tracer; // save typing + + // Create an ExecutorService and wrap it in a TracedExecutorService. + ExecutorService realExecutor = Executors.newFixedThreadPool(500); + final ExecutorService otExecutor = new TracedExecutorService(realExecutor, tracer.activeSpanManager()); + + // Hacky lists of futures we wait for before exiting asyncSpans. + final List> futures = new ArrayList<>(); + final List> subfutures = new ArrayList<>(); + + // Create a parent SpanClosure for all of the async activity. + try (SpanManager.SpanClosure parentSpanClosure = tracer.buildSpan("parent").startAndActivate();) { + + // Create 10 async children. + for (int i = 0; i < 10; i++) { + final int j = i; + futures.add(otExecutor.submit(new Runnable() { + @Override + public void run() { + // START child body + + try (SpanManager.SpanClosure childSpanClosure = + tracer.buildSpan("child_" + j).startAndActivate();) { + Thread.currentThread().sleep(1000); + childSpanClosure.span().log("awoke"); + Runnable r = new Runnable() { + @Override + public void run() { + Span active = tracer.activeSpanManager().active(); + active.log("awoke again"); + // Create a grandchild for each child. + Span grandchild = tracer.buildSpan("grandchild_" + j).start(); + grandchild.finish(); + active.finish(); + } + }; + subfutures.add(otExecutor.submit(r)); + } catch (Exception e) { } + + // END child body + } + })); + } + } catch (Exception e) { } + + try { + for (Future f : futures) { + f.get(); + } + for (Future f : subfutures) { + f.get(); + } + } catch (Exception e) { } + + otExecutor.shutdown(); + try { + otExecutor.awaitTermination(3, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public static void main(String[] args) { + org.apache.log4j.BasicConfigurator.configure(); + final Logger logger = org.slf4j.LoggerFactory.getLogger("hack"); + MDC.put("mdcKey", "mdcVal"); + + final MockTracer tracer = new MockTracer(new MDCSpanManager()); + + // Do stuff with the MockTracer. + { + MDCDemo demo = new MDCDemo(tracer); + demo.trivialSpan(); + demo.trivialChild(); + demo.asyncSpans(); + } + + // Print out all mock-Spans + List finishedSpans = tracer.finishedSpans(); + for (MockSpan span : finishedSpans) { + logger.info("finished Span '{}'. trace={}, span={}, parent={}", span.operationName(), span.context().traceId(), span.context().spanId(), span.parentId()); + } + + } +} diff --git a/opentracing-mdc-demo/src/main/java/io/opentracing/mdcdemo/MDCSpanManager.java b/opentracing-mdc-demo/src/main/java/io/opentracing/mdcdemo/MDCSpanManager.java new file mode 100644 index 00000000..5fff078d --- /dev/null +++ b/opentracing-mdc-demo/src/main/java/io/opentracing/mdcdemo/MDCSpanManager.java @@ -0,0 +1,94 @@ +package io.opentracing.mdcdemo; + +import io.opentracing.Span; +import io.opentracing.SpanManager; +import org.slf4j.MDC; + +import java.util.Map; + +/** + * MDCSpanManager illustrates the core SpanManager concepts and capabilities to a first approximation. Not + * production-quality code. + */ +public class MDCSpanManager implements SpanManager { + private final ThreadLocal tlsSnapshot = new ThreadLocal(); + + class MDCSnapshot implements SpanClosure { + private final Map mdcContext; + private final Span span; + private MDCSnapshot toRestore = null; + + MDCSnapshot(Span span) { + this.mdcContext = MDC.getCopyOfContextMap(); + this.span = span; + } + + @Override + public Span activate() { + toRestore = tlsSnapshot.get(); + tlsSnapshot.set(this); + return span; + } + + @Override + public void close() { + this.deactivate(true); + } + + @Override + public Span span() { + return span; + } + + @Override + public void deactivate(boolean finishSpan) { + if (span != null && finishSpan) { + span.finish(); + } + + if (tlsSnapshot.get() != this) { + // This probably shouldn't happen. + // + // XXX: log or throw something here? + return; + } + tlsSnapshot.set(toRestore); + } + } + + @Override + public MDCSnapshot captureActive() { + return new MDCSnapshot(active()); + } + + @Override + public SpanClosure capture(Span span) { + return new MDCSnapshot(span); + } + + @Override + public Span active() { + MDCSnapshot snapshot = tlsSnapshot.get(); + if (snapshot == null) { + return null; + } + return snapshot.span; + } + + @Override + public void onFinish(Span span) { + MDCSnapshot snapshot = tlsSnapshot.get(); + MDCSnapshot prevSnapshot = null; + while (snapshot != null) { + if (snapshot.span == span) { + if (prevSnapshot == null) { + tlsSnapshot.set(snapshot.toRestore); + } else { + prevSnapshot.toRestore = snapshot.toRestore; + } + } + prevSnapshot = snapshot; + snapshot = snapshot.toRestore; + } + } +} diff --git a/opentracing-mdc-demo/src/main/java/io/opentracing/mdcdemo/TracedCallable.java b/opentracing-mdc-demo/src/main/java/io/opentracing/mdcdemo/TracedCallable.java new file mode 100644 index 00000000..beae2b2a --- /dev/null +++ b/opentracing-mdc-demo/src/main/java/io/opentracing/mdcdemo/TracedCallable.java @@ -0,0 +1,37 @@ +package io.opentracing.mdcdemo; + +import io.opentracing.SpanManager; +import io.opentracing.Span; +import io.opentracing.impl.GlobalTracer; + +import java.util.concurrent.Callable; + +public class TracedCallable implements Callable { + private SpanManager.SpanClosure spanClosure; + private SpanManager manager; + private Callable callable; + + public TracedCallable(Callable callable) { + this(callable, GlobalTracer.get().activeSpanManager()); + } + + public TracedCallable(Callable callable, SpanManager manager) { + this(callable, manager.active(), manager); + } + + public TracedCallable(Callable callable, Span span, SpanManager manager) { + if (callable == null) throw new NullPointerException("Callable is ."); + this.callable = callable; + this.manager = manager; + this.spanClosure = manager.captureActive(); + } + + public T call() throws Exception { + final Span span = spanClosure.activate(); + try { + return callable.call(); + } finally { + spanClosure.deactivate(false); + } + } +} diff --git a/opentracing-mdc-demo/src/main/java/io/opentracing/mdcdemo/TracedExecutorService.java b/opentracing-mdc-demo/src/main/java/io/opentracing/mdcdemo/TracedExecutorService.java new file mode 100644 index 00000000..4f7168b6 --- /dev/null +++ b/opentracing-mdc-demo/src/main/java/io/opentracing/mdcdemo/TracedExecutorService.java @@ -0,0 +1,100 @@ +package io.opentracing.mdcdemo; + +import io.opentracing.SpanManager; +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 SpanManager manager; + + public TracedExecutorService(ExecutorService executor){ + this(executor, GlobalTracer.get().activeSpanManager()); + } + + public TracedExecutorService(ExecutorService executor, SpanManager manager) { + if (executor == null) throw new NullPointerException("Executor is ."); + if (manager == null) throw new NullPointerException("SpanManager 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-mdc-demo/src/main/java/io/opentracing/mdcdemo/TracedRunnable.java b/opentracing-mdc-demo/src/main/java/io/opentracing/mdcdemo/TracedRunnable.java new file mode 100644 index 00000000..fae446ea --- /dev/null +++ b/opentracing-mdc-demo/src/main/java/io/opentracing/mdcdemo/TracedRunnable.java @@ -0,0 +1,41 @@ +package io.opentracing.mdcdemo; + +import io.opentracing.SpanManager; +import io.opentracing.Span; +import io.opentracing.impl.GlobalTracer; + + +public class TracedRunnable implements Runnable { + private Runnable runnable; + private SpanManager manager; + private SpanManager.SpanClosure spanClosure; + + 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, SpanManager manager) { + this(runnable, manager.active(), manager); + } + + public TracedRunnable(Runnable runnable, Span span, SpanManager manager) { + if (runnable == null) throw new NullPointerException("Runnable is ."); + this.runnable = runnable; + this.manager = manager; + this.spanClosure = manager.captureActive(); + } + + @Override + public void run() { + final Span span = this.spanClosure.activate(); + try { + runnable.run(); + } finally { + this.spanClosure.deactivate(false); + } + } +} diff --git a/opentracing-mock/pom.xml b/opentracing-mock/pom.xml index 7c3dbffa..86964e41 100644 --- a/opentracing-mock/pom.xml +++ b/opentracing-mock/pom.xml @@ -36,6 +36,11 @@ ${project.groupId} opentracing-api + + io.opentracing + opentracing-impl + test + 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 c432090d..46f2c9bd 100644 --- a/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java +++ b/opentracing-mock/src/main/java/io/opentracing/mock/MockSpan.java @@ -106,9 +106,13 @@ public void finish() { @Override public synchronized void finish(long finishMicros) { finishedCheck("Finishing already finished span"); + this.finishMicros = finishMicros; this.mockTracer.appendFinishedSpan(this); this.finished = true; + if (this.mockTracer.activeSpanManager() != null) { + this.mockTracer.activeSpanManager().onFinish(this); + } } @Override 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..f3d9e6dc 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,11 +35,17 @@ public class MockTracer implements Tracer { private List finishedSpans = new ArrayList<>(); private final Propagator propagator; + private SpanManager spanManager; public MockTracer() { this(Propagator.PRINTER); } + public MockTracer(SpanManager manager) { + this(Propagator.PRINTER); + this.spanManager = manager; + } + /** * Create a new MockTracer that passes through any calls to inject() and/or extract(). */ @@ -145,8 +148,20 @@ public MockSpan.MockContext extract(Format format, C carrier) { } @Override - public Tracer.SpanBuilder buildSpan(String operationName) { - return new SpanBuilder(operationName); + public SpanBuilder buildSpan(String operationName) { + SpanBuilder sb = new SpanBuilder(operationName); + if (this.spanManager != null) { + Span active = this.spanManager.active(); + if (active != null) { + sb.asChildOf(active.context()); + } + } + return sb; + } + + @Override + public SpanManager activeSpanManager() { + return spanManager; } @Override @@ -164,7 +179,7 @@ synchronized void appendFinishedSpan(MockSpan mockSpan) { this.onSpanFinished(mockSpan); } - final class SpanBuilder implements Tracer.SpanBuilder { + public final class SpanBuilder implements Tracer.SpanBuilder { private final String operationName; private long startMicros; private MockSpan.MockContext firstParent; @@ -174,17 +189,17 @@ final class SpanBuilder implements Tracer.SpanBuilder { this.operationName = operationName; } @Override - public Tracer.SpanBuilder asChildOf(SpanContext parent) { + public SpanBuilder asChildOf(SpanContext parent) { return addReference(References.CHILD_OF, parent); } @Override - public Tracer.SpanBuilder asChildOf(Span parent) { + public SpanBuilder asChildOf(Span parent) { return addReference(References.CHILD_OF, parent.context()); } @Override - public Tracer.SpanBuilder addReference(String referenceType, SpanContext referencedContext) { + public SpanBuilder addReference(String referenceType, SpanContext referencedContext) { if (firstParent == null && ( referenceType.equals(References.CHILD_OF) || referenceType.equals(References.FOLLOWS_FROM))) { this.firstParent = (MockSpan.MockContext)referencedContext; @@ -193,35 +208,45 @@ public Tracer.SpanBuilder addReference(String referenceType, SpanContext referen } @Override - public Tracer.SpanBuilder withTag(String key, String value) { + public SpanBuilder withTag(String key, String value) { this.initialTags.put(key, value); return this; } @Override - public Tracer.SpanBuilder withTag(String key, boolean value) { + public SpanBuilder withTag(String key, boolean value) { this.initialTags.put(key, value); return this; } @Override - public Tracer.SpanBuilder withTag(String key, Number value) { + public SpanBuilder withTag(String key, Number value) { this.initialTags.put(key, value); return this; } @Override - public Tracer.SpanBuilder withStartTimestamp(long microseconds) { + public SpanBuilder withStartTimestamp(long microseconds) { this.startMicros = microseconds; return this; } @Override - public Span start() { + public MockSpan start() { if (this.startMicros == 0) { this.startMicros = MockSpan.nowMicros(); } - return new MockSpan(MockTracer.this, this.operationName, this.startMicros, initialTags, this.firstParent); + MockSpan rval = new MockSpan(MockTracer.this, this.operationName, this.startMicros, initialTags, this.firstParent); + if (MockTracer.this.spanManager != null) { + MockTracer.this.spanManager.capture(rval).activate(); + } + return rval; + } + + @Override + public SpanManager.SpanClosure startAndActivate() { + MockSpan span = this.start(); + return MockTracer.this.spanManager.capture(span); } @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..562a42e5 100644 --- a/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java +++ b/opentracing-mock/src/test/java/io/opentracing/mock/MockTracerTest.java @@ -16,20 +16,24 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.*; +import io.opentracing.*; +import io.opentracing.mock.MockTracer; import org.junit.Assert; import org.junit.Test; -import io.opentracing.Span; -import io.opentracing.Tracer; -import io.opentracing.SpanContext; import io.opentracing.propagation.Format; import io.opentracing.propagation.TextMapExtractAdapter; import io.opentracing.propagation.TextMapInjectAdapter; +import org.slf4j.Logger; +import org.slf4j.MDC; + public class MockTracerTest { @Test public void testRootSpan() { diff --git a/opentracing-noop/src/main/java/io/opentracing/NoopSpan.java b/opentracing-noop/src/main/java/io/opentracing/NoopSpan.java index 8e971804..1a5e20c5 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; } diff --git a/opentracing-noop/src/main/java/io/opentracing/NoopSpanBuilder.java b/opentracing-noop/src/main/java/io/opentracing/NoopSpanBuilder.java index 30c5e20c..85e85979 100644 --- a/opentracing-noop/src/main/java/io/opentracing/NoopSpanBuilder.java +++ b/opentracing-noop/src/main/java/io/opentracing/NoopSpanBuilder.java @@ -62,6 +62,9 @@ public Span start() { return NoopSpanImpl.INSTANCE; } + @Override + public SpanManager.SpanClosure startAndActivate() { return null; } // XXX: not correct + @Override public Iterable> baggageItems() { return Collections.EMPTY_MAP.entrySet(); 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..11a9aec6 100644 --- a/opentracing-noop/src/main/java/io/opentracing/NoopTracer.java +++ b/opentracing-noop/src/main/java/io/opentracing/NoopTracer.java @@ -24,6 +24,11 @@ final class NoopTracerImpl implements NoopTracer { @Override public SpanBuilder buildSpan(String operationName) { return NoopSpanBuilderImpl.INSTANCE; } + public void setSpanManager(SpanManager mgr) {} + + @Override + public SpanManager activeSpanManager() { return null; } + @Override public void inject(SpanContext spanContext, Format format, C carrier) {} diff --git a/pom.xml b/pom.xml index 047c24c3..132c335b 100644 --- a/pom.xml +++ b/pom.xml @@ -238,7 +238,7 @@ true - + maven-release-plugin ${maven-release-plugin.version}