@SuppressWarnings("WeakerAccess") protected zipkin2.Span.Kind determineZipkinKind(Span wingtipsSpan) { SpanPurpose wtsp = wingtipsSpan.getSpanPurpose(); // Clunky if checks necessary to avoid code coverage gaps with a switch statement // due to unreachable default case. :( if (SpanPurpose.SERVER == wtsp) { return zipkin2.Span.Kind.SERVER; } else if (SpanPurpose.CLIENT == wtsp) { return zipkin2.Span.Kind.CLIENT; } else if (SpanPurpose.LOCAL_ONLY == wtsp || SpanPurpose.UNKNOWN == wtsp) { // No Zipkin Kind associated with these SpanPurposes. return null; } else { // This case should technically be impossible, but in case it happens we'll log a warning and default to // no Zipkin kind. logger.warn("Unhandled SpanPurpose type: {}", String.valueOf(wtsp)); return null; } }
@SuppressWarnings("WeakerAccess") protected zipkin2.Span.Kind determineZipkinKind(Span wingtipsSpan) { SpanPurpose wtsp = wingtipsSpan.getSpanPurpose(); // Clunky if checks necessary to avoid code coverage gaps with a switch statement // due to unreachable default case. :( if (SpanPurpose.SERVER == wtsp) { return zipkin2.Span.Kind.SERVER; } else if (SpanPurpose.CLIENT == wtsp) { return zipkin2.Span.Kind.CLIENT; } else if (SpanPurpose.LOCAL_ONLY == wtsp || SpanPurpose.UNKNOWN == wtsp) { // No Zipkin Kind associated with these SpanPurposes. return null; } else { // This case should technically be impossible, but in case it happens we'll log a warning and default to // no Zipkin kind. logger.warn("Unhandled SpanPurpose type: {}", String.valueOf(wtsp)); return null; } }
@Test public void public_constructor_defaults_to_UNKNOWN_span_purpose_if_passed_null() { // when Span span = new Span(traceId, parentSpanId, spanId, spanName, true, userId, null, 42, null, null, null, null); // then assertThat(span.getSpanPurpose()).isEqualTo(SpanPurpose.UNKNOWN); }
private void verifySpanPurposeRelatedStuff(zipkin2.Span zipkinSpan, Span wingtipsSpan) { SpanPurpose spanPurpose = wingtipsSpan.getSpanPurpose(); switch(spanPurpose) { case SERVER: assertThat(zipkinSpan.kind()).isEqualTo(zipkin2.Span.Kind.SERVER); break; case CLIENT: assertThat(zipkinSpan.kind()).isEqualTo(zipkin2.Span.Kind.CLIENT); break; case LOCAL_ONLY: case UNKNOWN: // intentional fall-through: local and unknown span purpose are treated the same way break; default: throw new IllegalStateException("Unhandled spanPurpose: " + spanPurpose.name()); } }
@Test public void fromHttpServletRequestOrCreateRootSpan_returns_new_root_span_if_traceId_is_missing_from_headers() { // given: no trace ID in headers, but user ID exists given(request.getHeader(ALT_USER_ID_HEADER_KEY)).willReturn(altUserId); // when: creating Span object from HTTP request using fromHttpServletRequestOrCreateRootSpan long beforeCallNanos = System.nanoTime(); Span newSpan = HttpSpanFactory.fromHttpServletRequestOrCreateRootSpan(request, USER_ID_HEADER_KEYS); long afterCallNanos = System.nanoTime(); // then: ensure root span object is created even though there was no trace ID header, and the returned span contains the expected user ID assertThat(newSpan).isNotNull(); assertThat(newSpan.getParentSpanId()).isNull(); assertThat(newSpan.getUserId()).isEqualTo(altUserId); assertThat(newSpan.getSpanStartTimeNanos()).isBetween(beforeCallNanos, afterCallNanos); assertThat(newSpan.isCompleted()).isFalse(); assertThat(newSpan.getSpanPurpose()).isEqualTo(SpanPurpose.SERVER); }
protected zipkin.Span.Builder createNewZipkinSpanBuilderWithSpanPurposeAnnotations( Span wingtipsSpan, long startEpochMicros, long durationMicros, Endpoint zipkinEndpoint, String localComponentNamespace ) { zipkin.Span.Builder zsb = zipkin.Span.builder(); switch(wingtipsSpan.getSpanPurpose()) { case SERVER: zsb.addAnnotation(Annotation.create(startEpochMicros, Constants.SERVER_RECV, zipkinEndpoint)) .addAnnotation(Annotation.create(startEpochMicros + durationMicros, Constants.SERVER_SEND, zipkinEndpoint)); break; case CLIENT: zsb.addAnnotation(Annotation.create(startEpochMicros, Constants.CLIENT_SEND, zipkinEndpoint)) .addAnnotation(Annotation.create(startEpochMicros + durationMicros, Constants.CLIENT_RECV, zipkinEndpoint)); break; case LOCAL_ONLY: case UNKNOWN: // intentional fall-through: local and unknown span purpose are treated the same way zsb.addBinaryAnnotation(BinaryAnnotation.create(Constants.LOCAL_COMPONENT, localComponentNamespace, zipkinEndpoint)); break; default: logger.warn("Unhandled SpanPurpose type: " + wingtipsSpan.getSpanPurpose().name()); } return zsb; }
protected zipkin.Span.Builder createNewZipkinSpanBuilderWithSpanPurposeAnnotations( Span wingtipsSpan, long startEpochMicros, long durationMicros, Endpoint zipkinEndpoint, String localComponentNamespace ) { zipkin.Span.Builder zsb = zipkin.Span.builder(); switch(wingtipsSpan.getSpanPurpose()) { case SERVER: zsb.addAnnotation(Annotation.create(startEpochMicros, Constants.SERVER_RECV, zipkinEndpoint)) .addAnnotation(Annotation.create(startEpochMicros + durationMicros, Constants.SERVER_SEND, zipkinEndpoint)); break; case CLIENT: zsb.addAnnotation(Annotation.create(startEpochMicros, Constants.CLIENT_SEND, zipkinEndpoint)) .addAnnotation(Annotation.create(startEpochMicros + durationMicros, Constants.CLIENT_RECV, zipkinEndpoint)); break; case LOCAL_ONLY: case UNKNOWN: // intentional fall-through: local and unknown span purpose are treated the same way zsb.addBinaryAnnotation(BinaryAnnotation.create(Constants.LOCAL_COMPONENT, localComponentNamespace, zipkinEndpoint)); break; default: logger.warn("Unhandled SpanPurpose type: " + wingtipsSpan.getSpanPurpose().name()); } return zsb; }
@Test public void public_constructor_works_as_expected_for_completed_span() { // when Span span = new Span( traceId, parentSpanId, spanId, spanName, sampleableForFullyCompleteSpan, userId, spanPurposeForFullyCompletedSpan, startTimeEpochMicrosForFullyCompleteSpan, startTimeNanosForFullyCompleteSpan, durationNanosForFullyCompletedSpan, tags, annotations ); // then assertThat(span.getTraceId()).isEqualTo(traceId); assertThat(span.getParentSpanId()).isEqualTo(parentSpanId); assertThat(span.getSpanId()).isEqualTo(spanId); assertThat(span.getSpanName()).isEqualTo(spanName); assertThat(span.isSampleable()).isEqualTo(sampleableForFullyCompleteSpan); assertThat(span.getUserId()).isEqualTo(userId); assertThat(span.getSpanStartTimeEpochMicros()).isEqualTo(startTimeEpochMicrosForFullyCompleteSpan); assertThat(span.getSpanStartTimeNanos()).isEqualTo(startTimeNanosForFullyCompleteSpan); assertThat(span.getSpanPurpose()).isEqualTo(spanPurposeForFullyCompletedSpan); assertThat(span.isCompleted()).isTrue(); assertThat(span.getDurationNanos()).isEqualTo(durationNanosForFullyCompletedSpan); assertThat(span.getTags()).isEqualTo(tags); assertThat(span.getTimestampedAnnotations()).isEqualTo(annotations); }
@Test public void public_constructor_works_as_expected_for_incomplete_span() { // when Span span = new Span( traceId, parentSpanId, spanId, spanName, sampleableForFullyCompleteSpan, userId, spanPurposeForFullyCompletedSpan, startTimeEpochMicrosForFullyCompleteSpan, startTimeNanosForFullyCompleteSpan, null, tags, annotations ); // then assertThat(span.getTraceId()).isEqualTo(traceId); assertThat(span.getParentSpanId()).isEqualTo(parentSpanId); assertThat(span.getSpanId()).isEqualTo(spanId); assertThat(span.getSpanName()).isEqualTo(spanName); assertThat(span.isSampleable()).isEqualTo(sampleableForFullyCompleteSpan); assertThat(span.getUserId()).isEqualTo(userId); assertThat(span.getSpanStartTimeEpochMicros()).isEqualTo(startTimeEpochMicrosForFullyCompleteSpan); assertThat(span.getSpanStartTimeNanos()).isEqualTo(startTimeNanosForFullyCompleteSpan); assertThat(span.getSpanPurpose()).isEqualTo(spanPurposeForFullyCompletedSpan); assertThat(span.isCompleted()).isFalse(); assertThat(span.getDurationNanos()).isNull(); assertThat(span.getTags()).isEqualTo(tags); assertThat(span.getTimestampedAnnotations()).isEqualTo(annotations); }
@Test public void fromHttpServletRequestOrCreateRootSpan_pulls_from_headers_if_available() { // given: a set of standard Span header values given(request.getHeader(TraceHeaders.TRACE_ID)).willReturn(sampleTraceID); given(request.getHeader(TraceHeaders.TRACE_SAMPLED)).willReturn(Boolean.TRUE.toString()); given(request.getHeader(TraceHeaders.SPAN_ID)).willReturn(sampleSpanID); given(request.getHeader(TraceHeaders.PARENT_SPAN_ID)).willReturn(sampleParentSpanID); given(request.getHeader(USER_ID_HEADER_KEY)).willReturn(userId); // when: creating Span object from HTTP request using fromHttpServletRequestOrCreateRootSpan long beforeCallNanos = System.nanoTime(); Span goodSpan = HttpSpanFactory.fromHttpServletRequestOrCreateRootSpan(request, USER_ID_HEADER_KEYS); long afterCallNanos = System.nanoTime(); // then: ensure Span object gets identical values from corresponding headers assertThat(goodSpan.getTraceId()).isEqualTo(sampleTraceID); assertThat(goodSpan.isSampleable()).isTrue(); assertThat(goodSpan.getSpanId()).isEqualTo(sampleSpanID); assertThat(goodSpan.getParentSpanId()).isEqualTo(sampleParentSpanID); assertThat(goodSpan.getUserId()).isEqualTo(userId); assertThat(goodSpan.getSpanStartTimeNanos()).isBetween(beforeCallNanos, afterCallNanos); assertThat(goodSpan.isCompleted()).isFalse(); assertThat(goodSpan.getSpanPurpose()).isEqualTo(SpanPurpose.SERVER); }
@Test public void equals_returns_false_and_hashCode_different_if_spanPurpose_is_different() { // given Span fullSpan1 = createFilledOutSpan(true); Span fullSpan2 = createFilledOutSpan(true); List<SpanPurpose> badDataList = Arrays.asList(SpanPurpose.CLIENT, SpanPurpose.UNKNOWN, null); for (SpanPurpose badData : badDataList) { assertThat(fullSpan1.getSpanPurpose()).isNotEqualTo(badData); Whitebox.setInternalState(fullSpan2, "spanPurpose", badData); // expect assertThat(fullSpan1.equals(fullSpan2)).isFalse(); assertThat(fullSpan2.equals(fullSpan1)).isFalse(); assertThat(fullSpan1.hashCode()).isNotEqualTo(fullSpan2.hashCode()); } }
@Test public void fromHttpServletRequest_creates_span_with_all_values_if_all_values_available_from_request_headers() { // given: a set of standard Span header values given(request.getHeader(TraceHeaders.TRACE_ID)).willReturn(sampleTraceID); given(request.getHeader(TraceHeaders.TRACE_SAMPLED)).willReturn(Boolean.FALSE.toString()); given(request.getHeader(TraceHeaders.SPAN_ID)).willReturn(sampleSpanID); given(request.getHeader(TraceHeaders.PARENT_SPAN_ID)).willReturn(sampleParentSpanID); given(request.getHeader(USER_ID_HEADER_KEY)).willReturn(userId); // when: creating Span object from HTTP request long beforeCallNanos = System.nanoTime(); Span goodSpan = HttpSpanFactory.fromHttpServletRequest(request, USER_ID_HEADER_KEYS); long afterCallNanos = System.nanoTime(); // then: ensure Span object gets identical values from corresponding headers assertThat(goodSpan.getTraceId()).isEqualTo(sampleTraceID); assertThat(goodSpan.isSampleable()).isFalse(); assertThat(goodSpan.getSpanId()).isEqualTo(sampleSpanID); assertThat(goodSpan.getParentSpanId()).isEqualTo(sampleParentSpanID); assertThat(goodSpan.getUserId()).isEqualTo(userId); assertThat(goodSpan.getSpanStartTimeNanos()).isBetween(beforeCallNanos, afterCallNanos); assertThat(goodSpan.isCompleted()).isFalse(); assertThat(goodSpan.getSpanPurpose()).isEqualTo(SpanPurpose.SERVER); }
@Test public void fromHttpServletRequest_pulls_from_alt_user_id_if_specified_in_header_and_primary_is_missing() { // given: a set of standard Span header values with the alt user ID header specified instead of the primary given(request.getHeader(TraceHeaders.TRACE_ID)).willReturn(sampleTraceID); given(request.getHeader(TraceHeaders.TRACE_SAMPLED)).willReturn(Boolean.TRUE.toString()); given(request.getHeader(TraceHeaders.SPAN_ID)).willReturn(sampleSpanID); given(request.getHeader(TraceHeaders.PARENT_SPAN_ID)).willReturn(sampleParentSpanID); given(request.getHeader(ALT_USER_ID_HEADER_KEY)).willReturn(altUserId); // when: creating Span object from HTTP request long beforeCallNanos = System.nanoTime(); Span goodSpan = HttpSpanFactory.fromHttpServletRequest(request, USER_ID_HEADER_KEYS); long afterCallNanos = System.nanoTime(); // then: ensure Span object gets identical values from corresponding headers assertThat(goodSpan.getTraceId()).isEqualTo(sampleTraceID); assertThat(goodSpan.isSampleable()).isTrue(); assertThat(goodSpan.getSpanId()).isEqualTo(sampleSpanID); assertThat(goodSpan.getParentSpanId()).isEqualTo(sampleParentSpanID); assertThat(goodSpan.getUserId()).isEqualTo(altUserId); assertThat(goodSpan.getSpanStartTimeNanos()).isBetween(beforeCallNanos, afterCallNanos); assertThat(goodSpan.isCompleted()).isFalse(); assertThat(goodSpan.getSpanPurpose()).isEqualTo(SpanPurpose.SERVER); }
@Test public void fromHttpServletRequest_creates_span_with_all_values_minus_user_id_if_user_id_list_is_not_passed_in() { // given: a set of standard Span header values given(request.getHeader(TraceHeaders.TRACE_ID)).willReturn(sampleTraceID); given(request.getHeader(TraceHeaders.TRACE_SAMPLED)).willReturn(Boolean.FALSE.toString()); given(request.getHeader(TraceHeaders.SPAN_ID)).willReturn(sampleSpanID); given(request.getHeader(TraceHeaders.PARENT_SPAN_ID)).willReturn(sampleParentSpanID); given(request.getHeader(USER_ID_HEADER_KEY)).willReturn(userId); // when: creating Span object from HTTP request long beforeCallNanos = System.nanoTime(); Span goodSpan = HttpSpanFactory.fromHttpServletRequest(request, null); long afterCallNanos = System.nanoTime(); // then: ensure Span object gets identical values from corresponding headers assertThat(goodSpan.getTraceId()).isEqualTo(sampleTraceID); assertThat(goodSpan.isSampleable()).isFalse(); assertThat(goodSpan.getSpanId()).isEqualTo(sampleSpanID); assertThat(goodSpan.getParentSpanId()).isEqualTo(sampleParentSpanID); assertThat(goodSpan.getUserId()).isNull(); assertThat(goodSpan.getSpanStartTimeNanos()).isBetween(beforeCallNanos, afterCallNanos); assertThat(goodSpan.isCompleted()).isFalse(); assertThat(goodSpan.getSpanPurpose()).isEqualTo(SpanPurpose.SERVER); }
public static void verifySpanDeepEquals( Span spanToVerify, Span expectedSpan, boolean allowStartTimeNanosFudgeFactor ) { assertThat(spanToVerify.getSpanStartTimeEpochMicros()).isEqualTo(expectedSpan.getSpanStartTimeEpochMicros()); if (allowStartTimeNanosFudgeFactor) { assertThat(spanToVerify.getSpanStartTimeNanos()) .isCloseTo(expectedSpan.getSpanStartTimeNanos(), Offset.offset(TimeUnit.MILLISECONDS.toNanos(1))); } else { assertThat(spanToVerify.getSpanStartTimeNanos()).isEqualTo(expectedSpan.getSpanStartTimeNanos()); } assertThat(spanToVerify.isCompleted()).isEqualTo(expectedSpan.isCompleted()); assertThat(spanToVerify.getTraceId()).isEqualTo(expectedSpan.getTraceId()); assertThat(spanToVerify.getSpanId()).isEqualTo(expectedSpan.getSpanId()); assertThat(spanToVerify.getParentSpanId()).isEqualTo(expectedSpan.getParentSpanId()); assertThat(spanToVerify.getSpanName()).isEqualTo(expectedSpan.getSpanName()); assertThat(spanToVerify.isSampleable()).isEqualTo(expectedSpan.isSampleable()); assertThat(spanToVerify.getUserId()).isEqualTo(expectedSpan.getUserId()); assertThat(spanToVerify.getDurationNanos()).isEqualTo(expectedSpan.getDurationNanos()); assertThat(spanToVerify.getSpanPurpose()).isEqualTo(expectedSpan.getSpanPurpose()); assertThat(spanToVerify.getTags()).isEqualTo(expectedSpan.getTags()); assertThat(spanToVerify.getTimestampedAnnotations()).isEqualTo(expectedSpan.getTimestampedAnnotations()); }
@Test public void fromRequestWithHeaders_generates_span_from_headers_in_request_for_all_user_id_header_keys() { // Verify more than 1 distinct user ID header key. assertThat(new HashSet<>(USER_ID_HEADER_KEYS).size()).isGreaterThan(1); for (String userIdHeaderKey : USER_ID_HEADER_KEYS) { // given: a set of standard span header values for the header key String userIdValue = UUID.randomUUID().toString(); RequestWithHeaders request = mock(RequestWithHeaders.class); given(request.getHeader(TraceHeaders.TRACE_ID)).willReturn(sampleTraceID); given(request.getHeader(TraceHeaders.TRACE_SAMPLED)).willReturn(Boolean.TRUE.toString()); given(request.getHeader(TraceHeaders.SPAN_ID)).willReturn(sampleSpanID); given(request.getHeader(TraceHeaders.PARENT_SPAN_ID)).willReturn(sampleParentSpanID); given(request.getHeader(userIdHeaderKey)).willReturn(userIdValue); // when: creating span object from HTTP request Span goodSpan = HttpRequestTracingUtils.fromRequestWithHeaders(request, USER_ID_HEADER_KEYS); // then: ensure span object gets identical values from corresponding headers, and sets the span purpose to SERVER assertThat(goodSpan.getTraceId()).isEqualTo(sampleTraceID); assertThat(goodSpan.isSampleable()).isTrue(); assertThat(goodSpan.getSpanId()).isEqualTo(sampleSpanID); assertThat(goodSpan.getParentSpanId()).isEqualTo(sampleParentSpanID); assertThat(goodSpan.getUserId()).isEqualTo(userIdValue); assertThat(goodSpan.getSpanPurpose()).isEqualTo(SpanPurpose.SERVER); } }
@Test public void fromRequestWithHeaders_generates_span_from_headers_in_request() { // given: a set of standard span header values given(request.getHeader(TraceHeaders.TRACE_ID)).willReturn(sampleTraceID); given(request.getHeader(TraceHeaders.TRACE_SAMPLED)).willReturn(Boolean.TRUE.toString()); given(request.getHeader(TraceHeaders.SPAN_ID)).willReturn(sampleSpanID); given(request.getHeader(TraceHeaders.PARENT_SPAN_ID)).willReturn(sampleParentSpanID); given(request.getHeader(USER_ID_HEADER_KEY)).willReturn(userId); // when: creating span object from HTTP request Span goodSpan = HttpRequestTracingUtils.fromRequestWithHeaders(request, USER_ID_HEADER_KEYS); // then: ensure span object gets identical values from corresponding headers, and the span purpose is set to SERVER assertThat(goodSpan.getTraceId()).isEqualTo(sampleTraceID); assertThat(goodSpan.isSampleable()).isTrue(); assertThat(goodSpan.getSpanId()).isEqualTo(sampleSpanID); assertThat(goodSpan.getParentSpanId()).isEqualTo(sampleParentSpanID); assertThat(goodSpan.getUserId()).isEqualTo(userId); assertThat(goodSpan.getSpanPurpose()).isEqualTo(SpanPurpose.SERVER); }
@DataProvider(value = { "SERVER", "CLIENT", "LOCAL_ONLY", "UNKNOWN" }, splitBy = "\\|") @Test public void newBuilder_with_copy_arg_returns_exact_copy(SpanPurpose spanPurpose) { // given Span origSpan = createFilledOutSpan(true); Whitebox.setInternalState(origSpan, "spanPurpose", spanPurpose); assertThat(origSpan.getSpanPurpose()).isEqualTo(spanPurpose); // when Span copySpan = Span.newBuilder(origSpan).build(); // then verifySpanDeepEquals(copySpan, origSpan, false); }
@UseDataProvider("tagAndAnnotationScenarioDataProvider") @Test public void convertSpanToJSON_should_function_properly_when_there_are_no_null_values( TagAndAnnotationScenario scenario ) throws IOException { // given: valid span without any null values, span completed (so that end time is not null), and JSON string // from SpanParser.convertSpanToJSON() Span validSpan = createFilledOutSpan(true, scenario.tags, scenario.annotations); assertThat(validSpan.getTraceId()).isNotEmpty(); assertThat(validSpan.getUserId()).isNotEmpty(); assertThat(validSpan.getParentSpanId()).isNotEmpty(); assertThat(validSpan.getSpanName()).isNotEmpty(); assertThat(validSpan.getSpanId()).isNotEmpty(); assertThat(validSpan.getDurationNanos()).isNotNull(); assertThat(validSpan.isCompleted()).isTrue(); assertThat(validSpan.getSpanPurpose()).isNotNull(); assertThat(validSpan.getTags()).isEqualTo(scenario.tags); assertThat(validSpan.getTimestampedAnnotations()).isEqualTo(scenario.annotations); String json = SpanParser.convertSpanToJSON(validSpan); // when: jackson is used to deserialize that JSON Map<String, Object> spanValuesFromJackson = objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {}); // then: the original span context and jackson's span context values should be exactly the same verifySpanEqualsDeserializedValues(validSpan, spanValuesFromJackson); }
@UseDataProvider("tagAndAnnotationScenarioDataProvider") @Test public void fromJson_should_function_properly_when_there_are_no_null_values(TagAndAnnotationScenario scenario) { // given: valid span without any null values, completed (so that end time is not null) and JSON string // from SpanParser.convertSpanToJSON() Span validSpan = createFilledOutSpan(true, scenario.tags, scenario.annotations); assertThat(validSpan).isNotNull(); assertThat(validSpan.getTraceId()).isNotNull(); assertThat(validSpan.getUserId()).isNotNull(); assertThat(validSpan.getParentSpanId()).isNotNull(); assertThat(validSpan.getSpanName()).isNotNull(); assertThat(validSpan.getSpanId()).isNotNull(); assertThat(validSpan.getDurationNanos()).isNotNull(); assertThat(validSpan.isCompleted()).isTrue(); assertThat(validSpan.getSpanPurpose()).isNotNull(); assertThat(validSpan.getTags()).isEqualTo(scenario.tags); assertThat(validSpan.getTimestampedAnnotations()).isEqualTo(scenario.annotations); String json = SpanParser.convertSpanToJSON(validSpan); // when: fromJson is called Span spanFromJson = SpanParser.fromJSON(json); // then: the original span and the fromJson() span values should be exactly the same verifySpanDeepEquals(spanFromJson, validSpan, true); }