/** * 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; }
/** * @return A newly-generated random 64-bit long encoded as a String <b>UNSIGNED AND IN HEX FORMAT</b> - intended for use as a trace or span ID. * The returned string will have a length of 16 characters (zeroes will be prepended as padding if necessary to * reach a string length of 16). The hex format is necessary to be fully B3 compatible and therefore fully Zipkin compatible * (see <a href="http://zipkin.io/pages/instrumenting.html">http://zipkin.io/pages/instrumenting.html</a>). If you simply want a full 64-bit * random normal *signed* Java long encoded in *decimal* format for other reasons then you can call {@link String#valueOf(long)} and pass in a * long created by {@link #generate64BitRandomLong()}. * <p>You can convert the unsigned hex-encoded longs returned by this method back into normal Java long primitives by passing them to * {@link #unsignedLowerHexStringToLong(String)}. */ public static String generateId() { return longToUnsignedLowerHexString(generate64BitRandomLong()); }
protected Long nullSafeLong(String lowerHexStr) { if (lowerHexStr == null) return null; return TraceAndSpanIdGenerator.unsignedLowerHexStringToLong(lowerHexStr); }
String traceId = generateId(); String spanId = generateId(); String parentId = generateId(); long startTimeEpochMicros = Math.abs(random.nextLong()); long durationNanos = Math.abs(random.nextLong()); assertThat(zipkinSpan.id).isEqualTo(unsignedLowerHexStringToLong(wingtipsSpan.getSpanId())); assertThat(zipkinSpan.name).isEqualTo(wingtipsSpan.getSpanName()); assertThat(zipkinSpan.parentId).isEqualTo(unsignedLowerHexStringToLong(wingtipsSpan.getParentSpanId())); assertThat(zipkinSpan.timestamp).isEqualTo(wingtipsSpan.getSpanStartTimeEpochMicros()); assertThat(zipkinSpan.traceId).isEqualTo(unsignedLowerHexStringToLong(wingtipsSpan.getTraceId())); assertThat(zipkinSpan.duration).isEqualTo(durationMicros); assertThat(zipkinSpan.binaryAnnotations).contains(tagOneAsAnnotation, tagTwoAsAnnotation);
@DataProvider(value = { "0000000000000000 | 0", "0000000000000001 | 1", "ffffffffffffffff | 18446744073709551615", "fffffffffffffffe | 18446744073709551614", "7fae59489091369a | 9200389256962455194", "eb5e7aaefeb92b4f | 16960128138740312911", "d2153abe4c047408 | 15138070311469347848", "9041ee0d07d6c72c | 10394851154681317164", "6470a5ce0e9262f4 | 7237466905610707700", "000003c8a251fb93 | 4160251624339", }, splitBy = "\\|") @Test public void longToUnsignedLowerHexString_and_unsignedLowerHexStringToLong_work_as_expected_for_known_values(String actualHexValue, String actualUnsignedDecimalValue) { // given long actualSignedPrimitive = new BigInteger(actualUnsignedDecimalValue).longValue(); // when String calculatedHexValue = TraceAndSpanIdGenerator.longToUnsignedLowerHexString(actualSignedPrimitive); long calculatedPrimitiveValue = TraceAndSpanIdGenerator.unsignedLowerHexStringToLong(actualHexValue); // then assertThat(calculatedHexValue).isEqualTo(actualHexValue); assertThat(calculatedPrimitiveValue).isEqualTo(actualSignedPrimitive); }
String sanitizedId = TraceAndSpanIdGenerator.longToUnsignedLowerHexString(originalIdAsRawLong); logger.info(SANITIZED_ID_LOG_MSG, originalId, sanitizedId); return sanitizedId;
/** * @return A random long pulled from the full 64-bit random search space (as opposed to the 48 bits of randomness you get from * {@link java.util.Random#nextLong()}). */ public static long generate64BitRandomLong() { byte[] random8Bytes = new byte[8]; random.nextBytes(random8Bytes); return convertBytesToLong(random8Bytes); }
@Test public void generate64BitRandomLong_should_not_generate_duplicate_ids_over_reasonable_number_of_attempts() throws Exception { // given Set<Long> randomLongs = new HashSet<>(); int numAttempts = 1000000; // when for (int i = 0; i < numAttempts; i++) { randomLongs.add(TraceAndSpanIdGenerator.generate64BitRandomLong()); } // then assertThat(randomLongs.size()).isEqualTo(numAttempts); }
String traceId = generateId(); String spanId = generateId(); long startTimeEpochMicros = Math.abs(random.nextLong()); long durationNanos = Math.abs(random.nextLong()); assertThat(zipkinSpan.id).isEqualTo(unsignedLowerHexStringToLong(wingtipsSpan.getSpanId())); assertThat(zipkinSpan.name).isEqualTo(wingtipsSpan.getSpanName()); assertThat(zipkinSpan.parentId).isNull(); assertThat(zipkinSpan.timestamp).isEqualTo(wingtipsSpan.getSpanStartTimeEpochMicros()); assertThat(zipkinSpan.traceId).isEqualTo(unsignedLowerHexStringToLong(wingtipsSpan.getTraceId())); assertThat(zipkinSpan.duration).isEqualTo(durationMicros); assertThat(zipkinSpan.binaryAnnotations).doesNotContain(tagOneAsAnnotation, tagTwoAsAnnotation);
String sanitizedId = TraceAndSpanIdGenerator.longToUnsignedLowerHexString(originalIdAsRawLong); logger.info(SANITIZED_ID_LOG_MSG, originalId, sanitizedId); return sanitizedId;
@Test public void convertBytesToLong_should_work_correctly_for_known_value() { // given: byte[] that maps to known long value final long EXPECTED_LONG_VALUE = 4242424242L; ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / 8); buffer.putLong(EXPECTED_LONG_VALUE); byte[] longAsByteArray = buffer.array(); assertThat(longAsByteArray.length).isEqualTo(8); // when: convertBytesToLong() is called long returnVal = TraceAndSpanIdGenerator.convertBytesToLong(longAsByteArray); // then: the return value is what we expect assertThat(returnVal).isEqualTo(EXPECTED_LONG_VALUE); }
traceId = TraceAndSpanIdGenerator.generateId(); spanId = TraceAndSpanIdGenerator.generateId();
protected Long nullSafeLong(String lowerHexStr, int index) { if (lowerHexStr == null) return null; return TraceAndSpanIdGenerator.unsignedLowerHexStringToLong(lowerHexStr, index); } }
@Test(expected = IllegalArgumentException.class) public void convertBytesToLong_should_explode_if_byte_array_is_less_than_8_bytes() { byte[] badByteArray = new byte[]{0, 0, 0, 0, 0, 0, 1}; assertThat(badByteArray.length).isLessThan(8); TraceAndSpanIdGenerator.convertBytesToLong(badByteArray); fail("Expected IllegalArgumentException but none was thrown"); }
/** * 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); }
protected Long nullSafeLong(String lowerHexStr) { if (lowerHexStr == null) return null; return TraceAndSpanIdGenerator.unsignedLowerHexStringToLong(lowerHexStr); }
@Test(expected = IllegalArgumentException.class) public void convertBytesToLong_should_explode_if_byte_array_is_more_than_8_bytes() { byte[] badByteArray = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 1}; assertThat(badByteArray.length).isGreaterThan(8); TraceAndSpanIdGenerator.convertBytesToLong(badByteArray); fail("Expected IllegalArgumentException but none was thrown"); }
@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); }
protected Long nullSafeLong(String lowerHexStr, int index) { if (lowerHexStr == null) return null; return TraceAndSpanIdGenerator.unsignedLowerHexStringToLong(lowerHexStr, index); } }
@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'); }