@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)); }
Span origSpan = Span.newBuilder(UUID.randomUUID().toString(), LOCAL_ONLY).withTraceId(UUID.randomUUID().toString()).build(); origTraceStack.add(origSpan); Map<String, String> origMdcInfo = new HashMap<>();
@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)); }
.withTraceId(traceId) .withSpanId(spanId) .withParentSpanId(parentSpanId)
.withTraceId(traceId) .withParentSpanId(getSpanIdFromRequest(request, TraceHeaders.PARENT_SPAN_ID, false)) .withSpanId(getSpanIdFromRequest(request, TraceHeaders.SPAN_ID, true))
.withTraceId(traceId) .withParentSpanId(parentSpanId) .withSampleable(sampleable)
.withTraceId(badTraceId) .withSpanId(badSpanId) .withParentSpanId(badParentSpanId)
@Test public void convertSpanToJSON_and_fromJSON_should_escape_and_unescape_expected_non_tag_or_annotation_values() { // The TAGS_AND_ANNOTATIONS_WITH_SPECIAL_CHARS case already verified tags and annotations. Now we need to // verify that non-tag values are escaped. Also note that other tests have verified that escapeJson() // and unescapeJson() work properly. // given String complexSpanName = "span-name-" + ALL_JSON_CHARS_THAT_NEED_ESCAPING; String complexTraceId = "trace-id-" + ALL_JSON_CHARS_THAT_NEED_ESCAPING; String complexParentId = "parent-id-" + ALL_JSON_CHARS_THAT_NEED_ESCAPING; String complexSpanId = "span-id-" + ALL_JSON_CHARS_THAT_NEED_ESCAPING; String complexUserId = "user-id-" + ALL_JSON_CHARS_THAT_NEED_ESCAPING; Span span = Span.newBuilder(complexSpanName, SpanPurpose.CLIENT) .withTraceId(complexTraceId) .withParentSpanId(complexParentId) .withSpanId(complexSpanId) .withUserId(complexUserId) .build(); // when String json = SpanParser.convertSpanToJSON(span); // then assertThat(json).contains("\"traceId\":\"trace-id-" + ESCAPED_JSON_CHARS + "\""); assertThat(json).contains("\"parentSpanId\":\"parent-id-" + ESCAPED_JSON_CHARS + "\""); assertThat(json).contains("\"spanId\":\"span-id-" + ESCAPED_JSON_CHARS + "\""); assertThat(json).contains("\"userId\":\"user-id-" + ESCAPED_JSON_CHARS + "\""); assertThat(json).contains("\"spanName\":\"span-name-" + ESCAPED_JSON_CHARS + "\""); // and when Span deserialized = SpanParser.fromJSON(json); // then verifySpanDeepEquals(deserialized, span, true); }
@Test public void convertSpanToKeyValueFormat_and_fromKeyValueString_should_escape_and_unescape_expected_non_tag_or_annotation_values() { // The TAGS_AND_ANNOTATIONS_WITH_SPECIAL_CHARS case already verified tags and annotations. Now we need to // verify that non-tag values are escaped. Also note that other tests have verified that escapeJson() // and unescapeJson() work properly. // given String complexSpanName = "span-name-" + ALL_JSON_CHARS_THAT_NEED_ESCAPING; String complexTraceId = "trace-id-" + ALL_JSON_CHARS_THAT_NEED_ESCAPING; String complexParentId = "parent-id-" + ALL_JSON_CHARS_THAT_NEED_ESCAPING; String complexSpanId = "span-id-" + ALL_JSON_CHARS_THAT_NEED_ESCAPING; String complexUserId = "user-id-" + ALL_JSON_CHARS_THAT_NEED_ESCAPING; Span span = Span.newBuilder(complexSpanName, SpanPurpose.CLIENT) .withTraceId(complexTraceId) .withParentSpanId(complexParentId) .withSpanId(complexSpanId) .withUserId(complexUserId) .build(); // when String keyValueStr = SpanParser.convertSpanToKeyValueFormat(span); // then assertThat(keyValueStr).contains("traceId=\"trace-id-" + ESCAPED_JSON_CHARS + "\""); assertThat(keyValueStr).contains("parentSpanId=\"parent-id-" + ESCAPED_JSON_CHARS + "\""); assertThat(keyValueStr).contains("spanId=\"span-id-" + ESCAPED_JSON_CHARS + "\""); assertThat(keyValueStr).contains("userId=\"user-id-" + ESCAPED_JSON_CHARS + "\""); assertThat(keyValueStr).contains("spanName=\"span-name-" + ESCAPED_JSON_CHARS + "\""); // and when Span deserialized = SpanParser.fromKeyValueString(keyValueStr); // then verifySpanDeepEquals(deserialized, span, true); }
@DataProvider public static Object[][] dataProviderForContainsSameSpansInSameOrder() { Span spanA = Span.newBuilder("span-A", SpanPurpose.LOCAL_ONLY).withTraceId("A").build(); Span spanB = Span.newBuilder("span-B", SpanPurpose.SERVER).withTraceId("B").build(); Span spanC = Span.newBuilder("span-C", SpanPurpose.CLIENT).withTraceId("C").build(); Span otherSpanA = Span.newBuilder(spanA).build(); Span otherSpanB = Span.newBuilder(spanB).build(); Span otherSpanC = Span.newBuilder(spanC).build(); Deque<Span> abStack = new LinkedList<>(Arrays.asList(spanA, spanB)); Deque<Span> abcStack = new LinkedList<>(Arrays.asList(spanA, spanB, spanC)); Deque<Span> acbStack = new LinkedList<>(Arrays.asList(spanA, spanC, spanB)); Deque<Span> a_null_c_Stack = new LinkedList<>(Arrays.asList(spanA, null, spanC)); Deque<Span> abcStackCopy = new LinkedList<>(abcStack); Deque<Span> abcStackWithDuplicateSpans = new LinkedList<>(Arrays.asList(otherSpanA, otherSpanB, otherSpanC)); return new Object[][] { { abcStack, abcStackCopy, true, "stack copy test" }, { abStack, abStack, true, "same stack instance test" }, { null, null, true, "null stacks test" }, { abcStack, abcStackWithDuplicateSpans, true, "duplicate spans but not same instance test" }, { abStack, null, false, "other stack null test" }, { null, abStack, false, "first stack null test" }, { abStack, abcStack, false, "not same stack size test" }, { abcStack, acbStack, false, "not same order test" }, { abcStack, a_null_c_Stack, false, "null span in other stack test" }, { a_null_c_Stack, abcStack, false, "null span in first stack test" } }; }
@DataProvider(value = { " \t\n\r ", "" }, splitBy = "\\|") @Test @SuppressWarnings("UnnecessaryLocalVariable") public void convertWingtipsSpanToZipkinSpan_throws_IllegalArgumentException_when_passed_wingtipsSpan_with_empty_traceId_format( final String emptyString ) { // given String emptyTraceId = emptyString; long startTimeEpochMicros = Math.abs(random.nextLong()); long durationNanos = Math.abs(random.nextLong()); final Endpoint zipkinEndpoint = Endpoint.newBuilder().serviceName(UUID.randomUUID().toString()).build(); final Span wingtipsSpan = Span.newBuilder("foo", SpanPurpose.CLIENT) .withTraceId(emptyTraceId) .withSpanStartTimeEpochMicros(startTimeEpochMicros) .withDurationNanos(durationNanos) .build(); // when Throwable ex = catchThrowable(() -> impl.convertWingtipsSpanToZipkinSpan(wingtipsSpan, zipkinEndpoint)); // then assertThat(ex) .isInstanceOf(IllegalArgumentException.class) .hasMessage("traceId is empty"); }
@UseDataProvider("idSanitizationScenarios") @Test public void convertWingtipsSpanToZipkinSpan_does_not_sanitize_ids_if_enableIdSanitization_is_false( IdSanitizationScenario scenario ) { // given impl = new WingtipsToZipkinSpanConverterDefaultImpl(false); final Endpoint zipkinEndpoint = Endpoint.newBuilder().serviceName(UUID.randomUUID().toString()).build(); final Span wingtipsSpan = Span.newBuilder("foo", SpanPurpose.CLIENT) .withTraceId(scenario.originalId) .withSpanId(scenario.originalId) .withSpanStartTimeEpochMicros(Math.abs(random.nextLong())) .withDurationNanos(Math.abs(random.nextLong())) .build(); String expectedExceptionMessageSuffix = (scenario.originalId.length() > 16) ? "id.length > 16" : "should be lower-hex encoded with no prefix"; // when Throwable ex = catchThrowable(() -> impl.convertWingtipsSpanToZipkinSpan(wingtipsSpan, zipkinEndpoint)); // then assertThat(ex) .isInstanceOf(IllegalArgumentException.class) .hasMessageEndingWith(expectedExceptionMessageSuffix); }
@UseDataProvider("idSanitizationScenarios") @Test public void convertWingtipsSpanToZipkinSpan_sanitizes_traceId_as_expected_when_sanitization_is_enabled( IdSanitizationScenario scenario ) { // given impl = new WingtipsToZipkinSpanConverterDefaultImpl(true); final Endpoint zipkinEndpoint = Endpoint.newBuilder().serviceName(UUID.randomUUID().toString()).build(); final Span wingtipsSpan = Span.newBuilder("foo", SpanPurpose.CLIENT) .withTraceId(scenario.originalId) .withSpanStartTimeEpochMicros(Math.abs(random.nextLong())) .withDurationNanos(Math.abs(random.nextLong())) .build(); String expectedZipkinInvalidIdTagValue = (scenario.expectedSanitizedResultForTraceId.equals(scenario.originalId)) ? null : scenario.originalId; // when zipkin2.Span zipkinSpan = impl.convertWingtipsSpanToZipkinSpan(wingtipsSpan, zipkinEndpoint); // then assertThat(zipkinSpan.traceId()).isEqualTo(scenario.expectedSanitizedResultForTraceId); assertThat(zipkinSpan.tags().get("invalid.trace_id")).isEqualTo(expectedZipkinInvalidIdTagValue); }
@Test @SuppressWarnings("UnnecessaryLocalVariable") public void convertWingtipsSpanToZipkinSpan_works_as_expected_for_128_bit_trace_id() { // given String high64Bits = "463ac35c9f6413ad"; String low64Bits = "48485a3953bb6124"; String traceId128Bits = high64Bits + low64Bits; long startTimeEpochMicros = Math.abs(random.nextLong()); long durationNanos = Math.abs(random.nextLong()); Endpoint zipkinEndpoint = Endpoint.newBuilder().serviceName(UUID.randomUUID().toString()).build(); Span wingtipsSpan = Span.newBuilder("foo", SpanPurpose.CLIENT) .withTraceId(traceId128Bits) .withSpanStartTimeEpochMicros(startTimeEpochMicros) .withDurationNanos(durationNanos) .build(); // when zipkin2.Span zipkinSpan = impl.convertWingtipsSpanToZipkinSpan(wingtipsSpan, zipkinEndpoint); // then assertThat(zipkinSpan.traceId()).isEqualTo(traceId128Bits); }
/** * @param spanName The {@link Span#getSpanName()} to use for the new child sub-span. * @param spanPurpose The {@link SpanPurpose} for the new child span. See the javadocs for {@link SpanPurpose} for full details on what each enum option * means. If you pass in null for this then {@link SpanPurpose#UNKNOWN} will be used. * @return A new uncompleted span representing a child of this instance. The returned instance's {@link #getParentSpanId()} will be this instance's * {@link #getSpanId()}, its {@link #getSpanName()} will be the given value, its {@link #getSpanId()} will be randomly generated, and its * {@link #getSpanStartTimeEpochMicros()} and {@link #getSpanStartTimeNanos()} values will be set to the appropriate values based on when this * method is called. It will share this instance's {@link #getTraceId()}, {@link #isSampleable()}, and {@link #getUserId()} values. */ public Span generateChildSpan(String spanName, SpanPurpose spanPurpose) { return Span.newBuilder(spanName, spanPurpose) .withTraceId(this.getTraceId()) .withSampleable(this.isSampleable()) .withUserId(this.getUserId()) .withParentSpanId(this.getSpanId()) .withSpanId(TraceAndSpanIdGenerator.generateId()) .withSpanStartTimeEpochMicros(TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis())) .withSpanStartTimeNanos(System.nanoTime()) .withDurationNanos(null) .build(); }