diff --git a/astra/src/main/java/com/slack/astra/writer/SpanFormatter.java b/astra/src/main/java/com/slack/astra/writer/SpanFormatter.java index d73a519aeb..b2a5c8c2ea 100644 --- a/astra/src/main/java/com/slack/astra/writer/SpanFormatter.java +++ b/astra/src/main/java/com/slack/astra/writer/SpanFormatter.java @@ -21,6 +21,7 @@ public class SpanFormatter { public static final String DEFAULT_LOG_MESSAGE_TYPE = "INFO"; public static final String DEFAULT_INDEX_NAME = "unknown"; + public static final int DEPTH_LIMIT = 3; public static Timestamp parseDate(String dateStr, Schema.SchemaFieldType type) { Instant instant; @@ -148,14 +149,28 @@ public static List convertKVtoProto( @VisibleForTesting public static List convertKVtoProtoDefault( String key, Object value, Schema.IngestSchema schema) { + return convertKVtoProtoDefault(key, value, schema, DEPTH_LIMIT - 1); + } + + @VisibleForTesting + public static List convertKVtoProtoDefault( + String key, Object value, Schema.IngestSchema schema, int depthLimit) { List tags = new ArrayList<>(); if (value instanceof Map) { - // todo - consider adding a depth param to prevent excessively nested fields + if (depthLimit <= 0) { + // We could encode the value with json instead of toString to make it more friendly to API + // consumers. + // NB: Using BINARY here to ensure it is not indexed. + tags.add(makeTraceKV(key, value.toString(), Schema.SchemaFieldType.BINARY)); + return tags; + } + ((Map) value) .forEach( (key1, value1) -> { List nestedValues = - convertKVtoProtoDefault(String.format("%s.%s", key, key1), value1, schema); + convertKVtoProtoDefault( + String.format("%s.%s", key, key1), value1, schema, depthLimit - 1); tags.addAll(nestedValues); }); } else if (value instanceof String || value instanceof List) { diff --git a/astra/src/test/java/com/slack/astra/schema/SpanFormatterWithSchemaTest.java b/astra/src/test/java/com/slack/astra/schema/SpanFormatterWithSchemaTest.java index a5297c7297..0d290cba16 100644 --- a/astra/src/test/java/com/slack/astra/schema/SpanFormatterWithSchemaTest.java +++ b/astra/src/test/java/com/slack/astra/schema/SpanFormatterWithSchemaTest.java @@ -35,6 +35,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; import org.apache.commons.io.FileUtils; import org.apache.lucene.document.Document; @@ -157,6 +158,53 @@ public void canParseSimpleMapValues() throws Exception { .isEqualTo("subsubvalue1"); } + @Test + @SuppressWarnings("OptionalGetWithoutIsPresent") + public void stopsAtDepthN() throws Exception { + + Function, Map> nestedMap = + (keyPath) -> { + Object value = "value"; + for (String k : keyPath.reversed()) { + value = Map.of(k, value); + } + return (Map) value; + }; + Map head = nestedMap.apply(List.of("a", "b", "c", "d", "e", "f")); + List list; + list = SpanFormatter.convertKVtoProtoDefault("init", head, schema); + assertThat(list.size()).isEqualTo(1); + assertThat(list.getFirst().getKey()).isEqualTo("init.a.b"); + assertThat(list.getFirst().getVBinary().toStringUtf8()).isEqualTo("{c={d={e={f=value}}}}"); + + list = SpanFormatter.convertKVtoProtoDefault("init", head, schema, 0); + assertThat(list.size()).isEqualTo(1); + assertThat(list.getFirst().getKey()).isEqualTo("init"); + + list = SpanFormatter.convertKVtoProtoDefault("init", head, schema, 1); + assertThat(list.size()).isEqualTo(1); + assertThat(list.getFirst().getKey()).isEqualTo("init.a"); + + list = SpanFormatter.convertKVtoProtoDefault("init", head, schema, 2); + assertThat(list.size()).isEqualTo(1); + assertThat(list.getFirst().getKey()).isEqualTo("init.a.b"); + + list = SpanFormatter.convertKVtoProtoDefault("init", head, schema, 6); + assertThat(list.size()).isEqualTo(1); + assertThat(list.getFirst().getKey()).isEqualTo("init.a.b.c.d.e.f"); + + list = + SpanFormatter.convertKVtoProtoDefault("init", nestedMap.apply(List.of("a", "b")), schema); + assertThat(list.size()).isEqualTo(1); + assertThat(list.getFirst().getKey()).isEqualTo("init.a.b"); + + list = + SpanFormatter.convertKVtoProtoDefault( + "init", nestedMap.apply(List.of("a", "b", "c")), schema); + assertThat(list.size()).isEqualTo(1); + assertThat(list.getFirst().getKey()).isEqualTo("init.a.b"); + } + @Test public void parseIndexRequestWithNullValues() throws Exception { final File schemaFile =