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()); }
private Pair<Span, Map<String, String>> generateUpstreamSpanHeaders() { Span span = Span.newBuilder("upstreamSpan", Span.SpanPurpose.CLIENT).build(); Map<String, String> headers = MapBuilder .builder(TraceHeaders.TRACE_ID, span.getTraceId()) .put(TraceHeaders.SPAN_ID, span.getSpanId()) .put(TraceHeaders.SPAN_NAME, span.getSpanName()) .put(TraceHeaders.TRACE_SAMPLED, String.valueOf(span.isSampleable())) .build(); return Pair.of(span, headers); }
/** * 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()); }
/** * Helper method for adding tracing-related request attributes to the given request based on the given span. * * @param span The span for the overall request. * @param request The request object to add tracing-related request attributes to. */ protected void addTracingInfoToRequestAttributes(Span span, HttpServletRequest request) { request.setAttribute(TraceHeaders.TRACE_SAMPLED, span.isSampleable()); request.setAttribute(TraceHeaders.TRACE_ID, span.getTraceId()); request.setAttribute(TraceHeaders.SPAN_ID, span.getSpanId()); request.setAttribute(TraceHeaders.PARENT_SPAN_ID, span.getParentSpanId()); request.setAttribute(TraceHeaders.SPAN_NAME, span.getSpanName()); request.setAttribute(Span.class.getName(), span); }
@Override public zipkin.Span convertWingtipsSpanToZipkinSpan(Span wingtipsSpan, Endpoint zipkinEndpoint, String localComponentNamespace) { String traceId = wingtipsSpan.getTraceId(); long startEpochMicros = wingtipsSpan.getSpanStartTimeEpochMicros(); long durationMicros = TimeUnit.NANOSECONDS.toMicros(wingtipsSpan.getDurationNanos()); zipkin.Span.Builder builder = createNewZipkinSpanBuilderWithSpanPurposeAnnotations(wingtipsSpan, startEpochMicros, durationMicros, zipkinEndpoint, localComponentNamespace) .id(nullSafeLong(wingtipsSpan.getSpanId())) .name(wingtipsSpan.getSpanName()) .parentId(nullSafeLong(wingtipsSpan.getParentSpanId())) .timestamp(startEpochMicros) .traceIdHigh(traceId.length() == 32 ? nullSafeLong(traceId, 0) : 0) .traceId(nullSafeLong(traceId)) .duration(durationMicros); addAllTagsToBuilderAsBinaryAnnotations(builder, wingtipsSpan.getTags(), zipkinEndpoint); addAllAnnotationsToBuilder(builder, wingtipsSpan.getTimestampedAnnotations(), zipkinEndpoint); return builder.build(); }
@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); }
@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); }
private void verifySingleSpanCompletedAndReturnedInResponse(ExtractableResponse response, long expectedMinSpanDurationMillis, Span expectedUpstreamSpan) { // We can have a race condition where the response is sent and we try to verify here before the servlet filter // has had a chance to complete the span. Wait a few milliseconds to give the servlet filter time to // finish. waitUntilSpanRecorderHasExpectedNumSpans(1); assertThat(spanRecorder.completedSpans).hasSize(1); Span completedSpan = spanRecorder.completedSpans.get(0); String traceIdFromResponse = response.header(TraceHeaders.TRACE_ID); assertThat(traceIdFromResponse).isNotNull(); assertThat(completedSpan.getTraceId()).isEqualTo(traceIdFromResponse); assertThat(TimeUnit.NANOSECONDS.toMillis(completedSpan.getDurationNanos())) .isGreaterThanOrEqualTo(expectedMinSpanDurationMillis); if (expectedUpstreamSpan != null) { assertThat(completedSpan.getTraceId()).isEqualTo(expectedUpstreamSpan.getTraceId()); assertThat(completedSpan.getParentSpanId()).isEqualTo(expectedUpstreamSpan.getSpanId()); } }
@Test public void convertSpanToJSON_should_function_properly_when_there_are_null_values() throws IOException { // given: valid span with null values and JSON string from SpanParser.convertSpanToJSON() Span validSpan = Span.generateRootSpanForNewTrace(spanName, null).build(); assertThat(validSpan.getParentSpanId()).isNull(); assertThat(validSpan.getUserId()).isNull(); assertThat(validSpan.getDurationNanos()).isNull(); 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); }
@Test public void fromHttpServletRequest_generates_new_spanId_if_missing_from_headers() { // given: a request with a trace ID but no span ID in the headers String traceId = UUID.randomUUID().toString(); given(request.getHeader(TraceHeaders.TRACE_ID)).willReturn(traceId); // when: we use it to create span objects Span firstSpan = HttpSpanFactory.fromHttpServletRequest(request, USER_ID_HEADER_KEYS); Span secondSpan = HttpSpanFactory.fromHttpServletRequest(request, USER_ID_HEADER_KEYS); // then: ensure each call generates a span with the same trace ID but new span ID assertThat(firstSpan.getTraceId()).isEqualTo(traceId); assertThat(secondSpan.getTraceId()).isEqualTo(traceId); assertThat(firstSpan.getSpanId()).isNotEmpty(); assertThat(secondSpan.getSpanId()).isNotEmpty(); assertThat(firstSpan.getSpanId()).isNotEqualTo(secondSpan.getSpanId()); }
private Span findCompletedSpan(String expectedSpanName, String expectedSpanHandler) { return spanRecorder.completedSpans .stream() .filter( s -> s.getSpanName().equals(expectedSpanName) && expectedSpanHandler.equals(s.getTags().get(WingtipsTags.SPAN_HANDLER)) ) .findFirst() .orElseThrow( () -> new RuntimeException( "Unable to find span with expected span name: " + expectedSpanName + " and span handler: " + expectedSpanHandler ) ); }
@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); }
@Test public void changeSpanName_works_as_expected() { // given Span span = Span.newBuilder("origSpanName", Span.SpanPurpose.SERVER).build(); String newSpanName = UUID.randomUUID().toString(); assertThat(span.getSpanName()).isNotEqualTo(newSpanName); // when SpanMutator.changeSpanName(span, newSpanName); // then assertThat(span.getSpanName()).isEqualTo(newSpanName); }
@Test public void fromRequestWithHeaders_sets_user_id_to_null_if_passed_null_or_empty_userIdHeaderKeys_list() { // given String traceId = UUID.randomUUID().toString(); given(request.getHeader(TraceHeaders.TRACE_ID)).willReturn(traceId); List<List<String>> badLists = Arrays.asList(null, Collections.<String>emptyList()); for (List<String> badList : badLists) { // when Span result = HttpRequestTracingUtils.fromRequestWithHeaders(request, badList); // expect assertThat(result.getTraceId()).isEqualTo(traceId); assertThat(result.getUserId()).isNull(); } }
@UseDataProvider("escapedAndUnescapedQuotesBeforeKeyOrValueEndScenarioDataProvider") @Test public void fromJSON_properly_handles_escaped_quotes_and_unescaped_quotes_preceded_by_backslashes( EscapedAndUnescapedQuotesBeforeKeyOrValueEndScenario scenario ) { // given Span span = Span.newBuilder("someSpan", SpanPurpose.CLIENT) .withTag(scenario.unescapedKey, scenario.unescapedValue) .withTimestampedAnnotation( TimestampedAnnotation.forEpochMicros(1234, scenario.unescapedValue) ) .build(); String json = SpanParser.convertSpanToJSON(span); // when Span result = SpanParser.fromJSON(json); // then assertThat(result.getTags().get(scenario.unescapedKey)).isEqualTo(scenario.unescapedValue); assertThat(result.getTimestampedAnnotations().get(0).getValue()).isEqualTo(scenario.unescapedValue); }
@Test public void builder_withTags_does_nothing_if_passed_null() { // given Span.Builder builder = Span.newBuilder("foo", SpanPurpose.UNKNOWN); Map<String, String> tagsMapSpy = spy(new LinkedHashMap<>()); Whitebox.setInternalState(builder, "tags", tagsMapSpy); // when Span.Builder resultingBuilder = builder.withTags(null); // then assertThat(resultingBuilder).isSameAs(builder); verifyZeroInteractions(tagsMapSpy); // and when Span resultingSpan = resultingBuilder.build(); // then assertThat(resultingSpan.getTags()).isEmpty(); }
@Test public void setSpanName_works_as_expected() { // given Span span = Span.newBuilder("origSpanName", SpanPurpose.SERVER).build(); String newSpanName = UUID.randomUUID().toString(); assertThat(span.getSpanName()).isNotEqualTo(newSpanName); // when span.setSpanName(newSpanName); // then assertThat(span.getSpanName()).isEqualTo(newSpanName); }
@Test public void getCurrentSpan_should_return_current_span() throws Exception { // given Tracer tracer = Tracer.getInstance(); tracer.startRequestWithRootSpan("test-span"); // when Span span = tracer.getCurrentSpan(); // then assertThat(span).isNotNull(); assertThat(span.getSpanName()).isEqualTo("test-span"); }
/** * Notifies all listeners that the given span was sampled using {@link SpanLifecycleListener#spanSampled(Span)}, <b>but only if the span's {@link Span#isSampleable()} * method returns true!</b> If the span is not sampleable then this method does nothing. */ protected void notifyIfSpanSampled(Span span) { if (span.isSampleable()) { for (SpanLifecycleListener tll : spanLifecycleListeners) { tll.spanSampled(span); } } }
@Test public void putTag_works_as_expected() { // given Span span = Span.newBuilder("foo", SpanPurpose.CLIENT).build(); assertThat(span.getTags()).isEmpty(); String tagKey = "key-" + UUID.randomUUID().toString(); String tagValue = "value-" + UUID.randomUUID().toString(); String otherValue = "othervalue-" + UUID.randomUUID().toString(); // when span.putTag(tagKey, tagValue); // then assertThat(span.getTags()).hasSize(1); assertThat(span.getTags().get(tagKey)).isEqualTo(tagValue); // and when span.putTag(tagKey, otherValue); // then assertThat(span.getTags()).hasSize(1); assertThat(span.getTags().get(tagKey)).isEqualTo(otherValue); }