diff --git a/README.md b/README.md
index 0418cf8..5833959 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,62 @@
This project provides API extensions to the core OpenTracing APIs.
-## Observer
+## APIs
-TODO: Provide some code snippets for use of Observer API.
+The APIs can be included using the following:
+
+```xml
+
+ io.opentracing.contrib
+ opentracing-api-extensions
+
+
+```
+
+### Observer
+
+The Observer API can be used to monitor Span related activity and perform additional
+tasks.
+
+There are two types of Observer, stateful and stateless.
+
+* A stateful observer will use
+identity information supplied with the `SpanData`, to maintain state information about a particular
+`Span` instance - and at the appropriate time perform some action using the accumulated information.
+
+* A stateless observer will not maintain any local state information about the `Span` instances, and
+instead perform tasks directly in the callback that provides the event/information of interest (e.g. recording
+metrics `onFinish` or logging events when `onLog` is called).
+
+The benefit of a stateless approach is that the same observer instance (i.e. singleton) can be used for
+all `Span` instances. Whereas the stateful approach will require an observer instance to be instantiated
+for each call to `TracerObserver.onStart()`.
+
+
+## Registering API extensions
+
+There are two ways an extension API can be registered for use with a `Tracer`.
+
+1) Native support within the `Tracer` implementation
+
+Some `Tracer` implementations may decide to implement support for the extension APIs directly, in which
+case an implementation specific mechanism will be provided for registering the APIs.
+
+2) Using the extension `Tracer` wrapper
+
+```xml
+
+ io.opentracing.contrib
+ opentracing-api-extensions-tracer
+
+
+```
+
+The `io.opentracing.contrib.api.tracer.APIExtensionsTracer` provides a single constructor which is supplied
+the `Tracer` instance to be wrapped.
+
+This class also provides `addTracerObserver` and `removeTracerObserver` methods to enable a `TracerObserver`
+instance to be registered with the tracer wrapper, and perform relevant tasks when new spans are started.
## Release
diff --git a/opentracing-api-extensions-tracer/pom.xml b/opentracing-api-extensions-tracer/pom.xml
new file mode 100644
index 0000000..80399a0
--- /dev/null
+++ b/opentracing-api-extensions-tracer/pom.xml
@@ -0,0 +1,46 @@
+
+
+
+ 4.0.0
+
+ opentracing-api-extensions-parent
+ io.opentracing.contrib
+ 0.0.2-SNAPSHOT
+
+
+ opentracing-api-extensions-tracer
+
+
+
+ io.opentracing.contrib
+ opentracing-api-extensions
+
+
+
+ org.mockito
+ mockito-all
+ test
+
+
+ junit
+ junit
+ ${version.junit}
+ test
+
+
+
+
diff --git a/opentracing-api-extensions-tracer/src/main/java/io/opentracing/contrib/api/tracer/APIExtensionsSpan.java b/opentracing-api-extensions-tracer/src/main/java/io/opentracing/contrib/api/tracer/APIExtensionsSpan.java
new file mode 100644
index 0000000..dec956e
--- /dev/null
+++ b/opentracing-api-extensions-tracer/src/main/java/io/opentracing/contrib/api/tracer/APIExtensionsSpan.java
@@ -0,0 +1,270 @@
+/**
+ * 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.
+ */
+package io.opentracing.contrib.api.tracer;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+
+import io.opentracing.Span;
+import io.opentracing.SpanContext;
+import io.opentracing.contrib.api.SpanData;
+import io.opentracing.contrib.api.SpanObserver;
+
+public class APIExtensionsSpan implements Span, SpanData {
+
+ private final Span wrappedSpan;
+
+ private String operationName;
+ private final long startTimestampMicro;
+ private long finishTimestampMicro;
+ private final long startTimeNano;
+ private long finishTimeNano;
+ private final Map tags;
+
+ private final List observers = new CopyOnWriteArrayList();
+
+ private final UUID correlationId = UUID.randomUUID();
+
+ /**
+ * This is the constructor for the extensions API span wrapper.
+ *
+ * @param span The span being wrapped
+ * @param operationName The operation name
+ * @param startTimestampMicro The start timestamp (microseconds)
+ * @param startTimeNano The start nano time, or 0 if the start timestamp was explicitly provided by the app
+ * @param tags The initial tags
+ */
+ APIExtensionsSpan(Span span, String operationName,
+ long startTimestampMicro, long startTimeNano, Map tags) {
+ this.wrappedSpan = span;
+ this.operationName = operationName;
+ this.startTimestampMicro = startTimestampMicro;
+ this.startTimeNano = startTimeNano;
+ this.tags = tags;
+ }
+
+ /**
+ * This method adds a new {@link SpanObserver}.
+ *
+ * @param observer The observer
+ */
+ public void addSpanObserver(SpanObserver observer) {
+ if (observer != null) {
+ observers.add(observer);
+ }
+ }
+
+ /**
+ * This method removes a {@link SpanObserver}.
+ *
+ * @param observer The observer
+ */
+ public void removeSpanObserver(SpanObserver observer) {
+ if (observer != null) {
+ observers.remove(observer);
+ }
+ }
+
+ @Override
+ public SpanContext context() {
+ return wrappedSpan.context();
+ }
+
+ @Override
+ public Object getCorrelationId() {
+ return correlationId;
+ }
+
+ @Override
+ public long getStartTime() {
+ return startTimestampMicro;
+ }
+
+ @Override
+ public long getFinishTime() {
+ return finishTimestampMicro;
+ }
+
+ @Override
+ public Span setOperationName(String operationName) {
+ wrappedSpan.setOperationName(operationName);
+ this.operationName = operationName;
+ for (SpanObserver observer : observers) {
+ observer.onSetOperationName(this, operationName);
+ }
+ return this;
+ }
+
+ @Override
+ public String getOperationName() {
+ return operationName;
+ }
+
+ @Override
+ public String getBaggageItem(String name) {
+ return wrappedSpan.getBaggageItem(name);
+ }
+
+ @Override
+ public Span setBaggageItem(String name, String value) {
+ wrappedSpan.setBaggageItem(name, value);
+ for (SpanObserver observer : observers) {
+ observer.onSetBaggageItem(this, name, value);
+ }
+ return this;
+ }
+
+ @Override
+ public Span log(Map fields) {
+ wrappedSpan.log(fields);
+ return handleLog(TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()), fields);
+ }
+
+ @Override
+ public Span log(long timestampMicroseconds, Map fields) {
+ wrappedSpan.log(timestampMicroseconds, fields);
+ return handleLog(timestampMicroseconds, fields);
+ }
+
+ private Span handleLog(long timestampMicroseconds, Map fields) {
+ for (SpanObserver observer : observers) {
+ observer.onLog(this, timestampMicroseconds, fields);
+ }
+ return this;
+ }
+
+ @Override
+ public Span log(String event) {
+ wrappedSpan.log(event);
+ return handleLog(TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()), event);
+ }
+
+ @Override
+ public Span log(long timestampMicroseconds, String event) {
+ wrappedSpan.log(timestampMicroseconds, event);
+ return handleLog(timestampMicroseconds, event);
+ }
+
+ private Span handleLog(long timestampMicroseconds, String event) {
+ for (SpanObserver observer : observers) {
+ observer.onLog(this, timestampMicroseconds, event);
+ }
+ return this;
+ }
+
+ @Override
+ public Span setTag(String key, String value) {
+ wrappedSpan.setTag(key, value);
+ return handleSetTag(key, value);
+ }
+
+ @Override
+ public Span setTag(String key, boolean value) {
+ wrappedSpan.setTag(key, value);
+ return handleSetTag(key, value);
+ }
+
+ @Override
+ public Span setTag(String key, Number value) {
+ wrappedSpan.setTag(key, value);
+ return handleSetTag(key, value);
+ }
+
+ private Span handleSetTag(String key, Object value) {
+ tags.put(key, value);
+ for (SpanObserver observer : observers) {
+ observer.onSetTag(this, key, value);
+ }
+ return this;
+ }
+
+ @Override
+ public Map getTags() {
+ return Collections.unmodifiableMap(tags);
+ }
+
+ @Override
+ public String getStringTag(String key) {
+ Object value = tags.get(key);
+ if (value instanceof String) {
+ return (String) value;
+ }
+ return null;
+ }
+
+ @Override
+ public Number getNumberTag(String key) {
+ Object value = tags.get(key);
+ if (value instanceof Number) {
+ return (Number) value;
+ }
+ return null;
+ }
+
+ @Override
+ public Boolean getBooleanTag(String key) {
+ Object value = tags.get(key);
+ if (value instanceof Boolean) {
+ return (Boolean) value;
+ }
+ return null;
+ }
+
+ @Override
+ public void finish() {
+ wrappedSpan.finish();
+ // Only set the finish nano time if not explicitly providing a timestamp
+ finishTimeNano = System.nanoTime();
+ handleFinish(TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()));
+ }
+
+ @Override
+ public void finish(long finishMicros) {
+ wrappedSpan.finish(finishMicros);
+ handleFinish(finishMicros);
+ }
+
+ private void handleFinish(long finishMicros) {
+ finishTimestampMicro = finishMicros;
+ for (SpanObserver observer : observers) {
+ observer.onFinish(this, finishMicros);
+ }
+ }
+
+ @Override
+ public long getDuration() {
+ // If start or finish nano times are not available, then use timestamps
+ if (startTimeNano == 0 || finishTimeNano == 0) {
+ return finishTimestampMicro == 0 ? 0 : finishTimestampMicro - startTimestampMicro;
+ }
+ return TimeUnit.NANOSECONDS.toMicros(finishTimeNano - startTimeNano);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public Span log(String eventName, Object payload) {
+ return wrappedSpan.log(eventName, payload);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public Span log(long timestampMicroseconds, String eventName, Object payload) {
+ return wrappedSpan.log(timestampMicroseconds, eventName, payload);
+ }
+
+}
diff --git a/opentracing-api-extensions-tracer/src/main/java/io/opentracing/contrib/api/tracer/APIExtensionsSpanBuilder.java b/opentracing-api-extensions-tracer/src/main/java/io/opentracing/contrib/api/tracer/APIExtensionsSpanBuilder.java
new file mode 100644
index 0000000..f1ca441
--- /dev/null
+++ b/opentracing-api-extensions-tracer/src/main/java/io/opentracing/contrib/api/tracer/APIExtensionsSpanBuilder.java
@@ -0,0 +1,130 @@
+/**
+ * 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.
+ */
+package io.opentracing.contrib.api.tracer;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+import io.opentracing.ActiveSpan;
+import io.opentracing.BaseSpan;
+import io.opentracing.Span;
+import io.opentracing.SpanContext;
+import io.opentracing.Tracer;
+import io.opentracing.Tracer.SpanBuilder;
+import io.opentracing.contrib.api.TracerObserver;
+
+public class APIExtensionsSpanBuilder implements SpanBuilder {
+
+ private final Tracer tracer;
+ private final List observers;
+
+ private final String operationName;
+ private final SpanBuilder wrappedBuilder;
+ private long startTimestampMicro = TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis());
+ private long startTimeNano = System.nanoTime();
+ private final Map tags = new ConcurrentHashMap();
+
+ APIExtensionsSpanBuilder(Tracer tracer, List observers,
+ String operationName, SpanBuilder builder) {
+ this.tracer = tracer;
+ this.observers = observers;
+ this.operationName = operationName;
+ this.wrappedBuilder = builder;
+ }
+
+ @Override
+ public SpanBuilder asChildOf(SpanContext parent) {
+ wrappedBuilder.asChildOf(parent);
+ return this;
+ }
+
+ @Override
+ public SpanBuilder asChildOf(BaseSpan> parent) {
+ wrappedBuilder.asChildOf(parent);
+ return this;
+ }
+
+ @Override
+ public SpanBuilder addReference(String referenceType, SpanContext referencedContext) {
+ wrappedBuilder.addReference(referenceType, referencedContext);
+ return this;
+ }
+
+ @Override
+ public SpanBuilder ignoreActiveSpan() {
+ wrappedBuilder.ignoreActiveSpan();
+ return this;
+ }
+
+ @Override
+ public SpanBuilder withTag(String key, String value) {
+ tags.put(key, value);
+ wrappedBuilder.withTag(key, value);
+ return this;
+ }
+
+ @Override
+ public SpanBuilder withTag(String key, boolean value) {
+ tags.put(key, value);
+ wrappedBuilder.withTag(key, value);
+ return this;
+ }
+
+ @Override
+ public SpanBuilder withTag(String key, Number value) {
+ tags.put(key, value);
+ wrappedBuilder.withTag(key, value);
+ return this;
+ }
+
+ @Override
+ public SpanBuilder withStartTimestamp(long microseconds) {
+ wrappedBuilder.withStartTimestamp(microseconds);
+ // Reset the nano start time, so that duration will be calculated based on explicitly
+ // provided timestamps
+ this.startTimeNano = 0;
+ return this;
+ }
+
+ @Override
+ public ActiveSpan startActive() {
+ return tracer.makeActive(startManual());
+ }
+
+ @Override
+ public Span startManual() {
+ APIExtensionsSpan span = new APIExtensionsSpan(wrappedBuilder.startManual(),
+ operationName, startTimestampMicro, startTimeNano, tags);
+ for (TracerObserver observer : observers) {
+ span.addSpanObserver(observer.onStart(span));
+ }
+ return span;
+ }
+
+ @Override
+ public Span start() {
+ return startManual();
+ }
+
+ Map tags() {
+ return tags;
+ }
+
+ long startTimeNano() {
+ return startTimeNano;
+ }
+
+}
diff --git a/opentracing-api-extensions-tracer/src/main/java/io/opentracing/contrib/api/tracer/APIExtensionsTracer.java b/opentracing-api-extensions-tracer/src/main/java/io/opentracing/contrib/api/tracer/APIExtensionsTracer.java
new file mode 100644
index 0000000..000551b
--- /dev/null
+++ b/opentracing-api-extensions-tracer/src/main/java/io/opentracing/contrib/api/tracer/APIExtensionsTracer.java
@@ -0,0 +1,71 @@
+/**
+ * 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.
+ */
+package io.opentracing.contrib.api.tracer;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import io.opentracing.ActiveSpan;
+import io.opentracing.Span;
+import io.opentracing.SpanContext;
+import io.opentracing.Tracer;
+import io.opentracing.contrib.api.TracerObserver;
+import io.opentracing.propagation.Format;
+
+public class APIExtensionsTracer implements Tracer {
+
+ private final Tracer wrappedTracer;
+ private final List observers = new CopyOnWriteArrayList();
+
+ public APIExtensionsTracer(Tracer tracer) {
+ this.wrappedTracer = tracer;
+ }
+
+ public void addTracerObserver(TracerObserver observer) {
+ if (observer != null) {
+ observers.add(observer);
+ }
+ }
+
+ public void removeTracerObserver(TracerObserver observer) {
+ if (observer != null) {
+ observers.remove(observer);
+ }
+ }
+
+ @Override
+ public ActiveSpan activeSpan() {
+ return wrappedTracer.activeSpan();
+ }
+
+ @Override
+ public ActiveSpan makeActive(Span span) {
+ return wrappedTracer.makeActive(span);
+ }
+
+ @Override
+ public SpanBuilder buildSpan(String operation) {
+ return new APIExtensionsSpanBuilder(wrappedTracer, observers, operation, wrappedTracer.buildSpan(operation));
+ }
+
+ @Override
+ public SpanContext extract(Format format, C carrier) {
+ return wrappedTracer.extract(format, carrier);
+ }
+
+ @Override
+ public void inject(SpanContext context, Format format, C carrier) {
+ wrappedTracer.inject(context, format, carrier);
+ }
+
+}
diff --git a/opentracing-api-extensions-tracer/src/test/java/io/opentracing/contrib/api/tracer/APIExtensionsSpanBuilderTest.java b/opentracing-api-extensions-tracer/src/test/java/io/opentracing/contrib/api/tracer/APIExtensionsSpanBuilderTest.java
new file mode 100644
index 0000000..98164f4
--- /dev/null
+++ b/opentracing-api-extensions-tracer/src/test/java/io/opentracing/contrib/api/tracer/APIExtensionsSpanBuilderTest.java
@@ -0,0 +1,165 @@
+/**
+ * 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.
+ */
+package io.opentracing.contrib.api.tracer;
+
+import static org.junit.Assert.*;
+
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import io.opentracing.ActiveSpan;
+import io.opentracing.References;
+import io.opentracing.Span;
+import io.opentracing.SpanContext;
+import io.opentracing.Tracer;
+import io.opentracing.Tracer.SpanBuilder;
+import io.opentracing.contrib.api.TracerObserver;
+
+public class APIExtensionsSpanBuilderTest {
+
+ @Captor
+ private ArgumentCaptor spanCaptor;
+
+ @Before
+ public void init(){
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testAsChildOfSpanContext() {
+ TestResources res = new TestResources();
+
+ res.extSpanBuilder.asChildOf(res.spanContext);
+ Mockito.verify(res.spanBuilder).asChildOf(res.spanContext);
+ }
+
+ @Test
+ public void testAsChildOfSpan() {
+ TestResources res = new TestResources();
+
+ res.extSpanBuilder.asChildOf(res.span);
+ Mockito.verify(res.spanBuilder).asChildOf(res.span);
+ }
+
+ @Test
+ public void testAddReference() {
+ TestResources res = new TestResources();
+
+ res.extSpanBuilder.addReference(References.FOLLOWS_FROM, res.spanContext);
+ Mockito.verify(res.spanBuilder).addReference(References.FOLLOWS_FROM, res.spanContext);
+ }
+
+ @Test
+ public void testIgnoreActiveSpan() {
+ TestResources res = new TestResources();
+
+ res.extSpanBuilder.ignoreActiveSpan();
+ Mockito.verify(res.spanBuilder).ignoreActiveSpan();
+ }
+
+ @Test
+ public void testWithTagString() {
+ TestResources res = new TestResources();
+
+ res.extSpanBuilder.withTag("tagName", "tagValue");
+ Mockito.verify(res.spanBuilder).withTag("tagName", "tagValue");
+ assertEquals("tagValue", res.extSpanBuilder.tags().get("tagName"));
+ }
+
+ @Test
+ public void testWithTagNumber() {
+ TestResources res = new TestResources();
+
+ res.extSpanBuilder.withTag("tagName", 5);
+ Mockito.verify(res.spanBuilder).withTag("tagName", 5);
+ assertEquals(5, res.extSpanBuilder.tags().get("tagName"));
+ }
+
+ @Test
+ public void testWithTagBoolean() {
+ TestResources res = new TestResources();
+
+ res.extSpanBuilder.withTag("tagName", Boolean.TRUE);
+ Mockito.verify(res.spanBuilder).withTag("tagName", Boolean.TRUE);
+ assertEquals(Boolean.TRUE, res.extSpanBuilder.tags().get("tagName"));
+ }
+
+ @Test
+ public void testWithStartTimestamp() {
+ TestResources res = new TestResources();
+
+ // Initially should have value, but will be reset to 0 when timestamp explicitly set
+ assertNotEquals(0, res.extSpanBuilder.startTimeNano());
+
+ long ts = TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis());
+ res.extSpanBuilder.withStartTimestamp(ts);
+ Mockito.verify(res.spanBuilder).withStartTimestamp(ts);
+
+ assertEquals(0, res.extSpanBuilder.startTimeNano());
+ }
+
+ @Test
+ public void testStartManual() {
+ TestResources res = new TestResources();
+
+ Span manualSpan = res.extSpanBuilder.startManual();
+ assertTrue(manualSpan instanceof APIExtensionsSpan);
+
+ APIExtensionsSpan extSpan = (APIExtensionsSpan)manualSpan;
+
+ Mockito.verify(res.observer).onStart(extSpan);
+ Mockito.verify(res.spanBuilder).startManual();
+ assertEquals("op", extSpan.getOperationName());
+ }
+
+ @Test
+ public void testStartActive() {
+ TestResources res = new TestResources();
+
+ Mockito.when(res.tracer.makeActive(spanCaptor.capture())).thenReturn(res.activeSpan);
+
+ ActiveSpan extActiveSpan = res.extSpanBuilder.startActive();
+
+ assertEquals(res.activeSpan, extActiveSpan);
+ assertTrue(spanCaptor.getValue() instanceof APIExtensionsSpan);
+ }
+
+ public class TestResources {
+ public Tracer tracer;
+ public SpanBuilder spanBuilder;
+ public TracerObserver observer;
+ public APIExtensionsSpanBuilder extSpanBuilder;
+ public SpanContext spanContext;
+ public Span span;
+ public ActiveSpan activeSpan;
+
+ public TestResources() {
+ tracer = Mockito.mock(Tracer.class);
+ spanBuilder = Mockito.mock(SpanBuilder.class);
+ observer = Mockito.mock(TracerObserver.class);
+ extSpanBuilder = new APIExtensionsSpanBuilder(tracer, Collections.singletonList(observer),
+ "op", spanBuilder);
+ spanContext = Mockito.mock(SpanContext.class);
+ span = Mockito.mock(Span.class);
+ activeSpan = Mockito.mock(ActiveSpan.class);
+ }
+ }
+}
diff --git a/opentracing-api-extensions-tracer/src/test/java/io/opentracing/contrib/api/tracer/APIExtensionsSpanTest.java b/opentracing-api-extensions-tracer/src/test/java/io/opentracing/contrib/api/tracer/APIExtensionsSpanTest.java
new file mode 100644
index 0000000..68b29d8
--- /dev/null
+++ b/opentracing-api-extensions-tracer/src/test/java/io/opentracing/contrib/api/tracer/APIExtensionsSpanTest.java
@@ -0,0 +1,231 @@
+/**
+ * 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.
+ */
+package io.opentracing.contrib.api.tracer;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.verify;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import io.opentracing.Span;
+import io.opentracing.contrib.api.SpanObserver;
+
+public class APIExtensionsSpanTest {
+
+ @Captor
+ private ArgumentCaptor longCaptor;
+
+ @Before
+ public void init(){
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testGetDurationFromTimestampsNotFinished() throws InterruptedException {
+ APIExtensionsSpan span = new APIExtensionsSpan(null, null,
+ TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()), 0, null);
+ synchronized(this) {
+ wait(100);
+ }
+ assertEquals(0, span.getDuration());
+ }
+
+ @Test
+ public void testGetDurationFromTimestampsFinished() throws InterruptedException {
+ APIExtensionsSpan span = new APIExtensionsSpan(Mockito.mock(Span.class), null,
+ TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()), 0, null);
+ synchronized(this) {
+ wait(100);
+ }
+ span.finish();
+ assertNotEquals(0, span.getDuration());
+ }
+
+ @Test
+ public void testGetDurationFromNanosNotFinished() throws InterruptedException {
+ APIExtensionsSpan span = new APIExtensionsSpan(null, null,
+ TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()), System.nanoTime(), null);
+ synchronized(this) {
+ wait(100);
+ }
+ assertEquals(0, span.getDuration());
+ }
+
+ @Test
+ public void testGetDurationFromNanosFinished() throws InterruptedException {
+ APIExtensionsSpan span = new APIExtensionsSpan(Mockito.mock(Span.class), null,
+ TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()), System.nanoTime(), null);
+ synchronized(this) {
+ wait(100);
+ }
+ span.finish();
+ assertNotEquals(0, span.getDuration());
+ }
+
+ @Test
+ public void testGetDurationNanosAccuracy() throws InterruptedException {
+ APIExtensionsSpan span = new APIExtensionsSpan(Mockito.mock(Span.class), null,
+ TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()), System.nanoTime(), null);
+ synchronized(this) {
+ wait(100);
+ }
+ span.finish();
+ assertNotEquals(0, span.getDuration() % 1000);
+ }
+
+ @Test
+ public void testGetDurationExplicitStartMillisAccuracy() throws InterruptedException {
+ APIExtensionsSpan span = new APIExtensionsSpan(Mockito.mock(Span.class), null,
+ TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()), 0, null);
+ synchronized(this) {
+ wait(100);
+ }
+ span.finish();
+ assertEquals(0, span.getDuration() % 1000);
+ }
+
+ @Test
+ public void testGetDurationExplicitFinishMillisAccuracy() throws InterruptedException {
+ APIExtensionsSpan span = new APIExtensionsSpan(Mockito.mock(Span.class), null,
+ TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()), System.nanoTime(), null);
+ synchronized(this) {
+ wait(100);
+ }
+ span.finish(TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()));
+ assertEquals(0, span.getDuration() % 1000);
+ }
+
+ @Test
+ public void testOnSetOperationName() throws InterruptedException {
+ APIExtensionsSpan span = new APIExtensionsSpan(Mockito.mock(Span.class), null,
+ 0, 0, null);
+ SpanObserver observer = Mockito.mock(SpanObserver.class);
+ span.addSpanObserver(observer);
+
+ span.setOperationName("testop");
+
+ verify(observer).onSetOperationName(span, "testop");
+ }
+
+ @Test
+ public void testOnSetTagString() throws InterruptedException {
+ APIExtensionsSpan span = new APIExtensionsSpan(Mockito.mock(Span.class), null,
+ 0, 0, new HashMap());
+ SpanObserver observer = Mockito.mock(SpanObserver.class);
+ span.addSpanObserver(observer);
+
+ span.setTag("testkey", "testvalue");
+
+ verify(observer).onSetTag(span, "testkey", "testvalue");
+ }
+
+ @Test
+ public void testOnSetTagNumber() throws InterruptedException {
+ APIExtensionsSpan span = new APIExtensionsSpan(Mockito.mock(Span.class), null,
+ 0, 0, new HashMap());
+ SpanObserver observer = Mockito.mock(SpanObserver.class);
+ span.addSpanObserver(observer);
+
+ span.setTag("testkey", 5);
+
+ verify(observer).onSetTag(span, "testkey", 5);
+ }
+
+ @Test
+ public void testOnSetTagBoolean() throws InterruptedException {
+ APIExtensionsSpan span = new APIExtensionsSpan(Mockito.mock(Span.class), null,
+ 0, 0, new HashMap());
+ SpanObserver observer = Mockito.mock(SpanObserver.class);
+ span.addSpanObserver(observer);
+
+ span.setTag("testkey", Boolean.TRUE);
+
+ verify(observer).onSetTag(span, "testkey", Boolean.TRUE);
+ }
+
+ @Test
+ public void testOnSetBaggageItem() throws InterruptedException {
+ APIExtensionsSpan span = new APIExtensionsSpan(Mockito.mock(Span.class), null,
+ 0, 0, null);
+ SpanObserver observer = Mockito.mock(SpanObserver.class);
+ span.addSpanObserver(observer);
+
+ span.setBaggageItem("testkey", "testvalue");
+
+ verify(observer).onSetBaggageItem(span, "testkey", "testvalue");
+ }
+
+ @Test
+ public void testOnLogFields() throws InterruptedException {
+ APIExtensionsSpan span = new APIExtensionsSpan(Mockito.mock(Span.class), null,
+ 0, 0, null);
+ SpanObserver observer = Mockito.mock(SpanObserver.class);
+ span.addSpanObserver(observer);
+
+ long ts = TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis());
+ Map fields = new HashMap();
+ span.log(ts, fields);
+
+ verify(observer).onLog(span, ts, fields);
+ }
+
+ @Test
+ public void testOnLogEvent() throws InterruptedException {
+ APIExtensionsSpan span = new APIExtensionsSpan(Mockito.mock(Span.class), null,
+ 0, 0, null);
+ SpanObserver observer = Mockito.mock(SpanObserver.class);
+ span.addSpanObserver(observer);
+
+ long ts = TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis());
+ span.log(ts, "testevent");
+
+ verify(observer).onLog(span, ts, "testevent");
+ }
+
+ @Test
+ public void testOnFinish() throws InterruptedException {
+ APIExtensionsSpan span = new APIExtensionsSpan(Mockito.mock(Span.class), null,
+ 0, 0, null);
+ SpanObserver observer = Mockito.mock(SpanObserver.class);
+ span.addSpanObserver(observer);
+
+ span.finish();
+
+ verify(observer).onFinish(Mockito.eq(span), longCaptor.capture());
+ assertNotEquals(0, longCaptor.getValue().longValue());
+ }
+
+ @Test
+ public void testOnFinishWithTimestamp() throws InterruptedException {
+ APIExtensionsSpan span = new APIExtensionsSpan(Mockito.mock(Span.class), null,
+ 0, 0, null);
+ SpanObserver observer = Mockito.mock(SpanObserver.class);
+ span.addSpanObserver(observer);
+
+ long ts = TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis());
+ span.finish(ts);
+
+ verify(observer).onFinish(span, ts);
+ }
+
+}
diff --git a/opentracing-api-extensions-tracer/src/test/java/io/opentracing/contrib/api/tracer/APIExtensionsTracerTest.java b/opentracing-api-extensions-tracer/src/test/java/io/opentracing/contrib/api/tracer/APIExtensionsTracerTest.java
new file mode 100644
index 0000000..b6766fb
--- /dev/null
+++ b/opentracing-api-extensions-tracer/src/test/java/io/opentracing/contrib/api/tracer/APIExtensionsTracerTest.java
@@ -0,0 +1,59 @@
+/**
+ * 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.
+ */
+package io.opentracing.contrib.api.tracer;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import io.opentracing.ActiveSpan;
+import io.opentracing.Span;
+import io.opentracing.Tracer;
+
+public class APIExtensionsTracerTest {
+
+ @Test
+ public void testActiveSpan() {
+ Tracer tracer = Mockito.mock(Tracer.class);
+ ActiveSpan activeSpan = Mockito.mock(ActiveSpan.class);
+ Mockito.when(tracer.activeSpan()).thenReturn(activeSpan);
+
+ APIExtensionsTracer extTracer = new APIExtensionsTracer(tracer);
+ assertEquals(activeSpan, extTracer.activeSpan());
+ }
+
+ @Test
+ public void testMakeActive() {
+ Tracer tracer = Mockito.mock(Tracer.class);
+ ActiveSpan activeSpan = Mockito.mock(ActiveSpan.class);
+ Span span = Mockito.mock(Span.class);
+ Mockito.when(tracer.makeActive(span)).thenReturn(activeSpan);
+
+ APIExtensionsTracer extTracer = new APIExtensionsTracer(tracer);
+ assertEquals(activeSpan, extTracer.makeActive(span));
+ }
+
+ @Test
+ public void testBuild() {
+ Tracer tracer = Mockito.mock(Tracer.class);
+ ActiveSpan activeSpan = Mockito.mock(ActiveSpan.class);
+ Span span = Mockito.mock(Span.class);
+ Mockito.when(tracer.makeActive(span)).thenReturn(activeSpan);
+
+ APIExtensionsTracer extTracer = new APIExtensionsTracer(tracer);
+ extTracer.buildSpan("testop");
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index ccca178..fd0aa1e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,6 +58,7 @@
opentracing-api-extensions
+ opentracing-api-extensions-tracer
@@ -67,6 +68,7 @@
0.30.0
4.12
+ 1.10.19
0.3.4
0.1.0
@@ -84,6 +86,19 @@
opentracing-api
${version.io.opentracing}
+
+
+ io.opentracing.contrib
+ opentracing-api-extensions
+ ${project.version}
+
+
+
+
+ org.mockito
+ mockito-all
+ ${version.org.mockito-mockito-all}
+