From 02473f7c1876a5a0e887987c87d88fac2b39e158 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Thu, 15 Sep 2016 21:12:33 +0800 Subject: [PATCH] Tolerate reads of 128 bit X-B3-TraceId (#408) 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. --- .../springframework/cloud/sleuth/Span.java | 26 ++++++++++++++----- .../cloud/sleuth/SpanTests.java | 10 +++++++ .../TraceChannelInterceptorTests.java | 14 ++++++++++ .../web/HttpServletRequestExtractorTests.java | 17 +++++++++++- 4 files changed, 60 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 3cbc67531..4a152355d 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,30 @@ public class Span { } /** - * 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 new IllegalArgumentException("Malformed id"); + + // 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 new IllegalArgumentException("Malformed id"); + } } + return result; } @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 1021a6213..bed930500 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 @@ -52,6 +52,16 @@ public class SpanTests { 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 37e173ebc..2ab5c63af 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 @@ -275,6 +275,20 @@ public class TraceChannelInterceptorTests implements MessageHandler { 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 fc660f2ad..fe1afc5df 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 class HttpServletRequestExtractorTests { 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)); + } +}