Skip to content

Commit 42fd911

Browse files
committed
special case handling to look up traceId in both base64 and hex
1 parent 2d0db5b commit 42fd911

File tree

4 files changed

+283
-14
lines changed

4 files changed

+283
-14
lines changed

astra/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@
152152
<version>${curator.version}</version>
153153
</dependency>
154154

155+
<dependency>
156+
<groupId>commons-codec</groupId>
157+
<artifactId>commons-codec</artifactId>
158+
<version>1.17.0</version>
159+
</dependency>
160+
155161
<!-- Lucene dependencies -->
156162
<dependency>
157163
<groupId>org.apache.lucene</groupId>

astra/src/main/java/com/slack/astra/zipkinApi/TraceFetcher.java

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.time.Instant;
2323
import java.time.temporal.ChronoUnit;
2424
import java.util.ArrayList;
25+
import java.util.Base64;
2526
import java.util.Comparator;
2627
import java.util.HashMap;
2728
import java.util.List;
@@ -30,6 +31,8 @@
3031
import java.util.UUID;
3132
import java.util.concurrent.TimeUnit;
3233
import java.util.regex.Pattern;
34+
import org.apache.commons.codec.binary.Hex;
35+
import org.json.JSONArray;
3336
import org.json.JSONObject;
3437
import org.slf4j.Logger;
3538
import org.slf4j.LoggerFactory;
@@ -88,20 +91,94 @@ private static boolean isDDTraceId(String s) {
8891
return s != null && DIGITS.matcher(s).matches();
8992
}
9093

94+
private record TraceIds(String hex, String base64) {}
95+
96+
private static TraceIds convertTraceId(String traceId) {
97+
if (traceId == null || traceId.isEmpty()) return null;
98+
99+
String hex = null;
100+
String base64Url = null;
101+
102+
// If input is hex → convert to Base64 URL-safe
103+
if (traceId.matches("^[0-9a-fA-F]+$") && traceId.length() % 2 == 0) {
104+
try {
105+
byte[] bytes = Hex.decodeHex(traceId.toCharArray());
106+
hex = traceId.toLowerCase();
107+
base64Url = Base64.getUrlEncoder().encodeToString(bytes);
108+
return new TraceIds(hex, base64Url);
109+
} catch (Exception ignored) {
110+
return null;
111+
}
112+
}
113+
114+
// Otherwise, assume Base64-URL → convert to hex
115+
try {
116+
byte[] bytes = Base64.getUrlDecoder().decode(traceId);
117+
hex = Hex.encodeHexString(bytes);
118+
base64Url = traceId;
119+
return new TraceIds(hex, base64Url);
120+
} catch (Exception ignored) {
121+
return null;
122+
}
123+
}
124+
125+
private static JSONObject singleTermQuery(String traceFieldName, String traceId) {
126+
JSONObject traceObject = new JSONObject();
127+
traceObject.put(traceFieldName, traceId);
128+
JSONObject queryJson = new JSONObject();
129+
queryJson.put("term", traceObject);
130+
return queryJson;
131+
}
132+
133+
private static JSONObject buildTraceIdQuery(
134+
String traceFieldName, String traceId, String convertedId) {
135+
// When there is no converted ID, return the original simple term query
136+
if (convertedId == null) {
137+
return singleTermQuery(traceFieldName, traceId);
138+
}
139+
140+
// When we have a convertedId, do traceId OR convertedId
141+
JSONArray shouldArray = new JSONArray();
142+
// term for original traceId
143+
shouldArray.put(singleTermQuery(traceFieldName, traceId));
144+
// term for convertedId
145+
shouldArray.put(singleTermQuery(traceFieldName, convertedId));
146+
JSONObject boolQuery = new JSONObject();
147+
boolQuery.put("should", shouldArray);
148+
boolQuery.put("minimum_should_match", 1);
149+
150+
JSONObject queryJson = new JSONObject();
151+
queryJson.put("bool", boolQuery);
152+
153+
return queryJson;
154+
}
155+
91156
private Result fetchTraceResult(
92157
String traceId,
93158
Optional<Long> startTimeEpochMs,
94159
Optional<Long> endTimeEpochMs,
95160
Optional<Integer> maxSpans,
96161
Optional<Boolean> userRequest,
97162
Optional<Long> dataFreshnessInSeconds,
98-
Optional<Boolean> ddTraceIdEnabled)
163+
Optional<Boolean> ddTraceIdEnabled,
164+
Optional<Boolean> searchByHexAndBase64)
99165
throws IOException {
100166

101167
String traceFieldName = "trace_id";
102-
// if trace id looks like dd_trace_id, then use dd_trace_id field to search
168+
String convertedId = null;
169+
// if trace id looks like dd_trace_id, then use dd_trace_id field to search. If true, ignores
170+
// the hex/base64 flag
103171
if (ddTraceIdEnabled.isPresent() && ddTraceIdEnabled.get() && isDDTraceId(traceId)) {
104172
traceFieldName = "dd_trace_id";
173+
} else if (searchByHexAndBase64.isPresent() && searchByHexAndBase64.get()) {
174+
TraceIds traceIds = convertTraceId(traceId);
175+
// to ensure we only cache the same trace once, we use the base64 version as the traceId.
176+
// TraceIds are null
177+
// in the case of being unable to convert, fallback to original traceId
178+
if (traceIds != null) {
179+
traceId = traceIds.base64;
180+
convertedId = traceIds.hex;
181+
}
105182
}
106183

107184
// Log the custom header userRequest value if present
@@ -117,11 +194,9 @@ private Result fetchTraceResult(
117194
}
118195
}
119196

