Add internal scope listener api
MODIFY
dd-trace-api/src/main/java/datadog/trace/api/GlobalTracer.java
dd-trace-api/src/main/java/datadog/trace/api/GlobalTracer.java
package datadog.trace.api;
import datadog.trace.api.interceptor.TraceInterceptor;
+import datadog.trace.context.ScopeListener;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -26,6 +27,9 @@ public class GlobalTracer {
public boolean addTraceInterceptor(TraceInterceptor traceInterceptor) {
return false;
}
+
+ @Override
+ public void addScopeListener(ScopeListener listener) {}
};
private static final AtomicReference<Tracer> provider = new AtomicReference<>(NO_OP);

MODIFY
dd-trace-api/src/main/java/datadog/trace/api/Tracer.java
dd-trace-api/src/main/java/datadog/trace/api/Tracer.java
package datadog.trace.api;
import datadog.trace.api.interceptor.TraceInterceptor;
+import datadog.trace.context.ScopeListener;
/** A class with Datadog tracer features. */
public interface Tracer {
@@ -21,4 +22,11 @@ public interface Tracer {
* @return false if an interceptor with same priority exists.
*/
boolean addTraceInterceptor(TraceInterceptor traceInterceptor);
+
+ /**
+ * Attach a scope listener to the global scope manager
+ *
+ * @param listener listener to attach
+ */
+ void addScopeListener(ScopeListener listener);
}

ADD
dd-trace-api/src/main/java/datadog/trace/context/ScopeListener.java
+package datadog.trace.context;
+
+/** Hooks for scope activation */
+public interface ScopeListener {
+ /**
+ * Called just after a scope becomes the active scope
+ *
+ * <p>May be called multiple times. When a scope is initially created, or after a child scope is
+ * deactivated.
+ */
+ void afterScopeActivated();
+
+ /** Called just after a scope is closed. */
+ void afterScopeClose();
+}

MODIFY
dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java
dd-trace-ot/src/main/java/datadog/opentracing/DDTracer.java
@@ -17,6 +17,7 @@ import datadog.trace.common.sampling.Sampler;
import datadog.trace.common.writer.DDAgentWriter;
import datadog.trace.common.writer.DDApi;
import datadog.trace.common.writer.Writer;
+import datadog.trace.context.ScopeListener;
import io.opentracing.References;
import io.opentracing.Scope;
import io.opentracing.ScopeManager;
@@ -354,6 +355,11 @@ public class DDTracer implements io.opentracing.Tracer, Closeable, datadog.trace
return interceptors.add(interceptor);
}
+ @Override
+ public void addScopeListener(ScopeListener listener) {
+ this.scopeManager.addScopeListener(listener);
+ }
+
@Override
public void close() {
PendingTrace.close();

MODIFY
dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/ContextualScopeManager.java
dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/ContextualScopeManager.java
package datadog.opentracing.scopemanager;
import datadog.opentracing.DDSpan;
+import datadog.trace.context.ScopeListener;
import io.opentracing.Scope;
import io.opentracing.ScopeManager;
import io.opentracing.Span;
import java.util.Deque;
+import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.CopyOnWriteArrayList;
public class ContextualScopeManager implements ScopeManager {
static final ThreadLocal<Scope> tlsScope = new ThreadLocal<>();
final Deque<ScopeContext> scopeContexts = new ConcurrentLinkedDeque<>();
+ final List<ScopeListener> scopeListeners = new CopyOnWriteArrayList<>();
@Override
public Scope activate(final Span span, final boolean finishOnClose) {
@@ -38,4 +42,9 @@ public class ContextualScopeManager implements ScopeManager {
public void addScopeContext(final ScopeContext context) {
scopeContexts.addFirst(context);
}
+
+ /** Attach a listener to scope activation events */
+ public void addScopeListener(ScopeListener listener) {
+ scopeListeners.add(listener);
+ }
}

MODIFY
dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/ContinuableScope.java
dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/ContinuableScope.java
@@ -3,6 +3,7 @@ package datadog.opentracing.scopemanager;
import datadog.opentracing.DDSpan;
import datadog.opentracing.DDSpanContext;
import datadog.opentracing.PendingTrace;
+import datadog.trace.context.ScopeListener;
import datadog.trace.context.TraceScope;
import io.opentracing.Scope;
import java.io.Closeable;
@@ -50,6 +51,9 @@ public class ContinuableScope implements Scope, TraceScope {
this.finishOnClose = finishOnClose;
toRestore = scopeManager.tlsScope.get();
scopeManager.tlsScope.set(this);
+ for (final ScopeListener listener : scopeManager.scopeListeners) {
+ listener.afterScopeActivated();
+ }
}
@Override
@@ -62,8 +66,17 @@ public class ContinuableScope implements Scope, TraceScope {
spanUnderScope.finish();
}
+ for (final ScopeListener listener : scopeManager.scopeListeners) {
+ listener.afterScopeClose();
+ }
+
if (scopeManager.tlsScope.get() == this) {
scopeManager.tlsScope.set(toRestore);
+ if (toRestore != null) {
+ for (final ScopeListener listener : scopeManager.scopeListeners) {
+ listener.afterScopeActivated();
+ }
+ }
}
}

MODIFY
dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/SimpleScope.java
dd-trace-ot/src/main/java/datadog/opentracing/scopemanager/SimpleScope.java
package datadog.opentracing.scopemanager;
+import datadog.trace.context.ScopeListener;
import io.opentracing.Scope;
import io.opentracing.Span;
@@ -19,6 +20,9 @@ public class SimpleScope implements Scope {
this.finishOnClose = finishOnClose;
this.toRestore = scopeManager.tlsScope.get();
scopeManager.tlsScope.set(this);
+ for (final ScopeListener listener : scopeManager.scopeListeners) {
+ listener.afterScopeActivated();
+ }
}
@Override
@@ -26,9 +30,17 @@ public class SimpleScope implements Scope {
if (finishOnClose) {
spanUnderScope.finish();
}
+ for (final ScopeListener listener : scopeManager.scopeListeners) {
+ listener.afterScopeClose();
+ }
if (scopeManager.tlsScope.get() == this) {
scopeManager.tlsScope.set(toRestore);
+ if (toRestore != null) {
+ for (final ScopeListener listener : scopeManager.scopeListeners) {
+ listener.afterScopeActivated();
+ }
+ }
}
}

MODIFY
dd-trace-ot/src/test/groovy/datadog/opentracing/scopemanager/ScopeManagerTest.groovy
dd-trace-ot/src/test/groovy/datadog/opentracing/scopemanager/ScopeManagerTest.groovy
@@ -5,6 +5,7 @@ import datadog.opentracing.DDSpanContext
import datadog.opentracing.DDTracer
import datadog.trace.agent.test.TestUtils
import datadog.trace.common.writer.ListWriter
+import datadog.trace.context.ScopeListener
import io.opentracing.Scope
import io.opentracing.Span
import io.opentracing.noop.NoopSpan
@@ -15,6 +16,7 @@ import spock.lang.Timeout
import java.lang.ref.WeakReference
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference
class ScopeManagerTest extends Specification {
@@ -407,6 +409,52 @@ class ScopeManagerTest extends Specification {
[new AtomicReferenceScope(true), new AtomicReferenceScope(true)] | _
}
+ def "add scope listener"() {
+ setup:
+ AtomicInteger activatedCount = new AtomicInteger(0)
+ AtomicInteger closedCount = new AtomicInteger(0)
+
+ scopeManager.addScopeListener(new ScopeListener() {
+ @Override
+ void afterScopeActivated() {
+ activatedCount.incrementAndGet()
+ }
+
+ @Override
+ void afterScopeClose() {
+ closedCount.incrementAndGet()
+ }
+ })
+
+ when:
+ Scope scope1 = scopeManager.activate(NoopSpan.INSTANCE, true)
+
+ then:
+ activatedCount.get() == 1
+ closedCount.get() == 0
+
+ when:
+ Scope scope2 = scopeManager.activate(NoopSpan.INSTANCE, true)
+
+ then:
+ activatedCount.get() == 2
+ closedCount.get() == 0
+
+ when:
+ scope2.close()
+
+ then:
+ activatedCount.get() == 3
+ closedCount.get() == 1
+
+ when:
+ scope1.close()
+
+ then:
+ activatedCount.get() == 3
+ closedCount.get() == 2
+ }
+
boolean spanFinished(Span span) {
return ((DDSpan) span).isFinished()
}

Add internal scope listener api