diff --git a/span-normalizer/span-normalizer/src/main/java/org/hypertrace/core/spannormalizer/jaeger/SpanDropFilter.java b/span-normalizer/span-normalizer/src/main/java/org/hypertrace/core/spannormalizer/jaeger/SpanDropFilter.java index ff4460b21..ee803dc5f 100644 --- a/span-normalizer/span-normalizer/src/main/java/org/hypertrace/core/spannormalizer/jaeger/SpanDropFilter.java +++ b/span-normalizer/span-normalizer/src/main/java/org/hypertrace/core/spannormalizer/jaeger/SpanDropFilter.java @@ -10,6 +10,7 @@ public enum Operator { EQ("EQ"), NEQ("NEQ"), EXISTS("EXISTS"), + NOT_EXISTS("NOT_EXISTS"), CONTAINS("CONTAINS"); private final String value; diff --git a/span-normalizer/span-normalizer/src/main/java/org/hypertrace/core/spannormalizer/jaeger/SpanFilter.java b/span-normalizer/span-normalizer/src/main/java/org/hypertrace/core/spannormalizer/jaeger/SpanFilter.java index 90c0b3975..015bb4019 100644 --- a/span-normalizer/span-normalizer/src/main/java/org/hypertrace/core/spannormalizer/jaeger/SpanFilter.java +++ b/span-normalizer/span-normalizer/src/main/java/org/hypertrace/core/spannormalizer/jaeger/SpanFilter.java @@ -50,6 +50,7 @@ public class SpanFilter { private static final String COMMA = ","; private static final String COLON = ":"; + private static final String OPERATION_NAME = "ht.operation.name"; private List>> spanDropCriterion = Collections.emptyList(); private List> spanDropFilters = Collections.emptyList(); @@ -129,7 +130,7 @@ public boolean shouldDropSpan( return true; } - if (anySpanDropFiltersMatch(spanDropFilters, tags, processTags)) { + if (anySpanDropFiltersMatch(span, spanDropFilters, tags, processTags)) { if (LOG.isDebugEnabled() && DROPPED_SPANS_RATE_LIMITER.tryAcquire()) { LOG.debug("Dropping span: [{}] with drop filters: [{}]", span, spanDropFilters.toString()); } @@ -189,6 +190,7 @@ private boolean isRootExitSpan( } private boolean anySpanDropFiltersMatch( + JaegerSpanInternalModel.Span span, List> spanDropFilters, Map tags, Map processTags) { @@ -198,11 +200,12 @@ private boolean anySpanDropFiltersMatch( andFilters.stream() .allMatch( filter -> - matchSpanDropFilter(filter, tags) - || matchSpanDropFilter(filter, processTags))); + matchSpanOperationName(filter, span.getOperationName()) + || matchTagsSpanDropFilter(filter, tags) + || matchProcessTagsSpanDropFilter(filter, processTags))); } - private boolean matchSpanDropFilter( + private boolean matchTagsSpanDropFilter( SpanDropFilter filter, Map tags) { switch (filter.getOperator()) { case EQ: @@ -216,6 +219,47 @@ private boolean matchSpanDropFilter( && StringUtils.contains(tags.get(filter.getTagKey()).getVStr(), filter.getTagValue()); case EXISTS: return tags.containsKey(filter.getTagKey()); + case NOT_EXISTS: + return !tags.isEmpty() && !tags.containsKey(filter.getTagKey()); + default: + return false; + } + } + + private boolean matchProcessTagsSpanDropFilter( + SpanDropFilter filter, Map tags) { + switch (filter.getOperator()) { + case EQ: + return tags.containsKey(filter.getTagKey()) + && StringUtils.equals(tags.get(filter.getTagKey()).getVStr(), filter.getTagValue()); + case NEQ: + return tags.containsKey(filter.getTagKey()) + && !StringUtils.equals(tags.get(filter.getTagKey()).getVStr(), filter.getTagValue()); + case CONTAINS: + return tags.containsKey(filter.getTagKey()) + && StringUtils.contains(tags.get(filter.getTagKey()).getVStr(), filter.getTagValue()); + case EXISTS: + return tags.containsKey(filter.getTagKey()); + default: + return false; + } + } + + private boolean matchSpanOperationName(SpanDropFilter filter, String operationName) { + switch (filter.getOperator()) { + case EQ: + return filter.getTagKey().equals(OPERATION_NAME) + && StringUtils.equals(operationName, filter.getTagValue()); + case NEQ: + return filter.getTagKey().equals(OPERATION_NAME) + && !StringUtils.equals(operationName, filter.getTagValue()); + case CONTAINS: + return filter.getTagKey().equals(OPERATION_NAME) + && StringUtils.contains(operationName, filter.getTagValue()); + case EXISTS: + return filter.getTagKey().equals(OPERATION_NAME) && !StringUtils.isEmpty(operationName); + case NOT_EXISTS: + return filter.getTagKey().equals(OPERATION_NAME) && StringUtils.isEmpty(operationName); default: return false; } diff --git a/span-normalizer/span-normalizer/src/test/java/org/hypertrace/core/spannormalizer/SpanNormalizerTest.java b/span-normalizer/span-normalizer/src/test/java/org/hypertrace/core/spannormalizer/SpanNormalizerTest.java index f7e5d7e7e..6276449bd 100644 --- a/span-normalizer/span-normalizer/src/test/java/org/hypertrace/core/spannormalizer/SpanNormalizerTest.java +++ b/span-normalizer/span-normalizer/src/test/java/org/hypertrace/core/spannormalizer/SpanNormalizerTest.java @@ -206,6 +206,82 @@ public void whenJaegerSpansAreProcessedExpectRawSpansToBeOutput() { inputTopic.pipeInput(span4); assertTrue(outputTopic.isEmpty()); + + // pipe in one more span which match one of spanDropFilters (operation_name, and span.kind not + // exists) + Span span5 = + Span.newBuilder() + .setSpanId(ByteString.copyFrom("4".getBytes())) + .setTraceId(ByteString.copyFrom("trace-4".getBytes())) + .setOperationName("/api/") + .addTags( + JaegerSpanInternalModel.KeyValue.newBuilder() + .setKey("jaeger.servicename") + .setVStr(SERVICE_NAME) + .build()) + .addTags( + JaegerSpanInternalModel.KeyValue.newBuilder() + .setKey("grpc1.url1") + .setVStr("xyz") + .build()) + .build(); + + inputTopic.pipeInput(span5); + assertTrue(outputTopic.isEmpty()); + + // pipe in one more span which does not match one of spanDropFilters (operation_name, and + // span.kind not + // exists) + Span span6 = + Span.newBuilder() + .setSpanId(ByteString.copyFrom("6".getBytes())) + .setTraceId(ByteString.copyFrom("trace-6".getBytes())) + .setOperationName("/api-should-be-there/") + .addTags( + JaegerSpanInternalModel.KeyValue.newBuilder() + .setKey("jaeger.servicename") + .setVStr(SERVICE_NAME) + .build()) + .addTags( + JaegerSpanInternalModel.KeyValue.newBuilder() + .setKey("grapc.url1") + .setVStr("xyz") + .build()) + .build(); + + inputTopic.pipeInput(span6); + KeyValue span6KV = outputTopic.readKeyValue(); + assertEquals("__default", kv1.key.getTenantId()); + assertEquals( + HexUtils.getHex(ByteString.copyFrom("trace-6".getBytes()).toByteArray()), + HexUtils.getHex(span6KV.key.getTraceId().array())); + + // pipe in one more span which does not match one of spanDropFilters (operation_name, and + // span.kind not + // exists) + Span span7 = + Span.newBuilder() + .setSpanId(ByteString.copyFrom("7".getBytes())) + .setTraceId(ByteString.copyFrom("trace-7".getBytes())) + .setOperationName("/api/") + .addTags( + JaegerSpanInternalModel.KeyValue.newBuilder() + .setKey("jaeger.servicename") + .setVStr(SERVICE_NAME) + .build()) + .addTags( + JaegerSpanInternalModel.KeyValue.newBuilder() + .setKey("span.kind") + .setVStr("client") + .build()) + .build(); + + inputTopic.pipeInput(span7); + KeyValue span7KV = outputTopic.readKeyValue(); + assertEquals("__default", kv1.key.getTenantId()); + assertEquals( + HexUtils.getHex(ByteString.copyFrom("trace-7".getBytes()).toByteArray()), + HexUtils.getHex(span7KV.key.getTraceId().array())); } @Test diff --git a/span-normalizer/span-normalizer/src/test/resources/configs/span-normalizer/application.conf b/span-normalizer/span-normalizer/src/test/resources/configs/span-normalizer/application.conf index c4e46e53e..7c5cb7518 100644 --- a/span-normalizer/span-normalizer/src/test/resources/configs/span-normalizer/application.conf +++ b/span-normalizer/span-normalizer/src/test/resources/configs/span-normalizer/application.conf @@ -48,6 +48,18 @@ processor { "operator": "NEQ", "tagValue": "Sent.TestServiceGetEchos" } + ], + [ + { + "tagKey": "ht.operation.name", + "operator": "EQ", + "tagValue": "/api/" + }, + { + "tagKey": "span.kind", + "operator": "NOT_EXISTS", + "tagValue": "" + } ] ] }