/** * @return The JSON representation of this span. See {@link #toJSON()}. */ @Override public String toString() { return toJSON(); }
/** * Sets the span variables on the MDC context. */ protected static void configureMDC(Span span) { MDC.put(TRACE_ID_MDC_KEY, span.getTraceId()); MDC.put(SPAN_JSON_MDC_KEY, span.toJSON()); }
/** * Uses {@link #spanLoggingRepresentation} to decide how to serialize the given span, and then returns the result of the serialization. */ protected String serializeSpanToDesiredStringRepresentation(Span span) { switch(spanLoggingRepresentation) { case JSON: return span.toJSON(); case KEY_VALUE: return span.toKeyValueString(); default: throw new IllegalStateException("Unknown span logging representation type: " + spanLoggingRepresentation); } }
@Test public void toJson_should_use_cached_json() { // given Span validSpan = Span.generateRootSpanForNewTrace(spanName, spanPurpose).build(); String uuidString = UUID.randomUUID().toString(); Whitebox.setInternalState(validSpan, "cachedJsonRepresentation", uuidString); // when String toJsonResult = validSpan.toJSON(); // then assertThat(toJsonResult).isEqualTo(uuidString); }
@Test public void toString_delegates_to_toJSON() { // given: span with all values filled in Span span = createFilledOutSpan(true); // when: toString is called on that span String toStringVal = span.toString(); // then: it has the same value as toJSON() assertThat(toStringVal).isEqualTo(span.toJSON()); }
@Test public void complete_should_reset_cached_json() throws IOException { // given Span validSpan = Span.generateRootSpanForNewTrace(spanName, spanPurpose).build(); String uuidString = UUID.randomUUID().toString(); Whitebox.setInternalState(validSpan, "cachedJsonRepresentation", uuidString); // when String beforeCompleteJson = validSpan.toJSON(); completeSpan(validSpan); // then String afterCompleteJson = validSpan.toJSON(); assertThat(afterCompleteJson).isNotEqualTo(beforeCompleteJson); assertThat(afterCompleteJson).isNotEqualTo(uuidString); Map<String, Object> spanValuesFromJackson = objectMapper.readValue(afterCompleteJson, new TypeReference<Map<String, Object>>() { }); verifySpanEqualsDeserializedValues(validSpan, spanValuesFromJackson); }
@Test public void unlinkTracingAndMdcFromCurrentThread_should_reset_tracing_and_mdc_to_originalThreadInfo_if_state_is_null() { // given doReturn(null).when(stateAttributeMock).get(); MDC.put("foo", "bar"); Tracer.getInstance().startRequestWithRootSpan("blahtrace"); assertThat(MDC.getCopyOfContextMap().isEmpty(), is(false)); assertThat(Tracer.getInstance().getCurrentSpan(), notNullValue()); Deque<Span> origTraceStack = new LinkedList<>(); Span origSpan = Span.newBuilder(UUID.randomUUID().toString(), LOCAL_ONLY).withTraceId(UUID.randomUUID().toString()).build(); origTraceStack.add(origSpan); Map<String, String> origMdcInfo = new HashMap<>(); origMdcInfo.put(UUID.randomUUID().toString(), UUID.randomUUID().toString()); origMdcInfo.put(Tracer.TRACE_ID_MDC_KEY, origSpan.getTraceId()); origMdcInfo.put(Tracer.SPAN_JSON_MDC_KEY, origSpan.toJSON()); Pair<Deque<Span>, Map<String, String>> origThreadInfo = Pair.of(origTraceStack, origMdcInfo); // when handler.unlinkTracingAndMdcFromCurrentThread(ctxMock, origThreadInfo); // then assertThat(MDC.getCopyOfContextMap(), is(origMdcInfo)); assertThat(Tracer.getInstance().getCurrentSpanStackCopy(), is(origTraceStack)); }
@Test public void registerWithThread_should_do_nothing_if_copy_of_same_stack_is_passed_in() { // given Tracer tracer = Tracer.getInstance(); 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> spanStack = getSpanStackThreadLocal().get(); tracer.registerWithThread(new LinkedList<>(spanStack)); // then assertThat(getSpanStackThreadLocal().get()).isEqualTo(spanStack); assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isEqualTo(subspan.getTraceId()); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isEqualTo(subspan.toJSON()); }
@Test public void registerWithThread_should_do_nothing_if_same_stack_is_passed_in() { // given Tracer tracer = Tracer.getInstance(); 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> spanStack = getSpanStackThreadLocal().get(); tracer.registerWithThread(spanStack); // then assertThat(getSpanStackThreadLocal().get()).isEqualTo(spanStack); assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isEqualTo(subspan.getTraceId()); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isEqualTo(subspan.toJSON()); }
@Test public void unregisterFromThread_should_work_as_advertised() { // 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()); assertThat(getSpanStackSize()).isEqualTo(2); // when Deque<Span> unregisteredStack = tracer.unregisterFromThread(); // then assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isNull(); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isNull(); assertThat(getSpanStackSize()).isEqualTo(0); assertThat(unregisteredStack).hasSize(2); assertThat(unregisteredStack.pop()).isEqualTo(subspan); assertThat(unregisteredStack.pop()).isEqualTo(parentSpan); }
@Test public void fromJSON_delegates_to_span_parser() { // given Span span = Span.newBuilder("foo", SpanPurpose.CLIENT) .withTag("blahtag", UUID.randomUUID().toString()) .build(); String json = span.toJSON(); // when Span result = span.fromJSON(json); // then verifySpanDeepEquals(result, span, true); }
@Test public void configureMDC_should_set_span_values_on_MDC() throws Exception { // given Span span = Span.newBuilder("test-span", SpanPurpose.LOCAL_ONLY).withParentSpanId("3").build(); String expected = span.toJSON(); // when Tracer.configureMDC(span); // then assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isEqualTo(expected); }
@Test public void registerWithThread_should_override_existing_stuff() { // given Tracer tracer = Tracer.getInstance(); Span existingSpan = tracer.startRequestWithRootSpan("old"); Deque<Span> newSpanStack = new LinkedList<>(); Span parentSpan = Span.newBuilder("foo", SpanPurpose.LOCAL_ONLY).build(); Span subspan = Span.newBuilder("bar", SpanPurpose.LOCAL_ONLY).build(); newSpanStack.push(parentSpan); newSpanStack.push(subspan); assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isEqualTo(existingSpan.getTraceId()); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isEqualTo(existingSpan.toJSON()); // when tracer.registerWithThread(newSpanStack); // then assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isEqualTo(subspan.getTraceId()); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isEqualTo(subspan.toJSON()); Deque<Span> spanStack = getSpanStackThreadLocal().get(); assertThat(spanStack).isEqualTo(newSpanStack); }
@Test public void linkTracingAndMdcToCurrentThread_should_set_tracing_and_mdc_to_state_values_if_available() { // given Map<String, String> stateMdcInfo = new HashMap<>(); stateMdcInfo.put("foo", "bar"); Deque<Span> stateTraceStack = new LinkedList<>(); Span span = Span.generateRootSpanForNewTrace("fooSpanName", LOCAL_ONLY).withTraceId("fooTraceId").build(); stateTraceStack.add(span); state.setLoggerMdcContextMap(stateMdcInfo); state.setDistributedTraceStack(stateTraceStack); assertThat(MDC.getCopyOfContextMap().isEmpty(), is(true)); assertThat(Tracer.getInstance().getCurrentSpan(), nullValue()); // when handler.linkTracingAndMdcToCurrentThread(ctxMock); // then // Tracer adds some stuff to the MDC stateMdcInfo.put(Tracer.TRACE_ID_MDC_KEY, span.getTraceId()); stateMdcInfo.put(Tracer.SPAN_JSON_MDC_KEY, span.toJSON()); assertThat(MDC.getCopyOfContextMap(), is(stateMdcInfo)); assertThat(Tracer.getInstance().getCurrentSpanStackCopy(), is(stateTraceStack)); }
@Test public void registerWithThread_should_reset_everything_if_passed_empty_instance() { // given Tracer tracer = Tracer.getInstance(); 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> emptyStack = new LinkedList<>(); tracer.registerWithThread(emptyStack); // then assertThat(getSpanStackThreadLocal().get()).isEqualTo(emptyStack); assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isNull(); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isNull(); }
@Test public void registerWithThread_should_reset_everything_if_passed_null() { // given Tracer tracer = Tracer.getInstance(); 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 tracer.registerWithThread(null); // then assertThat(getSpanStackThreadLocal().get()).isNull(); assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isNull(); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isNull(); }
@Test public void registerWithThread_should_work_as_advertised_if_existing_stack_is_empty() { // given getSpanStackThreadLocal().set(new LinkedList<Span>()); Tracer tracer = Tracer.getInstance(); Deque<Span> newSpanStack = new LinkedList<>(); Span parentSpan = Span.newBuilder("foo", SpanPurpose.LOCAL_ONLY).build(); Span subspan = Span.newBuilder("bar", SpanPurpose.LOCAL_ONLY).build(); newSpanStack.push(parentSpan); newSpanStack.push(subspan); assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isNull(); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isNull(); // when tracer.registerWithThread(newSpanStack); // then // our stack was registered, so subspan should be current assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isEqualTo(subspan.getTraceId()); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isEqualTo(subspan.toJSON()); assertThat(tracer.getCurrentSpan()).isEqualTo(subspan); // a *copy* of the stack we passed in should have been registered, and modifying the original stack should not affect Tracer's stack Deque<Span> spanStack = getSpanStackThreadLocal().get(); assertThat(Tracer.getInstance().containsSameSpansInSameOrder(spanStack, newSpanStack)).isTrue(); assertThat(spanStack).isNotSameAs(newSpanStack); newSpanStack.push(subspan.generateChildSpan("subsub", SpanPurpose.LOCAL_ONLY)); assertThat(newSpanStack).hasSize(3); assertThat(spanStack).hasSize(2); }
@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 registerWithThread_should_work_as_advertised() { // given Tracer tracer = Tracer.getInstance(); Deque<Span> newSpanStack = new LinkedList<>(); Span parentSpan = Span.newBuilder("foo", SpanPurpose.LOCAL_ONLY).build(); Span subspan = Span.newBuilder("bar", SpanPurpose.LOCAL_ONLY).build(); newSpanStack.push(parentSpan); newSpanStack.push(subspan); assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isNull(); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isNull(); // when tracer.registerWithThread(newSpanStack); // then // our stack was registered, so subspan should be current assertThat(MDC.get(Tracer.TRACE_ID_MDC_KEY)).isEqualTo(subspan.getTraceId()); assertThat(MDC.get(Tracer.SPAN_JSON_MDC_KEY)).isEqualTo(subspan.toJSON()); assertThat(tracer.getCurrentSpan()).isEqualTo(subspan); // a *copy* of the stack we passed in should have been registered, and modifying the original stack should not affect Tracer's stack Deque<Span> spanStack = getSpanStackThreadLocal().get(); assertThat(Tracer.getInstance().containsSameSpansInSameOrder(spanStack, newSpanStack)).isTrue(); assertThat(spanStack).isNotSameAs(newSpanStack); newSpanStack.push(subspan.generateChildSpan("subsub", SpanPurpose.LOCAL_ONLY)); assertThat(newSpanStack).hasSize(3); assertThat(spanStack).hasSize(2); }
@DataProvider(value = { "JSON", "KEY_VALUE" }, splitBy = "\\|") @Test public void verify_span_serialization_methods(Tracer.SpanLoggingRepresentation serializationOption) { // given Span span = Span.generateRootSpanForNewTrace(UUID.randomUUID().toString(), SpanPurpose.LOCAL_ONLY).build(); String expectedOutput; switch(serializationOption) { case JSON: expectedOutput = span.toJSON(); break; case KEY_VALUE: expectedOutput = span.toKeyValueString(); break; default: throw new IllegalArgumentException("Unhandled option: " + serializationOption); } Tracer.getInstance().setSpanLoggingRepresentation(serializationOption); // then assertThat(Tracer.getInstance().getSpanLoggingRepresentation()).isEqualTo(serializationOption); // and when String serializedString = Tracer.getInstance().serializeSpanToDesiredStringRepresentation(span); // then assertThat(serializedString).isEqualTo(expectedOutput); }