/** * The given header from the given request as a span ID (or parent span ID), with the generateNewSpanIdIfNotFoundInRequest argument determining whether this method returns null * or a new random span ID from {@link TraceAndSpanIdGenerator#generateId()} when the request doesn't contain that header. */ protected static String getSpanIdFromRequest(RequestWithHeaders request, String headerName, boolean generateNewSpanIdIfNotFoundInRequest) { String spanIdString = getHeaderWithAttributeAsBackup(request, headerName); if (spanIdString == null) return generateNewSpanIdIfNotFoundInRequest ? TraceAndSpanIdGenerator.generateId() : null; return spanIdString; }
traceId = TraceAndSpanIdGenerator.generateId(); spanId = TraceAndSpanIdGenerator.generateId();
/** * Similar to {@link #startRequestWithRootSpan(String)} but takes in an optional {@code userId} to populate the returned {@link Span#getUserId()}. * If {@code userId} is null then this method is equivalent to calling {@link #startRequestWithRootSpan(String)}. If you have parent span info then you should call * {@link #startRequestWithChildSpan(Span, String)} or {@link #startRequestWithSpanInfo(String, String, String, boolean, String, SpanPurpose)} instead). * The newly created root span will have a span purpose of {@link SpanPurpose#SERVER}. * <p/> * <b>WARNING:</b> This wipes out any existing spans on the span stack for this thread and starts fresh, therefore this should only be called at the request's * entry point when it's expected that the span stack should be empty. If you need to start a child span in the middle of a request somewhere then you should call * {@link #startSubSpan(String, SpanPurpose)} instead. * * @param spanName - The span name to use for the new span - should never be null. * @param userId - The ID of the user that should be associated with the {@link Span} - can be null. * @return The new span (which is now also the current one that will be returned by {@link #getCurrentSpan()}). */ public Span startRequestWithRootSpan(String spanName, String userId) { boolean sampleable = isNextRootSpanSampleable(); String traceId = TraceAndSpanIdGenerator.generateId(); return doNewRequestSpan(traceId, null, spanName, sampleable, userId, SpanPurpose.SERVER); }
@Test public void generateId_should_not_generate_duplicate_ids_over_reasonable_number_of_attempts() throws Exception { // given Set<String> ids = new HashSet<>(); int numAttempts = 1000000; // when for (int i = 0; i < numAttempts; i++) { ids.add(TraceAndSpanIdGenerator.generateId()); } // then assertThat(ids.size()).isEqualTo(numAttempts); }
@Test public void generateId_should_return_16_char_length_string_that_can_be_parsed_into_a_long_when_interpreted_as_a_64_bit_unsigned_hex_long() { Set<Character> charactersFromIds = new HashSet<>(); for (int i = 0; i < 10000; i++) { // given: String ID value generated by generateId() final String idVal = TraceAndSpanIdGenerator.generateId(); // then: that ID has 16 characters and can be interpreted as a 64 bit unsigned hex long and parsed into a Java long primitive assertThat(idVal).hasSize(16); Throwable parseException = catchThrowable(new ThrowableAssert.ThrowingCallable() { @Override public void call() throws Throwable { new BigInteger(idVal, 16).longValue(); } }); assertThat(parseException).isNull(); // Store the characters we see in this ID in a set so we can verify later that we've only ever seen hex-compatible characters. for (char c : idVal.toCharArray()) { charactersFromIds.add(c); } } // We should have only run into lowercase hex characters, and given how many IDs we generated we should have hit all the lowercase hex characters. assertThat(charactersFromIds).containsOnly('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'); }
@DataProvider(value = { "true", "false" }) @Test public void doFilterInternal_should_use_getInitialSpanName_for_span_name( boolean parentSpanExists ) throws ServletException, IOException { // given RequestTracingFilter filterSpy = spy(getBasicFilter()); filterSpy.tagAndNamingStrategy = tagAndNamingStrategy; filterSpy.tagAndNamingAdapter = tagAndNamingAdapterMock; String expectedSpanName = UUID.randomUUID().toString(); doReturn(expectedSpanName).when(filterSpy).getInitialSpanName( any(HttpServletRequest.class), any(HttpTagAndSpanNamingStrategy.class), any(HttpTagAndSpanNamingAdapter.class) ); if (parentSpanExists) { given(requestMock.getHeader(TraceHeaders.TRACE_ID)).willReturn(TraceAndSpanIdGenerator.generateId()); given(requestMock.getHeader(TraceHeaders.SPAN_ID)).willReturn(TraceAndSpanIdGenerator.generateId()); } // when filterSpy.doFilterInternal(requestMock, responseMock, spanCapturingFilterChain); // then assertThat(spanCapturingFilterChain.captureSpanCopyAtTimeOfDoFilter).isNotNull(); assertThat(spanCapturingFilterChain.captureSpanCopyAtTimeOfDoFilter.getSpanName()).isEqualTo(expectedSpanName); verify(filterSpy).getInitialSpanName(requestMock, tagAndNamingStrategy, tagAndNamingAdapterMock); }
String traceId = generateId(); String spanId = generateId(); long startTimeEpochMicros = Math.abs(random.nextLong()); long durationNanos = Math.abs(random.nextLong());
/** * @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(); }
String traceId = generateId(); String spanId = generateId(); String parentId = generateId(); long startTimeEpochMicros = Math.abs(random.nextLong()); long durationNanos = Math.abs(random.nextLong());
String expectedSanitizedSpanId = "15afe1fe8fe52ed3"; // SHA 256 hash of original span ID, take 16 chars String badParentSpanId = TraceAndSpanIdGenerator.generateId().toUpperCase(); String expectedSanitizedParentSpanId = badParentSpanId.toLowerCase();
String traceId = generateId(); String spanId = generateId(); String parentId = generateId(); long startTimeEpochMicros = Math.abs(random.nextLong()); long durationNanos = Math.abs(random.nextLong());
String traceId = generateId(); String spanId = generateId(); long startTimeEpochMicros = Math.abs(random.nextLong()); long durationNanos = Math.abs(random.nextLong());
traceId = TraceAndSpanIdGenerator.generateId(); String warningMsg = "Generating a dummy Trace ID for response header because a real Trace ID did not exist. This "
traceId = TraceAndSpanIdGenerator.generateId(); String warningMsg = "Generating a dummy Trace ID for response header because a real Trace ID did not exist. This "
@Test public void doFilterInternal_should_use_user_id_from_parent_span_info_if_present_in_request_headers( ) throws ServletException, IOException { // given: filter and request that has parent span info RequestTracingFilter spyFilter = spy(getBasicFilter()); given(requestMock.getHeader(ALT_USER_ID_HEADER_KEY)).willReturn("testUserId"); Span parentSpan = Span.newBuilder("someParentSpan", null) .withParentSpanId(TraceAndSpanIdGenerator.generateId()) .withSampleable(false) .withUserId("someUser") .build(); given(requestMock.getHeader(TraceHeaders.TRACE_ID)).willReturn(parentSpan.getTraceId()); given(requestMock.getHeader(TraceHeaders.SPAN_ID)).willReturn(parentSpan.getSpanId()); given(requestMock.getHeader(TraceHeaders.PARENT_SPAN_ID)).willReturn(parentSpan.getParentSpanId()); given(requestMock.getHeader(TraceHeaders.SPAN_NAME)).willReturn(parentSpan.getSpanName()); given(requestMock.getHeader(TraceHeaders.TRACE_SAMPLED)).willReturn(String.valueOf(parentSpan.isSampleable())); given(requestMock.getServletPath()).willReturn("/some/path"); given(requestMock.getMethod()).willReturn("GET"); // when: doFilterInternal is called spyFilter.doFilterInternal(requestMock, responseMock, spanCapturingFilterChain); // then: the span that is created should use the parent span info as its parent assertThat(spanCapturingFilterChain.capturedSpan).isNotNull(); Span newSpan = spanCapturingFilterChain.capturedSpan; assertThat(newSpan.getUserId()).isEqualTo("testUserId"); }
.withParentSpanId(TraceAndSpanIdGenerator.generateId()) .withSampleable(false) .withUserId("someUser")