Added SpanProcessor OnEnding callback (#6367)
Co-authored-by: jack-berg <34418638+jack-berg@users.noreply.github.com>
This commit is contained in:
parent
bc2fad4cf3
commit
09de4bd105
|
|
@ -7,6 +7,7 @@ package io.opentelemetry.sdk.trace;
|
|||
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||
import io.opentelemetry.sdk.trace.internal.ExtendedSpanProcessor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
|
@ -16,8 +17,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
* Implementation of the {@code SpanProcessor} that simply forwards all received events to a list of
|
||||
* {@code SpanProcessor}s.
|
||||
*/
|
||||
final class MultiSpanProcessor implements SpanProcessor {
|
||||
final class MultiSpanProcessor implements ExtendedSpanProcessor {
|
||||
private final List<SpanProcessor> spanProcessorsStart;
|
||||
private final List<ExtendedSpanProcessor> spanProcessorsEnding;
|
||||
private final List<SpanProcessor> spanProcessorsEnd;
|
||||
private final List<SpanProcessor> spanProcessorsAll;
|
||||
private final AtomicBoolean isShutdown = new AtomicBoolean(false);
|
||||
|
|
@ -58,6 +60,18 @@ final class MultiSpanProcessor implements SpanProcessor {
|
|||
return !spanProcessorsEnd.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnding(ReadWriteSpan span) {
|
||||
for (ExtendedSpanProcessor spanProcessor : spanProcessorsEnding) {
|
||||
spanProcessor.onEnding(span);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnEndingRequired() {
|
||||
return !spanProcessorsEnding.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableResultCode shutdown() {
|
||||
if (isShutdown.getAndSet(true)) {
|
||||
|
|
@ -83,10 +97,17 @@ final class MultiSpanProcessor implements SpanProcessor {
|
|||
this.spanProcessorsAll = spanProcessors;
|
||||
this.spanProcessorsStart = new ArrayList<>(spanProcessorsAll.size());
|
||||
this.spanProcessorsEnd = new ArrayList<>(spanProcessorsAll.size());
|
||||
this.spanProcessorsEnding = new ArrayList<>(spanProcessorsAll.size());
|
||||
for (SpanProcessor spanProcessor : spanProcessorsAll) {
|
||||
if (spanProcessor.isStartRequired()) {
|
||||
spanProcessorsStart.add(spanProcessor);
|
||||
}
|
||||
if (spanProcessor instanceof ExtendedSpanProcessor) {
|
||||
ExtendedSpanProcessor extendedSpanProcessor = (ExtendedSpanProcessor) spanProcessor;
|
||||
if (extendedSpanProcessor.isOnEndingRequired()) {
|
||||
spanProcessorsEnding.add(extendedSpanProcessor);
|
||||
}
|
||||
}
|
||||
if (spanProcessor.isEndRequired()) {
|
||||
spanProcessorsEnd.add(spanProcessor);
|
||||
}
|
||||
|
|
@ -98,6 +119,8 @@ final class MultiSpanProcessor implements SpanProcessor {
|
|||
return "MultiSpanProcessor{"
|
||||
+ "spanProcessorsStart="
|
||||
+ spanProcessorsStart
|
||||
+ ", spanProcessorsEnding="
|
||||
+ spanProcessorsEnding
|
||||
+ ", spanProcessorsEnd="
|
||||
+ spanProcessorsEnd
|
||||
+ ", spanProcessorsAll="
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import io.opentelemetry.sdk.trace.data.EventData;
|
|||
import io.opentelemetry.sdk.trace.data.LinkData;
|
||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||
import io.opentelemetry.sdk.trace.data.StatusData;
|
||||
import io.opentelemetry.sdk.trace.internal.ExtendedSpanProcessor;
|
||||
import io.opentelemetry.sdk.trace.internal.data.ExceptionEventData;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
|
@ -95,9 +96,24 @@ final class SdkSpan implements ReadWriteSpan {
|
|||
@GuardedBy("lock")
|
||||
private long endEpochNanos;
|
||||
|
||||
// True if the span is ended.
|
||||
private enum EndState {
|
||||
NOT_ENDED,
|
||||
ENDING,
|
||||
ENDED
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
private boolean hasEnded;
|
||||
private EndState hasEnded;
|
||||
|
||||
/**
|
||||
* The thread on which {@link #end()} is called and which will be invoking the {@link
|
||||
* SpanProcessor}s. This field is used to ensure that only this thread may modify the span while
|
||||
* it is in state {@link EndState#ENDING} to prevent concurrent updates outside of {@link
|
||||
* ExtendedSpanProcessor#onEnding(ReadWriteSpan)}.
|
||||
*/
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private Thread spanEndingThread;
|
||||
|
||||
private SdkSpan(
|
||||
SpanContext context,
|
||||
|
|
@ -122,7 +138,7 @@ final class SdkSpan implements ReadWriteSpan {
|
|||
this.kind = kind;
|
||||
this.spanProcessor = spanProcessor;
|
||||
this.resource = resource;
|
||||
this.hasEnded = false;
|
||||
this.hasEnded = EndState.NOT_ENDED;
|
||||
this.clock = clock;
|
||||
this.startEpochNanos = startEpochNanos;
|
||||
this.attributes = attributes;
|
||||
|
|
@ -220,7 +236,7 @@ final class SdkSpan implements ReadWriteSpan {
|
|||
status,
|
||||
name,
|
||||
endEpochNanos,
|
||||
hasEnded);
|
||||
hasEnded == EndState.ENDED);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -242,7 +258,7 @@ final class SdkSpan implements ReadWriteSpan {
|
|||
@Override
|
||||
public boolean hasEnded() {
|
||||
synchronized (lock) {
|
||||
return hasEnded;
|
||||
return hasEnded == EndState.ENDED;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -288,7 +304,7 @@ final class SdkSpan implements ReadWriteSpan {
|
|||
@Override
|
||||
public long getLatencyNanos() {
|
||||
synchronized (lock) {
|
||||
return (hasEnded ? endEpochNanos : clock.now()) - startEpochNanos;
|
||||
return (hasEnded == EndState.NOT_ENDED ? clock.now() : endEpochNanos) - startEpochNanos;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -303,7 +319,7 @@ final class SdkSpan implements ReadWriteSpan {
|
|||
return this;
|
||||
}
|
||||
synchronized (lock) {
|
||||
if (hasEnded) {
|
||||
if (!isModifiableByCurrentThread()) {
|
||||
logger.log(Level.FINE, "Calling setAttribute() on an ended Span.");
|
||||
return this;
|
||||
}
|
||||
|
|
@ -318,6 +334,12 @@ final class SdkSpan implements ReadWriteSpan {
|
|||
return this;
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
private boolean isModifiableByCurrentThread() {
|
||||
return hasEnded == EndState.NOT_ENDED
|
||||
|| (hasEnded == EndState.ENDING && Thread.currentThread() == spanEndingThread);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadWriteSpan addEvent(String name) {
|
||||
if (name == null) {
|
||||
|
|
@ -380,7 +402,7 @@ final class SdkSpan implements ReadWriteSpan {
|
|||
|
||||
private void addTimedEvent(EventData timedEvent) {
|
||||
synchronized (lock) {
|
||||
if (hasEnded) {
|
||||
if (!isModifiableByCurrentThread()) {
|
||||
logger.log(Level.FINE, "Calling addEvent() on an ended Span.");
|
||||
return;
|
||||
}
|
||||
|
|
@ -400,7 +422,7 @@ final class SdkSpan implements ReadWriteSpan {
|
|||
return this;
|
||||
}
|
||||
synchronized (lock) {
|
||||
if (hasEnded) {
|
||||
if (!isModifiableByCurrentThread()) {
|
||||
logger.log(Level.FINE, "Calling setStatus() on an ended Span.");
|
||||
return this;
|
||||
} else if (this.status.getStatusCode() == StatusCode.OK) {
|
||||
|
|
@ -438,7 +460,7 @@ final class SdkSpan implements ReadWriteSpan {
|
|||
return this;
|
||||
}
|
||||
synchronized (lock) {
|
||||
if (hasEnded) {
|
||||
if (!isModifiableByCurrentThread()) {
|
||||
logger.log(Level.FINE, "Calling updateName() on an ended Span.");
|
||||
return this;
|
||||
}
|
||||
|
|
@ -463,7 +485,7 @@ final class SdkSpan implements ReadWriteSpan {
|
|||
spanLimits.getMaxNumberOfAttributesPerLink(),
|
||||
spanLimits.getMaxAttributeValueLength()));
|
||||
synchronized (lock) {
|
||||
if (hasEnded) {
|
||||
if (!isModifiableByCurrentThread()) {
|
||||
logger.log(Level.FINE, "Calling addLink() on an ended Span.");
|
||||
return this;
|
||||
}
|
||||
|
|
@ -493,12 +515,22 @@ final class SdkSpan implements ReadWriteSpan {
|
|||
|
||||
private void endInternal(long endEpochNanos) {
|
||||
synchronized (lock) {
|
||||
if (hasEnded) {
|
||||
logger.log(Level.FINE, "Calling end() on an ended Span.");
|
||||
if (hasEnded != EndState.NOT_ENDED) {
|
||||
logger.log(Level.FINE, "Calling end() on an ended or ending Span.");
|
||||
return;
|
||||
}
|
||||
this.endEpochNanos = endEpochNanos;
|
||||
hasEnded = true;
|
||||
spanEndingThread = Thread.currentThread();
|
||||
hasEnded = EndState.ENDING;
|
||||
}
|
||||
if (spanProcessor instanceof ExtendedSpanProcessor) {
|
||||
ExtendedSpanProcessor extendedSpanProcessor = (ExtendedSpanProcessor) spanProcessor;
|
||||
if (extendedSpanProcessor.isOnEndingRequired()) {
|
||||
extendedSpanProcessor.onEnding(this);
|
||||
}
|
||||
}
|
||||
synchronized (lock) {
|
||||
hasEnded = EndState.ENDED;
|
||||
}
|
||||
if (spanProcessor.isEndRequired()) {
|
||||
spanProcessor.onEnd(this);
|
||||
|
|
@ -508,7 +540,7 @@ final class SdkSpan implements ReadWriteSpan {
|
|||
@Override
|
||||
public boolean isRecording() {
|
||||
synchronized (lock) {
|
||||
return !hasEnded;
|
||||
return hasEnded != EndState.ENDED;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -533,7 +565,7 @@ final class SdkSpan implements ReadWriteSpan {
|
|||
|
||||
// if the span has ended, then the events are unmodifiable
|
||||
// so we can return them directly and save copying all the data.
|
||||
if (hasEnded) {
|
||||
if (hasEnded == EndState.ENDED) {
|
||||
return Collections.unmodifiableList(events);
|
||||
}
|
||||
|
||||
|
|
@ -547,7 +579,7 @@ final class SdkSpan implements ReadWriteSpan {
|
|||
}
|
||||
// if the span has ended, then the attributes are unmodifiable,
|
||||
// so we can return them directly and save copying all the data.
|
||||
if (hasEnded) {
|
||||
if (hasEnded == EndState.ENDED) {
|
||||
return attributes;
|
||||
}
|
||||
// otherwise, make a copy of the data into an immutable container.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.trace.internal;
|
||||
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
||||
import io.opentelemetry.sdk.trace.ReadableSpan;
|
||||
import io.opentelemetry.sdk.trace.SpanProcessor;
|
||||
|
||||
/**
|
||||
* Extended {@link SpanProcessor} with experimental APIs.
|
||||
*
|
||||
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
|
||||
* at any time.
|
||||
*/
|
||||
public interface ExtendedSpanProcessor extends SpanProcessor {
|
||||
|
||||
/**
|
||||
* Called when a {@link io.opentelemetry.api.trace.Span} is ended, but before {@link
|
||||
* SpanProcessor#onEnd(ReadableSpan)} is invoked with an immutable variant of this span. This
|
||||
* means that the span will still be mutable. Note that the span will only be modifiable
|
||||
* synchronously from this callback, concurrent modifications from other threads will be
|
||||
* prevented. Only called if {@link Span#isRecording()} returns true.
|
||||
*
|
||||
* <p>This method is called synchronously on the execution thread, should not throw or block the
|
||||
* execution thread.
|
||||
*
|
||||
* @param span the {@code Span} that is just about to be ended.
|
||||
*/
|
||||
void onEnding(ReadWriteSpan span);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this {@link SpanProcessor} requires onEnding events.
|
||||
*
|
||||
* @return {@code true} if this {@link SpanProcessor} requires onEnding events.
|
||||
*/
|
||||
boolean isOnEndingRequired();
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import static org.mockito.Mockito.when;
|
|||
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||
import io.opentelemetry.sdk.trace.internal.ExtendedSpanProcessor;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
|
@ -27,18 +28,20 @@ import org.mockito.quality.Strictness;
|
|||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class MultiSpanProcessorTest {
|
||||
@Mock private SpanProcessor spanProcessor1;
|
||||
@Mock private SpanProcessor spanProcessor2;
|
||||
@Mock private ExtendedSpanProcessor spanProcessor1;
|
||||
@Mock private ExtendedSpanProcessor spanProcessor2;
|
||||
@Mock private ReadableSpan readableSpan;
|
||||
@Mock private ReadWriteSpan readWriteSpan;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
when(spanProcessor1.isStartRequired()).thenReturn(true);
|
||||
when(spanProcessor1.isOnEndingRequired()).thenReturn(true);
|
||||
when(spanProcessor1.isEndRequired()).thenReturn(true);
|
||||
when(spanProcessor1.forceFlush()).thenReturn(CompletableResultCode.ofSuccess());
|
||||
when(spanProcessor1.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
|
||||
when(spanProcessor2.isStartRequired()).thenReturn(true);
|
||||
when(spanProcessor2.isOnEndingRequired()).thenReturn(true);
|
||||
when(spanProcessor2.isEndRequired()).thenReturn(true);
|
||||
when(spanProcessor2.forceFlush()).thenReturn(CompletableResultCode.ofSuccess());
|
||||
when(spanProcessor2.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
|
||||
|
|
@ -61,12 +64,17 @@ class MultiSpanProcessorTest {
|
|||
|
||||
@Test
|
||||
void twoSpanProcessor() {
|
||||
SpanProcessor multiSpanProcessor =
|
||||
SpanProcessor.composite(Arrays.asList(spanProcessor1, spanProcessor2));
|
||||
ExtendedSpanProcessor multiSpanProcessor =
|
||||
(ExtendedSpanProcessor)
|
||||
SpanProcessor.composite(Arrays.asList(spanProcessor1, spanProcessor2));
|
||||
multiSpanProcessor.onStart(Context.root(), readWriteSpan);
|
||||
verify(spanProcessor1).onStart(same(Context.root()), same(readWriteSpan));
|
||||
verify(spanProcessor2).onStart(same(Context.root()), same(readWriteSpan));
|
||||
|
||||
multiSpanProcessor.onEnding(readWriteSpan);
|
||||
verify(spanProcessor1).onEnding(same(readWriteSpan));
|
||||
verify(spanProcessor2).onEnding(same(readWriteSpan));
|
||||
|
||||
multiSpanProcessor.onEnd(readableSpan);
|
||||
verify(spanProcessor1).onEnd(same(readableSpan));
|
||||
verify(spanProcessor2).onEnd(same(readableSpan));
|
||||
|
|
@ -83,9 +91,11 @@ class MultiSpanProcessorTest {
|
|||
@Test
|
||||
void twoSpanProcessor_DifferentRequirements() {
|
||||
when(spanProcessor1.isEndRequired()).thenReturn(false);
|
||||
when(spanProcessor2.isOnEndingRequired()).thenReturn(false);
|
||||
when(spanProcessor2.isStartRequired()).thenReturn(false);
|
||||
SpanProcessor multiSpanProcessor =
|
||||
SpanProcessor.composite(Arrays.asList(spanProcessor1, spanProcessor2));
|
||||
ExtendedSpanProcessor multiSpanProcessor =
|
||||
(ExtendedSpanProcessor)
|
||||
SpanProcessor.composite(Arrays.asList(spanProcessor1, spanProcessor2));
|
||||
|
||||
assertThat(multiSpanProcessor.isStartRequired()).isTrue();
|
||||
assertThat(multiSpanProcessor.isEndRequired()).isTrue();
|
||||
|
|
@ -94,6 +104,10 @@ class MultiSpanProcessorTest {
|
|||
verify(spanProcessor1).onStart(same(Context.root()), same(readWriteSpan));
|
||||
verify(spanProcessor2, times(0)).onStart(any(Context.class), any(ReadWriteSpan.class));
|
||||
|
||||
multiSpanProcessor.onEnding(readWriteSpan);
|
||||
verify(spanProcessor1).onEnding(same(readWriteSpan));
|
||||
verify(spanProcessor2, times(0)).onEnding(any(ReadWriteSpan.class));
|
||||
|
||||
multiSpanProcessor.onEnd(readableSpan);
|
||||
verify(spanProcessor1, times(0)).onEnd(any(ReadableSpan.class));
|
||||
verify(spanProcessor2).onEnd(same(readableSpan));
|
||||
|
|
@ -117,6 +131,7 @@ class MultiSpanProcessorTest {
|
|||
.hasToString(
|
||||
"MultiSpanProcessor{"
|
||||
+ "spanProcessorsStart=[spanProcessor1, spanProcessor1], "
|
||||
+ "spanProcessorsEnding=[spanProcessor1, spanProcessor1], "
|
||||
+ "spanProcessorsEnd=[spanProcessor1, spanProcessor1], "
|
||||
+ "spanProcessorsAll=[spanProcessor1, spanProcessor1]}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
|
@ -42,6 +44,7 @@ import io.opentelemetry.sdk.trace.data.EventData;
|
|||
import io.opentelemetry.sdk.trace.data.LinkData;
|
||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||
import io.opentelemetry.sdk.trace.data.StatusData;
|
||||
import io.opentelemetry.sdk.trace.internal.ExtendedSpanProcessor;
|
||||
import io.opentelemetry.sdk.trace.internal.data.ExceptionEventData;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
|
@ -58,6 +61,8 @@ import java.util.concurrent.ExecutorService;
|
|||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.stream.IntStream;
|
||||
import javax.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
|
@ -90,7 +95,7 @@ class SdkSpanTest {
|
|||
private final Map<AttributeKey, Object> attributes = new HashMap<>();
|
||||
private Attributes expectedAttributes;
|
||||
private final LinkData link = LinkData.create(spanContext);
|
||||
@Mock private SpanProcessor spanProcessor;
|
||||
@Mock private ExtendedSpanProcessor spanProcessor;
|
||||
|
||||
private TestClock testClock;
|
||||
|
||||
|
|
@ -107,6 +112,7 @@ class SdkSpanTest {
|
|||
expectedAttributes = builder.build();
|
||||
testClock = TestClock.create(Instant.ofEpochSecond(0, START_EPOCH_NANOS));
|
||||
when(spanProcessor.isStartRequired()).thenReturn(true);
|
||||
when(spanProcessor.isOnEndingRequired()).thenReturn(true);
|
||||
when(spanProcessor.isEndRequired()).thenReturn(true);
|
||||
}
|
||||
|
||||
|
|
@ -140,6 +146,92 @@ class SdkSpanTest {
|
|||
assertThat(span.hasEnded()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void onEnding_spanStillMutable() {
|
||||
SdkSpan span = createTestSpan(SpanKind.INTERNAL);
|
||||
|
||||
AttributeKey<String> dummyAttrib = AttributeKey.stringKey("processor_foo");
|
||||
|
||||
AtomicBoolean endedStateInProcessor = new AtomicBoolean();
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
ReadWriteSpan sp = invocation.getArgument(0, ReadWriteSpan.class);
|
||||
assertThat(sp.hasEnded()).isFalse();
|
||||
sp.end(); // should have no effect, nested end should be detected
|
||||
endedStateInProcessor.set(sp.hasEnded());
|
||||
sp.setAttribute(dummyAttrib, "bar");
|
||||
return null;
|
||||
})
|
||||
.when(spanProcessor)
|
||||
.onEnding(any());
|
||||
|
||||
span.end();
|
||||
verify(spanProcessor).onEnding(same(span));
|
||||
assertThat(span.hasEnded()).isTrue();
|
||||
assertThat(endedStateInProcessor.get()).isFalse();
|
||||
assertThat(span.getAttribute(dummyAttrib)).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void onEnding_concurrentModificationsPrevented() {
|
||||
SdkSpan span = createTestSpan(SpanKind.INTERNAL);
|
||||
|
||||
AttributeKey<String> syncAttrib = AttributeKey.stringKey("sync_foo");
|
||||
AttributeKey<String> concurrentAttrib = AttributeKey.stringKey("concurrent_foo");
|
||||
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
ReadWriteSpan sp = invocation.getArgument(0, ReadWriteSpan.class);
|
||||
|
||||
Thread concurrent =
|
||||
new Thread(
|
||||
() -> {
|
||||
sp.setAttribute(concurrentAttrib, "concurrent_bar");
|
||||
});
|
||||
concurrent.start();
|
||||
concurrent.join();
|
||||
|
||||
sp.setAttribute(syncAttrib, "sync_bar");
|
||||
|
||||
return null;
|
||||
})
|
||||
.when(spanProcessor)
|
||||
.onEnding(any());
|
||||
|
||||
span.end();
|
||||
verify(spanProcessor).onEnding(same(span));
|
||||
assertThat(span.getAttribute(concurrentAttrib)).isNull();
|
||||
assertThat(span.getAttribute(syncAttrib)).isEqualTo("sync_bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void onEnding_latencyPinned() {
|
||||
SdkSpan span = createTestSpan(SpanKind.INTERNAL);
|
||||
|
||||
AtomicLong spanLatencyInProcessor = new AtomicLong();
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
ReadWriteSpan sp = invocation.getArgument(0, ReadWriteSpan.class);
|
||||
|
||||
testClock.advance(Duration.ofSeconds(100));
|
||||
spanLatencyInProcessor.set(sp.getLatencyNanos());
|
||||
return null;
|
||||
})
|
||||
.when(spanProcessor)
|
||||
.onEnding(any());
|
||||
|
||||
testClock.advance(Duration.ofSeconds(1));
|
||||
long expectedDuration = testClock.now() - START_EPOCH_NANOS;
|
||||
|
||||
assertThat(span.getLatencyNanos()).isEqualTo(expectedDuration);
|
||||
|
||||
span.end();
|
||||
verify(spanProcessor).onEnding(same(span));
|
||||
assertThat(span.hasEnded()).isTrue();
|
||||
assertThat(span.getLatencyNanos()).isEqualTo(expectedDuration);
|
||||
assertThat(spanLatencyInProcessor.get()).isEqualTo(expectedDuration);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toSpanData_ActiveSpan() {
|
||||
SdkSpan span = createTestSpan(SpanKind.INTERNAL);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.trace.internal;
|
||||
|
||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
|
||||
|
||||
import io.opentelemetry.api.common.AttributeKey;
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.api.trace.Tracer;
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
|
||||
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
||||
import io.opentelemetry.sdk.trace.ReadableSpan;
|
||||
import io.opentelemetry.sdk.trace.SdkTracerProvider;
|
||||
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Demonstrating usage of {@link ExtendedSpanProcessor}. */
|
||||
class ExtendedSpanProcessorUsageTest {
|
||||
|
||||
private static final AttributeKey<String> FOO_KEY = AttributeKey.stringKey("foo");
|
||||
private static final AttributeKey<String> BAR_KEY = AttributeKey.stringKey("bar");
|
||||
|
||||
private static class CopyFooToBarProcessor implements ExtendedSpanProcessor {
|
||||
|
||||
@Override
|
||||
public void onStart(Context parentContext, ReadWriteSpan span) {}
|
||||
|
||||
@Override
|
||||
public boolean isStartRequired() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnd(ReadableSpan span) {}
|
||||
|
||||
@Override
|
||||
public boolean isEndRequired() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnding(ReadWriteSpan span) {
|
||||
String val = span.getAttribute(FOO_KEY);
|
||||
span.setAttribute(BAR_KEY, val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnEndingRequired() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void extendedSpanProcessorUsage() {
|
||||
InMemorySpanExporter exporter = InMemorySpanExporter.create();
|
||||
|
||||
try (SdkTracerProvider tracerProvider =
|
||||
SdkTracerProvider.builder()
|
||||
.addSpanProcessor(SimpleSpanProcessor.create(exporter))
|
||||
.addSpanProcessor(new CopyFooToBarProcessor())
|
||||
.build()) {
|
||||
|
||||
Tracer tracer = tracerProvider.get("dummy-tracer");
|
||||
Span span = tracer.spanBuilder("my-span").startSpan();
|
||||
|
||||
span.setAttribute(FOO_KEY, "Hello!");
|
||||
|
||||
span.end();
|
||||
|
||||
assertThat(exporter.getFinishedSpanItems())
|
||||
.hasSize(1)
|
||||
.first()
|
||||
.satisfies(
|
||||
spanData -> {
|
||||
assertThat(spanData.getAttributes())
|
||||
.containsEntry(FOO_KEY, "Hello!")
|
||||
.containsEntry(BAR_KEY, "Hello!");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue