2222import java .time .Instant ;
2323import java .time .temporal .ChronoUnit ;
2424import java .util .ArrayList ;
25+ import java .util .Base64 ;
2526import java .util .Comparator ;
2627import java .util .HashMap ;
2728import java .util .List ;
3031import java .util .UUID ;
3132import java .util .concurrent .TimeUnit ;
3233import java .util .regex .Pattern ;
34+ import org .apache .commons .codec .binary .Hex ;
35+ import org .json .JSONArray ;
3336import org .json .JSONObject ;
3437import org .slf4j .Logger ;
3538import 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 ;
0 commit comments