/** * Helper method for creating a `CompletableFuture` that has the `Supplier` wrapped around a SubSpan. * <p> * You would prefer this method over the above when your `Supplier` has logic that makes an outbound/downstream call * and you do not want the use of a `CircuitBreaker`. * <p> * You would prefer this method over {@link #supplyAsync(String, Supplier, CircuitBreaker, Executor, ChannelHandlerContext)} * when your `Supplier` has logic that you would like wrapped with distributed tracing logs and not use a {@link CircuitBreaker} * <p> * An example would be using a client SDK that makes blocking HTTP calls. * <p> * The SubSpan purpose will be set to `CLIENT` as this is the typical use case when utilizing these helpers. * <p> * <pre> * AsyncNettyHelper.supplyAsync("someWorkToBeDone", () -> { * //do some work in a background thread * return VOID; * }, executor, ctx); * </pre> */ public static <U> CompletableFuture<U> supplyAsync(String subSpanName, Supplier<U> f, Executor executor, ChannelHandlerContext ctx) { return CompletableFuture.supplyAsync(supplierWithTracingAndMdc(() -> { try { Tracer.getInstance().startSubSpan(subSpanName, Span.SpanPurpose.CLIENT); return f.get(); } finally { Tracer.getInstance().completeSubSpan(); } }, ctx), executor); }
/** * Helper method for creating a `CompletableFuture` that has the `Supplier` wrapped around a SubSpan. * <p> * You would prefer this method over the above when your `Supplier` has logic that makes an outbound/downstream call * and you do not want the use of a `CircuitBreaker`. * <p> * You would prefer this method over {@link #supplyAsync(String, Supplier, CircuitBreaker, Executor, ChannelHandlerContext)} * when your `Supplier` has logic that you would like wrapped with distributed tracing logs and not use a {@link CircuitBreaker} * <p> * An example would be using a client SDK that makes blocking HTTP calls. * <p> * The SubSpan purpose will be set to `CLIENT` as this is the typical use case when utilizing these helpers. * <p> * <pre> * AsyncNettyHelper.supplyAsync("someWorkToBeDone", () -> { * //do some work in a background thread * return VOID; * }, executor, ctx); * </pre> */ public static <U> CompletableFuture<U> supplyAsync(String subSpanName, Supplier<U> f, Executor executor, ChannelHandlerContext ctx) { return CompletableFuture.supplyAsync(supplierWithTracingAndMdc(() -> { try { Tracer.getInstance().startSubSpan(subSpanName, Span.SpanPurpose.CLIENT); return f.get(); } finally { Tracer.getInstance().completeSubSpan(); } }, ctx), executor); }
@Test public void completeSubSpan_should_do_nothing_if_there_is_only_one_span_on_the_stack() { // given: a single span on the stack Tracer.getInstance().startRequestWithRootSpan("somespan"); assertThat(Tracer.getInstance().getCurrentSpan()).isNotNull(); assertThat(getSpanStackSize()).isEqualTo(1); Span span = Tracer.getInstance().getCurrentSpan(); assertThat(span).isNotNull(); assertThat(span.getSpanName()).isEqualTo("somespan"); // when: completeSubSpan() is called Tracer.getInstance().completeSubSpan(); // then: nothing should be done because the stack only has one thing on it and completeSubSpan() requires at least two spans assertThat(span.isCompleted()).isFalse(); assertThat(Tracer.getInstance().getCurrentSpan()).isSameAs(span); assertThat(getSpanStackSize()).isEqualTo(1); }
@Test public void completeSubSpan_should_do_nothing_if_the_span_stack_is_null() { // given: a null span stack assertThat(Tracer.getInstance().getCurrentSpan()).isNull(); assertThat(getSpanStackSize()).isEqualTo(0); assertThat(getSpanStackFromTracer()).isNull(); // when: completeSubSpan() is called Tracer.getInstance().completeSubSpan(); // then: nothing should be done because the stack is null assertThat(Tracer.getInstance().getCurrentSpan()).isNull(); assertThat(getSpanStackSize()).isEqualTo(0); assertThat(getSpanStackFromTracer()).isNull(); }
@Test public void spanLifecycleListener_spanCompleted_is_called_when_subspan_is_completed() { // given SpanLifecycleListener listener1 = mock(SpanLifecycleListener.class); SpanLifecycleListener listener2 = mock(SpanLifecycleListener.class); Tracer tracer = Tracer.getInstance(); tracer.addSpanLifecycleListener(listener1); tracer.addSpanLifecycleListener(listener2); tracer.startRequestWithRootSpan("newspan"); Span subspan = tracer.startSubSpan("subspan", SpanPurpose.LOCAL_ONLY); verify(listener1).spanStarted(subspan); verify(listener1, times(0)).spanCompleted(subspan); verify(listener2).spanStarted(subspan); verify(listener2, times(0)).spanCompleted(subspan); // when tracer.completeSubSpan(); // then verify(listener1).spanCompleted(subspan); verify(listener2).spanCompleted(subspan); }
case MANAGED_CURRENT_SUB_SPAN: completeSubSpan(); break; case MANAGED_NON_CURRENT_ROOT_SPAN: //intentional fall-through
@Test public void getCurrentSpanStackCopy_returns_copy_of_stack_not_original() { // given Tracer tracer = Tracer.getInstance(); Span parentSpan = tracer.startRequestWithRootSpan("foo"); Span subspan = tracer.startSubSpan("bar", SpanPurpose.LOCAL_ONLY); assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isEqualTo(subspan.getTraceId()); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isEqualTo(subspan.toJSON()); // when Deque<Span> stack = tracer.getCurrentSpanStackCopy(); // Verify that the returned stack contains both spans assertThat(stack).hasSize(2); assertThat(stack.peek()).isEqualTo(subspan); assertThat(stack.peekLast()).isEqualTo(parentSpan); // Clear the returned stack stack.clear(); assertThat(stack.isEmpty()).isTrue(); // then // Now verify that tracer still points to a stack that contains both spans. This proves that getCurrentSpanStackCopy() returned a copy, not the original assertThat(tracer.getCurrentSpan()).isEqualTo(subspan); assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isEqualTo(subspan.getTraceId()); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isEqualTo(subspan.toJSON()); tracer.completeSubSpan(); assertThat(tracer.getCurrentSpan()).isEqualTo(parentSpan); assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isEqualTo(parentSpan.getTraceId()); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isEqualTo(parentSpan.toJSON()); }
@Test public void spanLifecycleListener_spanCompleted_is_not_called_if_subspan_was_already_completed() { // given SpanLifecycleListener listener1 = mock(SpanLifecycleListener.class); SpanLifecycleListener listener2 = mock(SpanLifecycleListener.class); Tracer tracer = Tracer.getInstance(); tracer.addSpanLifecycleListener(listener1); tracer.addSpanLifecycleListener(listener2); tracer.startRequestWithRootSpan("newspan"); Span subspan = tracer.startSubSpan("subspan", SpanPurpose.LOCAL_ONLY); subspan.complete(); verify(listener1).spanStarted(subspan); verify(listener1, times(0)).spanCompleted(subspan); verify(listener2).spanStarted(subspan); verify(listener2, times(0)).spanCompleted(subspan); // when tracer.completeSubSpan(); // then verify(listener1, never()).spanCompleted(subspan); verify(listener2, never()).spanCompleted(subspan); }
tracer.completeSubSpan();
Tracer.getInstance().completeRequestSpan(); else Tracer.getInstance().completeSubSpan();
Tracer.getInstance().completeRequestSpan(); else Tracer.getInstance().completeSubSpan();
@Test public void completeSubSpan_should_complete_the_sub_span() { // given: an already-started span AND a subspan Tracer.getInstance().startRequestWithRootSpan("parentspan"); Span parentSpan = Tracer.getInstance().getCurrentSpan(); assertThat(parentSpan.getSpanName()).isEqualTo("parentspan"); Tracer.getInstance().startSubSpan("subspan", SpanPurpose.LOCAL_ONLY); Span subspan = Tracer.getInstance().getCurrentSpan(); assertThat(subspan.getSpanName()).isEqualTo("subspan"); assertThat(getSpanStackSize()).isEqualTo(2); assertThat(parentSpan.isCompleted()).isFalse(); assertThat(subspan.isCompleted()).isFalse(); // when: completeSubSpan() is called long beforeNanoTime = System.nanoTime(); Tracer.getInstance().completeSubSpan(); long afterNanoTime = System.nanoTime(); // then: only the subspan should be completed, the stack decremented by 1, the current span set to the parent, and the MDC configured to point to the parent assertThat(parentSpan.isCompleted()).isFalse(); assertThat(subspan.isCompleted()).isTrue(); verifyDurationBetweenLowerAndUpperBounds(subspan, beforeNanoTime, afterNanoTime); assertThat(Tracer.getInstance().getCurrentSpan()).isNotNull(); assertThat(Tracer.getInstance().getCurrentSpan()).isSameAs(parentSpan); assertThat(getSpanStackSize()).isEqualTo(1); assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isEqualTo(parentSpan.getTraceId()); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isEqualTo(parentSpan.toJSON()); }
@Test public void handleSpanCloseMethod_does_nothing_if_span_is_already_completed() { // given Span rootSpan = Tracer.getInstance().startRequestWithRootSpan("root"); Span subspan = Tracer.getInstance().startSubSpan("subspan", SpanPurpose.LOCAL_ONLY); Tracer.getInstance().completeSubSpan(); assertThat(subspan.isCompleted()).isTrue(); assertThat(rootSpan.isCompleted()).isFalse(); assertThat(Tracer.getInstance().getCurrentSpan()).isSameAs(rootSpan); assertThat(Tracer.getInstance().getCurrentSpanStackCopy()).isEqualTo(singletonList(rootSpan)); // when Tracer.getInstance().handleSpanCloseMethod(subspan); // then assertThat(rootSpan.isCompleted()).isFalse(); assertThat(Tracer.getInstance().getCurrentSpan()).isSameAs(rootSpan); assertThat(Tracer.getInstance().getCurrentSpanStackCopy()).isEqualTo(singletonList(rootSpan)); }
@Test public void close_does_nothing_if_span_is_already_completed() { // given Span rootSpan = Tracer.getInstance().startRequestWithRootSpan("root"); Span subspan = Tracer.getInstance().startSubSpan("subspan", SpanPurpose.LOCAL_ONLY); Tracer.getInstance().completeSubSpan(); assertThat(subspan.isCompleted()).isTrue(); assertThat(rootSpan.isCompleted()).isFalse(); assertThat(Tracer.getInstance().getCurrentSpan()).isSameAs(rootSpan); assertThat(Tracer.getInstance().getCurrentSpanStackCopy()).isEqualTo(singletonList(rootSpan)); // when subspan.close(); // then assertThat(rootSpan.isCompleted()).isFalse(); assertThat(Tracer.getInstance().getCurrentSpan()).isSameAs(rootSpan); assertThat(Tracer.getInstance().getCurrentSpanStackCopy()).isEqualTo(singletonList(rootSpan)); }
Tracer.getInstance().completeRequestSpan(); else Tracer.getInstance().completeSubSpan(); }, distributedSpanStackToUse, mdcContextToUse
Tracer.getInstance().completeRequestSpan(); else Tracer.getInstance().completeSubSpan(); }, distributedSpanStackToUse, mdcContextToUse
Tracer.getInstance().completeSubSpan(); Tracer.getInstance().completeSubSpan(); Tracer.getInstance().completeRequestSpan();
Tracer.getInstance().completeSubSpan(); Tracer.getInstance().completeSubSpan(); Tracer.getInstance().completeRequestSpan();
tracer.completeSubSpan(); assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isEqualTo(reqAParentSpan.getTraceId()); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isEqualTo(reqAParentSpan.toJSON()); tracer.completeSubSpan(); assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isEqualTo(reqBParentSpan.getTraceId()); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isEqualTo(reqBParentSpan.toJSON());