From 5b3c206ef00351ea0236732568a8862948ef7209 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Thu, 15 Sep 2016 20:24:29 +0800 Subject: [PATCH] Tolerate reads of 128 bit X-B3-TraceId The first step of transitioning to 128bit `X-B3-TraceId` is tolerantly reading 32 character long ids by throwing away the high bits (any characters left of 16 characters). This allows the tracing system to more flexibly introduce 128bit trace id support in the future. Ex. when `X-B3-TraceId: 463ac35c9f6413ad48485a3953bb6124` is received, parse the lower 64 bits (right most 16 characters ex48485a3953bb6124) as the trace id. See https://github.com/openzipkin/b3-propagation/issues/6 --- .../springframework/cloud/sleuth/Span.java | 31 +++++++++++++++---- .../cloud/sleuth/SpanTests.java | 10 ++++++ .../TraceChannelInterceptorTests.java | 14 +++++++++ .../web/HttpServletRequestExtractorTests.java | 17 +++++++++- 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java index 3cbc67531e..d30cea4fcc 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/Span.java @@ -16,7 +16,6 @@ package org.springframework.cloud.sleuth; -import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -421,15 +420,35 @@ public static String idToHex(long id) { } /** - * Represents hex string as long + * Parses a 1 to 32 character lower-hex string with no prefix into an unsigned long, tossing any + * bits higher than 64. */ public static long hexToId(String hexString) { Assert.hasText(hexString, "Can't convert empty hex string to long"); - try { - return new BigInteger(hexString, 16).longValue(); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Malformed id [" + hexString + "]", e); + int length = hexString.length(); + if (length < 1 || length > 32) throw isntLowerHexLong(hexString); + + // trim off any high bits + int i = length > 16 ? length - 16 : 0; + + long result = 0; + for (; i < length; i++) { + char c = hexString.charAt(i); + result <<= 4; + if (c >= '0' && c <= '9') { + result |= c - '0'; + } else if (c >= 'a' && c <= 'f') { + result |= c - 'a' + 10; + } else { + throw isntLowerHexLong(hexString); + } } + return result; + } + + static NumberFormatException isntLowerHexLong(String lowerHex) { + throw new NumberFormatException( + lowerHex + " should be a 1 to 32 character lower-hex string with no prefix"); } @Override diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java index 8f80bc15a1..fc61600efc 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/SpanTests.java @@ -53,6 +53,16 @@ public void should_convert_hex_string_to_long() throws Exception { then(someLong).isEqualTo(123123L); } + @Test + public void should_convert_lower_64bits_of_hex_string_to_long() throws Exception { + String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; + String lower64Bits = "48485a3953bb6124"; + + long someLong = Span.hexToId(hex128Bits); + + then(someLong).isEqualTo(Span.hexToId(lower64Bits)); + } + @Test(expected = IllegalArgumentException.class) public void should_throw_exception_when_null_string_is_to_be_converted_to_long() throws Exception { Span.hexToId(null); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java index 2a8bb6589b..d00d425781 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/TraceChannelInterceptorTests.java @@ -276,6 +276,20 @@ public void shouldNotTraceIgnoredChannel() { then(TestSpanContextHolder.getCurrentSpan()).isNull(); } + @Test + public void downgrades128bitIdsByDroppingHighBits() { + String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; + String lower64Bits = "48485a3953bb6124"; + this.tracedChannel.send(MessageBuilder.withPayload("hi") + .setHeader(Span.TRACE_ID_NAME, hex128Bits) + .setHeader(Span.SPAN_ID_NAME, Span.idToHex(20L)).build()); + then(this.message).isNotNull(); + + long traceId = Span.hexToId(this.message.getHeaders() + .get(TraceMessageHeaders.TRACE_ID_NAME, String.class)); + then(traceId).isEqualTo(Span.hexToId(lower64Bits)); + } + @Configuration @EnableAutoConfiguration static class App { diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java index fc660f2adc..fe1afc5dfa 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/HttpServletRequestExtractorTests.java @@ -93,4 +93,19 @@ public void should_not_throw_exception_if_parent_id_is_invalid() { then(e).hasMessageContaining("Malformed id"); } } -} \ No newline at end of file + + @Test + public void should_downgrade_128bit_trace_id_by_dropping_high_bits() { + String hex128Bits = "463ac35c9f6413ad48485a3953bb6124"; + String lower64Bits = "48485a3953bb6124"; + + BDDMockito.given(this.request.getHeader(Span.TRACE_ID_NAME)) + .willReturn(hex128Bits); + BDDMockito.given(this.request.getHeader(Span.SPAN_ID_NAME)) + .willReturn(lower64Bits); + + Span span = this.extractor.joinTrace(this.request); + + then(span.getTraceId()).isEqualTo(Span.hexToId(lower64Bits)); + } +}