diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV1.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV1.java index 0cde12dddd8..8e43e43fa67 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV1.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/ddagent/TraceMapperV1.java @@ -1,6 +1,7 @@ package datadog.trace.common.writer.ddagent; import static datadog.communication.http.OkHttpUtils.msgpackRequestBodyOf; +import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; @@ -11,6 +12,7 @@ import datadog.communication.serialization.Writable; import datadog.communication.serialization.msgpack.MsgPackWriter; import datadog.environment.JavaVirtualMachine; +import datadog.json.JsonReader; import datadog.trace.api.Config; import datadog.trace.api.DDTags; import datadog.trace.api.DDTraceId; @@ -203,12 +205,12 @@ private void encodeSpanLinks( private void encodeSpanEvents(Writable writable, int fieldId, Object eventsObject) { writable.writeInt(fieldId); - if (!(eventsObject instanceof List) || ((List) eventsObject).isEmpty()) { + List events = parseSpanEvents(eventsObject); + if (events.isEmpty()) { writable.startArray(0); return; } - List events = (List) eventsObject; int encodableCount = 0; for (Object event : events) { if (isEncodableSpanEvent(event)) { @@ -237,6 +239,23 @@ private void encodeSpanEvents(Writable writable, int fieldId, Object eventsObjec } } + private List parseSpanEvents(Object eventsObject) { + if (eventsObject instanceof List) { + return (List) eventsObject; + } + if (eventsObject instanceof CharSequence) { + try (JsonReader reader = new JsonReader(eventsObject.toString())) { + Object events = reader.nextValue(); + if (events instanceof List) { + return (List) events; + } + } catch (IOException e) { + log.debug("Failed to parse span events from JSON", e); + } + } + return emptyList(); + } + private boolean isEncodableSpanEvent(Object event) { if (!(event instanceof Map)) { return false; diff --git a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java index 5a4b025e8fe..839abe61a39 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/DDSpanSerializationTest.java @@ -1,5 +1,6 @@ package datadog.trace.core; +import static datadog.trace.api.DDTags.SPAN_EVENTS; import static datadog.trace.api.DDTags.SPAN_LINKS; import static datadog.trace.api.TracePropagationStyle.DATADOG; import static datadog.trace.api.config.GeneralConfig.EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED; @@ -400,6 +401,52 @@ void serializeTraceWithSpanLinksAsStructuredLinksOnlyV1() throws Exception { tracer.close(); } + @Test + void serializeTraceWithJsonSpanEventsV1() throws Exception { + CoreTracer tracer = tracerBuilder().writer(new ListWriter()).build(); + DDSpanContext context = createSpanContext(tracer, Collections.emptyMap(), null, true, true, 1); + context.setTag( + SPAN_EVENTS, + "[{\"time_unix_nano\":1234567890,\"name\":\"event.one\"," + + "\"attributes\":{\"str\":\"value\",\"int\":42,\"double\":12.5," + + "\"bool\":true,\"arr\":[\"x\",7,2.5,false]}}," + + "{\"time_unix_nano\":1234567891,\"name\":\"event.two\"}]"); + DDSpan span = DDSpan.create("test", 0, context, null); + + V1PayloadReader.V1Span payload = V1PayloadReader.readFirstSpan(serializeV1Payload(span)); + + assertFalse(payload.getAttributes().containsKey(SPAN_EVENTS)); + assertEquals(2, payload.getEvents().size()); + V1PayloadReader.V1SpanEvent firstEvent = payload.getEvents().get(0); + assertEquals(1234567890L, firstEvent.getTimeUnixNano()); + assertEquals("event.one", firstEvent.getName()); + assertEquals("value", firstEvent.getAttributes().get("str")); + assertEquals(42L, firstEvent.getAttributes().get("int")); + assertEquals(12.5d, firstEvent.getAttributes().get("double")); + assertEquals(true, firstEvent.getAttributes().get("bool")); + assertEquals(Arrays.asList("x", 7L, 2.5d, false), firstEvent.getAttributes().get("arr")); + + V1PayloadReader.V1SpanEvent secondEvent = payload.getEvents().get(1); + assertEquals(1234567891L, secondEvent.getTimeUnixNano()); + assertEquals("event.two", secondEvent.getName()); + assertEquals(Collections.emptyMap(), secondEvent.getAttributes()); + tracer.close(); + } + + @Test + void serializeTraceWithMalformedJsonSpanEventsAsEmptyV1() throws Exception { + CoreTracer tracer = tracerBuilder().writer(new ListWriter()).build(); + DDSpanContext context = createSpanContext(tracer, Collections.emptyMap(), null, true, true, 1); + context.setTag(SPAN_EVENTS, "[{"); + DDSpan span = DDSpan.create("test", 0, context, null); + + V1PayloadReader.V1Span payload = V1PayloadReader.readFirstSpan(serializeV1Payload(span)); + + assertFalse(payload.getAttributes().containsKey(SPAN_EVENTS)); + assertEquals(Collections.emptyList(), payload.getEvents()); + tracer.close(); + } + @TableTest({ "protocol | rootFirst", "v0.4 | true ",