/** * 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); }
public EndpointSpanInfoDto(HttpServletRequest request, Span endpoint_execution_span, List<String> userIdHeaderKeys) { this.parent_span_info = new SpanInfoDto( request.getHeader(TraceHeaders.TRACE_ID), request.getHeader(TraceHeaders.SPAN_ID), request.getHeader(TraceHeaders.PARENT_SPAN_ID), request.getHeader(TraceHeaders.TRACE_SAMPLED), HttpSpanFactory.getUserIdFromHttpServletRequest(request, userIdHeaderKeys) ); this.endpoint_execution_span_info = new SpanInfoDto( endpoint_execution_span.getTraceId(), endpoint_execution_span.getSpanId(), endpoint_execution_span.getParentSpanId(), String.valueOf(endpoint_execution_span.isSampleable()), endpoint_execution_span.getUserId() ); }
public EndpointSpanInfoDto(HttpServletRequest request, Span endpoint_execution_span, List<String> userIdHeaderKeys) { this.parent_span_info = new SpanInfoDto( request.getHeader(TraceHeaders.TRACE_ID), request.getHeader(TraceHeaders.SPAN_ID), request.getHeader(TraceHeaders.PARENT_SPAN_ID), request.getHeader(TraceHeaders.TRACE_SAMPLED), HttpSpanFactory.getUserIdFromHttpServletRequest(request, userIdHeaderKeys) ); this.endpoint_execution_span_info = new SpanInfoDto( endpoint_execution_span.getTraceId(), endpoint_execution_span.getSpanId(), endpoint_execution_span.getParentSpanId(), String.valueOf(endpoint_execution_span.isSampleable()), endpoint_execution_span.getUserId() ); }
/** * Sets the tracing headers on the given {@link HttpObjectForPropagation} with values from the given {@link Span}. * Does nothing if any of the given arguments are null (i.e. it is safe to pass null, but nothing will happen). * * <p>This method conforms to the <a href="https://github.com/openzipkin/b3-propagation">B3 propagation spec</a>. * * @param httpObjectForPropagation The {@link HttpObjectForPropagation} to set tracing headers on. Can be null - * if this is null then this method will do nothing. * @param span The {@link Span} to get the tracing info from to set on the headers. Can be null - if this is null * then this method will do nothing. */ public static void propagateTracingHeaders(HttpObjectForPropagation httpObjectForPropagation, Span span) { if (span == null || httpObjectForPropagation == null) return; httpObjectForPropagation.setHeader(TRACE_ID, span.getTraceId()); httpObjectForPropagation.setHeader(SPAN_ID, span.getSpanId()); httpObjectForPropagation.setHeader(TRACE_SAMPLED, (span.isSampleable()) ? "1" : "0"); if (span.getParentSpanId() != null) httpObjectForPropagation.setHeader(PARENT_SPAN_ID, span.getParentSpanId()); }
@Test public void doFilterInternal_should_set_request_attributes_to_new_span_info() throws ServletException, IOException { // given: filter RequestTracingFilter filter = getBasicFilter(); // when: doFilterInternal is called filter.doFilterInternal(requestMock, responseMock, spanCapturingFilterChain); // then: request attributes should be set with the new span's info assertThat(spanCapturingFilterChain.capturedSpan).isNotNull(); Span newSpan = spanCapturingFilterChain.capturedSpan; verify(requestMock).setAttribute(TraceHeaders.TRACE_SAMPLED, newSpan.isSampleable()); verify(requestMock).setAttribute(TraceHeaders.TRACE_ID, newSpan.getTraceId()); verify(requestMock).setAttribute(TraceHeaders.SPAN_ID, newSpan.getSpanId()); verify(requestMock).setAttribute(TraceHeaders.PARENT_SPAN_ID, newSpan.getParentSpanId()); verify(requestMock).setAttribute(TraceHeaders.SPAN_NAME, newSpan.getSpanName()); verify(requestMock).setAttribute(Span.class.getName(), newSpan); }
@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); }
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 equals_returns_false_and_hashCode_different_if_parentSpanId_is_different() { // given Span fullSpan1 = createFilledOutSpan(true); Span fullSpan2 = createFilledOutSpan(true); List<String> badDataList = Arrays.asList(fullSpan1.getParentSpanId() + "_nope", null); for (String badData : badDataList) { Whitebox.setInternalState(fullSpan2, "parentSpanId", badData); // expect assertThat(fullSpan1.equals(fullSpan2)).isFalse(); assertThat(fullSpan2.equals(fullSpan1)).isFalse(); assertThat(fullSpan1.hashCode()).isNotEqualTo(fullSpan2.hashCode()); } }
private Span 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(completedSpan.getSpanName()).doesNotContain("?"); assertThat(TimeUnit.NANOSECONDS.toMillis(completedSpan.getDurationNanos())) .isGreaterThanOrEqualTo(expectedMinSpanDurationMillis); if (expectedUpstreamSpan != null) { assertThat(completedSpan.getTraceId()).isEqualTo(expectedUpstreamSpan.getTraceId()); assertThat(completedSpan.getParentSpanId()).isEqualTo(expectedUpstreamSpan.getSpanId()); } return completedSpan; }
private SpanInfoDto spanInfoDtoFromSpan(Span span) { return new SpanInfoDto( span.getTraceId(), span.getSpanId(), span.getParentSpanId(), String.valueOf(span.isSampleable()), span.getUserId() ); }
private Span 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(completedSpan.getSpanName()).doesNotContain("?"); assertThat(TimeUnit.NANOSECONDS.toMillis(completedSpan.getDurationNanos())) .isGreaterThanOrEqualTo(expectedMinSpanDurationMillis); if (expectedUpstreamSpan != null) { assertThat(completedSpan.getTraceId()).isEqualTo(expectedUpstreamSpan.getTraceId()); assertThat(completedSpan.getParentSpanId()).isEqualTo(expectedUpstreamSpan.getSpanId()); } return completedSpan; }
private SpanInfoDto spanInfoDtoFromSpan(Span span) { return new SpanInfoDto( span.getTraceId(), span.getSpanId(), span.getParentSpanId(), String.valueOf(span.isSampleable()), span.getUserId() ); }
@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 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); }
private void verifyExpectedTracingHeadersReceivedDownstream( ExtractableResponse response, Span expectedSpan ) { verifyExpectedTracingHeadersReceivedDownstream( response, expectedSpan.getTraceId(), expectedSpan.getSpanId(), expectedSpan.getParentSpanId(), convertSampleableBooleanToExpectedB3Value(expectedSpan.isSampleable()) ); }
@Test public void convertSpanToKeyValueFormat_should_function_properly_when_there_are_null_values() { // given: valid span with null values and key/value string from SpanParser.convertSpanToKeyValueFormat() Span validSpan = Span.generateRootSpanForNewTrace(spanName, null).build(); assertThat(validSpan.getParentSpanId()).isNull(); assertThat(validSpan.getUserId()).isNull(); assertThat(validSpan.getDurationNanos()).isNull(); String keyValueStr = SpanParser.convertSpanToKeyValueFormat(validSpan); // when: the string is deserialized into a map Map<String, Object> deserializedValues = deserializeKeyValueSpanString(keyValueStr); // then: the original span and deserialized map values should be exactly the same verifySpanEqualsDeserializedValues(validSpan, deserializedValues); }
@Test public void fromKeyValueString_should_function_properly_when_there_are_null_values() { // given: valid span with null values and key/value string from Span.fromKeyValueString() Span validSpan = Span.generateRootSpanForNewTrace(spanName, null).build(); assertThat(validSpan.getParentSpanId()).isNull(); assertThat(validSpan.getUserId()).isNull(); assertThat(validSpan.getDurationNanos()).isNull(); String keyValStr = SpanParser.convertSpanToKeyValueFormat(validSpan); // when: fromKeyValueString is called Span spanFromKeyValStr = SpanParser.fromKeyValueString(keyValStr); // then: the original span and the fromKeyValueString() span values should be exactly the same verifySpanDeepEquals(spanFromKeyValStr, validSpan, true); }
@Test public void fromJson_should_function_properly_when_there_are_null_values() { // 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: 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); }
@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(); }
@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(); }