From 56a55ce07e7151ccc3744ac099d07b7a0151feb1 Mon Sep 17 00:00:00 2001 From: Tommy Ludwig <8924140+shakuzen@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:34:36 +0900 Subject: [PATCH 1/2] Translate Spring 6 HTTP spans to Stackdriver convention From Spring Cloud Sleuth to Spring Framework 6, the tags on HTTP spans changed. This updates the SpanTranslator to handle the Spring 6 tags. --- .../stackdriver/brave/SpanTranslator.java | 22 +++++++++ .../stackdriver/brave/SpanTranslatorTest.java | 45 +++++++++++++++++++ .../stackdriver/brave/TestObjects.java | 21 +++++++++ 3 files changed, 88 insertions(+) diff --git a/encoder-stackdriver-brave/src/main/java/zipkin2/reporter/stackdriver/brave/SpanTranslator.java b/encoder-stackdriver-brave/src/main/java/zipkin2/reporter/stackdriver/brave/SpanTranslator.java index 50b6488..28f5c2c 100644 --- a/encoder-stackdriver-brave/src/main/java/zipkin2/reporter/stackdriver/brave/SpanTranslator.java +++ b/encoder-stackdriver-brave/src/main/java/zipkin2/reporter/stackdriver/brave/SpanTranslator.java @@ -10,11 +10,14 @@ import com.google.devtools.cloudtrace.v2.Span.TimeEvent; import com.google.devtools.cloudtrace.v2.Span.TimeEvents; import com.google.protobuf.Timestamp; + +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.logging.Logger; import static java.util.logging.Level.FINE; +import static zipkin2.reporter.stackdriver.brave.AttributesExtractor.toAttributeValue; import static zipkin2.reporter.stackdriver.brave.SpanUtil.toTruncatableString; /** SpanTranslator converts a Zipkin Span to a Stackdriver Trace Span. */ @@ -33,6 +36,15 @@ final class SpanTranslator { RENAMED_LABELS.put("http.url", "/http/url"); } + private static final Map SPRING6_RENAMED_HTTP_LABELS; + + static { + Map map = new LinkedHashMap<>(); + map.put("status", "/http/status_code"); + map.put("method", "/http/method"); + SPRING6_RENAMED_HTTP_LABELS = Collections.unmodifiableMap(map); + } + private final AttributesExtractor attributesExtractor; SpanTranslator(Tag errorTag) { @@ -79,6 +91,16 @@ Span.Builder translate(Span.Builder spanBuilder, MutableSpan braveSpan) { } spanBuilder.setAttributes(attributesExtractor.extract(braveSpan)); + // Spring 6 HTTP spans need mapping to Stackdriver conventional attribute names + if (braveSpan.name() != null && braveSpan.name().contains("http")) { + braveSpan.tags().forEach((key, value) -> { + if (SPRING6_RENAMED_HTTP_LABELS.containsKey(key)) { + spanBuilder.getAttributesBuilder() + .putAttributeMap(SPRING6_RENAMED_HTTP_LABELS.get(key), toAttributeValue(value)); + } + }); + } + if (braveSpan.annotationCount() > 0) { TimeEvents.Builder events = TimeEvents.newBuilder(); braveSpan.forEachAnnotation(SpanTranslator::addAnnotation, events); diff --git a/encoder-stackdriver-brave/src/test/java/zipkin2/reporter/stackdriver/brave/SpanTranslatorTest.java b/encoder-stackdriver-brave/src/test/java/zipkin2/reporter/stackdriver/brave/SpanTranslatorTest.java index 9904ec5..4ef3101 100644 --- a/encoder-stackdriver-brave/src/test/java/zipkin2/reporter/stackdriver/brave/SpanTranslatorTest.java +++ b/encoder-stackdriver-brave/src/test/java/zipkin2/reporter/stackdriver/brave/SpanTranslatorTest.java @@ -15,6 +15,7 @@ import static zipkin2.reporter.stackdriver.brave.AttributesExtractor.toAttributeValue; import static zipkin2.reporter.stackdriver.brave.SpanUtil.toTruncatableString; import static zipkin2.reporter.stackdriver.brave.TestObjects.clientSpan; +import static zipkin2.reporter.stackdriver.brave.TestObjects.spring6ServerSpan; class SpanTranslatorTest { SpanTranslator spanTranslator = new SpanTranslator(Tags.ERROR); @@ -73,4 +74,48 @@ class SpanTranslatorTest { assertThat(translated.getDisplayName().getValue()).isEqualTo("unknown"); } + + @Test void translate_spring6ServerSpan() { + MutableSpan braveSpan = spring6ServerSpan(); + Span translated = spanTranslator.translate(Span.newBuilder(), braveSpan).build(); + + assertThat(translated) + .isEqualTo( + Span.newBuilder() + .setSpanId(braveSpan.id()) + .setDisplayName(toTruncatableString("http get /test")) + .setStartTime(Timestamp.newBuilder().setSeconds(1472470996).setNanos(199_000_000).build()) + .setEndTime(Timestamp.newBuilder().setSeconds(1472470996).setNanos(406_000_000).build()) + .setAttributes(Span.Attributes.newBuilder() + .putAttributeMap("/agent", toAttributeValue("zipkin-java")) + .putAttributeMap("exception", toAttributeValue("none")) + .putAttributeMap("/http/url", toAttributeValue("/test")) + .putAttributeMap("/http/method", toAttributeValue("GET")) + .putAttributeMap("outcome", toAttributeValue("SUCCESS")) + .putAttributeMap("/http/status_code", toAttributeValue("200")) + .putAttributeMap("uri", toAttributeValue("/test")) + .putAttributeMap("method", toAttributeValue("GET")) + .putAttributeMap("status", toAttributeValue("200")) + .putAttributeMap("/kind", toAttributeValue("server")) + .putAttributeMap("/component", toAttributeValue("backend")) + .putAttributeMap("endpoint.ipv4", toAttributeValue("127.0.0.1")) + .build()) + .setTimeEvents(Span.TimeEvents.newBuilder() + .addTimeEvent(Span.TimeEvent.newBuilder() + .setTime(Timestamp.newBuilder().setSeconds(1472470996).setNanos(238_000_000).build()) + .setAnnotation( + Span.TimeEvent.Annotation.newBuilder() + .setDescription(toTruncatableString("foo")) + .build()) + .build()) + .addTimeEvent(Span.TimeEvent.newBuilder() + .setTime(Timestamp.newBuilder().setSeconds(1472470996).setNanos(403_000_000).build()) + .setAnnotation( + Span.TimeEvent.Annotation.newBuilder() + .setDescription(toTruncatableString("bar")) + .build()) + .build()) + .build()) + .build()); + } } diff --git a/encoder-stackdriver-brave/src/test/java/zipkin2/reporter/stackdriver/brave/TestObjects.java b/encoder-stackdriver-brave/src/test/java/zipkin2/reporter/stackdriver/brave/TestObjects.java index 87d8fda..77a6669 100644 --- a/encoder-stackdriver-brave/src/test/java/zipkin2/reporter/stackdriver/brave/TestObjects.java +++ b/encoder-stackdriver-brave/src/test/java/zipkin2/reporter/stackdriver/brave/TestObjects.java @@ -26,4 +26,25 @@ static MutableSpan clientSpan() { braveSpan.tag("http.path", "/api"); return braveSpan; } + + static MutableSpan spring6ServerSpan() { + MutableSpan braveSpan = new MutableSpan(); + braveSpan.traceId("673adb3c54aa03af9e941a86a679a9d5"); + braveSpan.id("9e941a86a679a9d5"); + braveSpan.name("http get /test"); + braveSpan.kind(brave.Span.Kind.SERVER); + braveSpan.localServiceName("backend"); + braveSpan.localIp("127.0.0.1"); + braveSpan.startTimestamp(1472470996199000L); + braveSpan.finishTimestamp(1472470996199000L + 207000L); + braveSpan.annotate(1472470996238000L, "foo"); + braveSpan.annotate(1472470996403000L, "bar"); + braveSpan.tag("exception", "none"); + braveSpan.tag("http.url", "/test"); + braveSpan.tag("method", "GET"); + braveSpan.tag("outcome", "SUCCESS"); + braveSpan.tag("status", "200"); + braveSpan.tag("uri", "/test"); + return braveSpan; + } } From 8ee1005cc60fc66e5e7434c8333acf308abf2d73 Mon Sep 17 00:00:00 2001 From: Tommy Ludwig <8924140+shakuzen@users.noreply.github.com> Date: Fri, 29 Nov 2024 10:39:43 +0900 Subject: [PATCH 2/2] Translate Spring 6 Zipkin spans to Stackdriver convention Applies the logic necessary for translating Spring 6 made Zipkin spans to Stackdriver spans with the conventional Stackdriver attribute naming. --- .../stackdriver/SpanTranslator.java | 21 +++++++ .../stackdriver/SpanTranslatorTest.java | 55 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/translation-stackdriver/src/main/java/zipkin2/translation/stackdriver/SpanTranslator.java b/translation-stackdriver/src/main/java/zipkin2/translation/stackdriver/SpanTranslator.java index 35a99c1..ec0e7ed 100644 --- a/translation-stackdriver/src/main/java/zipkin2/translation/stackdriver/SpanTranslator.java +++ b/translation-stackdriver/src/main/java/zipkin2/translation/stackdriver/SpanTranslator.java @@ -8,6 +8,7 @@ import com.google.devtools.cloudtrace.v2.Span.TimeEvents; import com.google.protobuf.Timestamp; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -16,6 +17,7 @@ import zipkin2.Span; import static java.util.logging.Level.FINE; +import static zipkin2.translation.stackdriver.AttributesExtractor.toAttributeValue; import static zipkin2.translation.stackdriver.SpanUtil.toTruncatableString; /** SpanTranslator converts a Zipkin Span to a Stackdriver Trace Span. */ @@ -35,6 +37,15 @@ public final class SpanTranslator { ATTRIBUTES_EXTRACTOR = new AttributesExtractor(renamedLabels); } + private static final Map SPRING6_RENAMED_HTTP_LABELS; + + static { + Map map = new LinkedHashMap<>(); + map.put("status", "/http/status_code"); + map.put("method", "/http/method"); + SPRING6_RENAMED_HTTP_LABELS = Collections.unmodifiableMap(map); + } + /** * Convert a Collection of Zipkin Spans into a Collection of Stackdriver Trace Spans. * @@ -104,6 +115,16 @@ public static com.google.devtools.cloudtrace.v2.Span.Builder translate( } spanBuilder.setAttributes(ATTRIBUTES_EXTRACTOR.extract(zipkinSpan)); + // Spring 6 HTTP spans need mapping to Stackdriver conventional attribute names + if (zipkinSpan.name() != null && zipkinSpan.name().contains("http")) { + zipkinSpan.tags().forEach((key, value) -> { + if (SPRING6_RENAMED_HTTP_LABELS.containsKey(key)) { + spanBuilder.getAttributesBuilder() + .putAttributeMap(SPRING6_RENAMED_HTTP_LABELS.get(key), toAttributeValue(value)); + } + }); + } + if (!zipkinSpan.annotations().isEmpty()) { TimeEvents.Builder events = TimeEvents.newBuilder(); for (Annotation annotation : zipkinSpan.annotations()) { diff --git a/translation-stackdriver/src/test/java/zipkin2/translation/stackdriver/SpanTranslatorTest.java b/translation-stackdriver/src/test/java/zipkin2/translation/stackdriver/SpanTranslatorTest.java index 0ddcc15..3b78863 100644 --- a/translation-stackdriver/src/test/java/zipkin2/translation/stackdriver/SpanTranslatorTest.java +++ b/translation-stackdriver/src/test/java/zipkin2/translation/stackdriver/SpanTranslatorTest.java @@ -116,4 +116,59 @@ public class SpanTranslatorTest { assertThat(stackdriverSpans.get(1).getDisplayName().getValue()).isEqualTo("unknown"); assertThat(stackdriverSpans.get(2).getDisplayName().getValue()).isEqualTo("somename"); } + + @Test void translate_spring6ServerSpan() { + Span zipkinSpan = + Span.newBuilder() + .traceId("7180c278b62e8f6a216a2aea45d08fc9") + .id("5b4185666d50f68b") + .name("http get /test") + .kind(Span.Kind.SERVER) + .localEndpoint(Endpoint.newBuilder().serviceName("backend").build()) + .timestamp(1_000_000L) // 1 second after epoch + .duration(123_456L) + .addAnnotation(1_123_000L, "foo") + .putTag("exception", "none") + .putTag("http.url", "/test") + .putTag("method", "GET") + .putTag("outcome", "SUCCESS") + .putTag("status", "200") + .putTag("uri", "/test") + .build(); + + com.google.devtools.cloudtrace.v2.Span + translated = SpanTranslator.translate( + com.google.devtools.cloudtrace.v2.Span.newBuilder(), zipkinSpan).build(); + + assertThat(translated) + .isEqualTo( + com.google.devtools.cloudtrace.v2.Span.newBuilder() + .setSpanId(zipkinSpan.id()) + .setDisplayName(toTruncatableString("http get /test")) + .setStartTime(Timestamp.newBuilder().setSeconds(1).build()) + .setEndTime(Timestamp.newBuilder().setSeconds(1).setNanos(123_456_000).build()) + .setAttributes(com.google.devtools.cloudtrace.v2.Span.Attributes.newBuilder() + .putAttributeMap("/agent", toAttributeValue("zipkin-java")) + .putAttributeMap("exception", toAttributeValue("none")) + .putAttributeMap("/http/url", toAttributeValue("/test")) + .putAttributeMap("/http/method", toAttributeValue("GET")) + .putAttributeMap("outcome", toAttributeValue("SUCCESS")) + .putAttributeMap("/http/status_code", toAttributeValue("200")) + .putAttributeMap("uri", toAttributeValue("/test")) + .putAttributeMap("method", toAttributeValue("GET")) + .putAttributeMap("status", toAttributeValue("200")) + .putAttributeMap("/kind", toAttributeValue("server")) + .putAttributeMap("/component", toAttributeValue("backend")) + .build()) + .setTimeEvents(com.google.devtools.cloudtrace.v2.Span.TimeEvents.newBuilder() + .addTimeEvent(com.google.devtools.cloudtrace.v2.Span.TimeEvent.newBuilder() + .setTime(createTimestamp(1_123_000L)) + .setAnnotation( + com.google.devtools.cloudtrace.v2.Span.TimeEvent.Annotation.newBuilder() + .setDescription(toTruncatableString("foo")) + .build()) + .build()) + .build()) + .build()); + } }