120-
JSONObject traceObject = new JSONObject();
121-
traceObject.put(traceFieldName, traceId);
122-
JSONObject queryJson = new JSONObject();
123-
queryJson.put("term", traceObject);
197+
JSONObject queryJson = buildTraceIdQuery(traceFieldName, traceId, convertedId);
124198
String queryString = queryJson.toString();
199+
LOG.debug("Querying with queryString={}", queryString);
125200

126201
long startTime =
127202
startTimeEpochMs.orElseGet(
@@ -180,7 +255,8 @@ public List<ZipkinSpanResponse> getSpansByTraceId(
180255
Optional<Integer> maxSpans,
181256
Optional<Boolean> userRequest,
182257
Optional<Long> dataFreshnessInSeconds,
183-
Optional<Boolean> ddTraceIdEnabled)
258+
Optional<Boolean> ddTraceIdEnabled,
259+
Optional<Boolean> searchByHexAndBase64)
184260
throws IOException {
185261
Result result =
186262
fetchTraceResult(
@@ -190,7 +266,8 @@ public List<ZipkinSpanResponse> getSpansByTraceId(
190266
maxSpans,
191267
userRequest,
192268
dataFreshnessInSeconds,
193-
ddTraceIdEnabled);
269+
ddTraceIdEnabled,
270+
searchByHexAndBase64);
194271

195272
if (result.isCached()) {
196273
return objectMapper.readValue(result.rawJson, new TypeReference<>() {});
@@ -205,7 +282,8 @@ public String getByTraceId(
205282
Optional<Integer> maxSpans,
206283
Optional<Boolean> userRequest,
207284
Optional<Long> dataFreshnessInSeconds,
208-
Optional<Boolean> ddTraceIdEnabled)
285+
Optional<Boolean> ddTraceIdEnabled,
286+
Optional<Boolean> searchByHexAndBase64)
209287
throws IOException {
210288
Result result =
211289
fetchTraceResult(
@@ -215,7 +293,8 @@ public String getByTraceId(
215293
maxSpans,
216294
userRequest,
217295
dataFreshnessInSeconds,
218-
ddTraceIdEnabled);
296+
ddTraceIdEnabled,
297+
searchByHexAndBase64);
219298

220299
if (result.isCached()) {
221300
return result.rawJson;

astra/src/main/java/com/slack/astra/zipkinApi/ZipkinService.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ public HttpResponse getTraceByTraceId(
7171
@Param("maxSpans") Optional<Integer> maxSpans,
7272
@Header("X-User-Request") Optional<Boolean> userRequest,
7373
@Header("X-Data-Freshness-In-Seconds") Optional<Long> dataFreshnessInSeconds,
74-
@Header("X-DD-TRACE-ID") Optional<Boolean> ddTraceIdEnabled)
74+
@Header("X-DD-TRACE-ID") Optional<Boolean> ddTraceIdEnabled,
75+
@Header("X-E2E-hex-base64") Optional<Boolean> searchByHexAndBase64)
7576
throws IOException {
7677
String output =
7778
this.traceFetcher.getByTraceId(
@@ -81,7 +82,8 @@ public HttpResponse getTraceByTraceId(
8182
maxSpans,
8283
userRequest,
8384
dataFreshnessInSeconds,
84-
ddTraceIdEnabled);
85+
ddTraceIdEnabled,
86+
searchByHexAndBase64);
8587
return HttpResponse.of(HttpStatus.OK, MediaType.JSON_UTF_8, output);
8688
}
8789
}

0 commit comments

Comments
 (0)