diff --git a/dd-java-agent/instrumentation/aws-java-eventbridge-2.0/src/main/java/datadog/trace/instrumentation/aws/v2/eventbridge/EventBridgeInterceptor.java b/dd-java-agent/instrumentation/aws-java-eventbridge-2.0/src/main/java/datadog/trace/instrumentation/aws/v2/eventbridge/EventBridgeInterceptor.java index 9fab55961f4..57891c1035d 100644 --- a/dd-java-agent/instrumentation/aws-java-eventbridge-2.0/src/main/java/datadog/trace/instrumentation/aws/v2/eventbridge/EventBridgeInterceptor.java +++ b/dd-java-agent/instrumentation/aws-java-eventbridge-2.0/src/main/java/datadog/trace/instrumentation/aws/v2/eventbridge/EventBridgeInterceptor.java @@ -8,15 +8,12 @@ import static datadog.trace.core.datastreams.TagsProcessor.TYPE_TAG; import static datadog.trace.instrumentation.aws.v2.eventbridge.TextMapInjectAdapter.SETTER; -import datadog.trace.api.JsonAdapter; import datadog.trace.api.TracePropagationStyle; import datadog.trace.bootstrap.InstanceStore; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.awssdk.core.SdkRequest; @@ -28,7 +25,6 @@ import software.amazon.awssdk.services.eventbridge.model.PutEventsRequestEntry; public class EventBridgeInterceptor implements ExecutionInterceptor { - private final JsonAdapter jsonAdapter; private static final Logger log = LoggerFactory.getLogger(EventBridgeInterceptor.class); public static final ExecutionAttribute SPAN_ATTRIBUTE = @@ -39,10 +35,6 @@ public class EventBridgeInterceptor implements ExecutionInterceptor { private static final String BUS_NAME_KEY = "BusName"; private static final String DATADOG_KEY = "_datadog"; - public EventBridgeInterceptor() { - this.jsonAdapter = AgentTracer.get().getJsonAdapter(); - } - @Override public SdkRequest modifyRequest( Context.ModifyRequest context, ExecutionAttributes executionAttributes) { @@ -58,22 +50,44 @@ public SdkRequest modifyRequest( String eventBusName = entry.eventBusName(); String traceContext = getTraceContextToInject(executionAttributes, eventBusName); - try { - Map detailMap = jsonAdapter.fromJson(entry.detail()); - - // Add new fields and trace context - detailMap.put(SENT_TIMESTAMP_KEY, String.valueOf(startTime)); - detailMap.put(BUS_NAME_KEY, eventBusName); - detailMap.put(DATADOG_KEY, jsonAdapter.fromJson(traceContext)); - - // Modify entry - String modifiedDetail = jsonAdapter.toJson(detailMap); - PutEventsRequestEntry modifiedEntry = entry.toBuilder().detail(modifiedDetail).build(); - modifiedEntries.add(modifiedEntry); - } catch (Exception e) { - log.warn("Failed to process detail JSON for entry: {}. Using original entry.", entry, e); + StringBuilder detailBuilder = new StringBuilder(entry.detail().trim()); + if (detailBuilder.length() == 0) { + detailBuilder.append("{}"); + } + if (detailBuilder.charAt(detailBuilder.length() - 1) != '}') { + log.debug( + "Unable to parse detail JSON. Not injecting trace context into EventBridge payload."); modifiedEntries.add(entry); // Add the original entry without modification + continue; + } + + detailBuilder.setLength(detailBuilder.length() - 1); // Remove the last bracket + if (detailBuilder.length() > 1) { + detailBuilder.append(", "); // Only add a comma if detail is not empty. } + + detailBuilder + .append("\"") + .append(SENT_TIMESTAMP_KEY) + .append("\": \"") + .append(startTime) + .append("\""); + detailBuilder + .append(", \"") + .append(BUS_NAME_KEY) + .append("\": \"") + .append(eventBusName) + .append("\""); + detailBuilder + .append(", \"") + .append(DATADOG_KEY) + .append("\": ") + .append(traceContext) + .append("}"); + + String modifiedDetail = detailBuilder.toString(); + PutEventsRequestEntry modifiedEntry = entry.toBuilder().detail(modifiedDetail).build(); + modifiedEntries.add(modifiedEntry); } return request.toBuilder().entries(modifiedEntries).build(); diff --git a/dd-java-agent/instrumentation/aws-java-eventbridge-2.0/src/test/groovy/EventBridgeClientTest.groovy b/dd-java-agent/instrumentation/aws-java-eventbridge-2.0/src/test/groovy/EventBridgeClientTest.groovy index fc85f1ea3a5..56c9a999f50 100644 --- a/dd-java-agent/instrumentation/aws-java-eventbridge-2.0/src/test/groovy/EventBridgeClientTest.groovy +++ b/dd-java-agent/instrumentation/aws-java-eventbridge-2.0/src/test/groovy/EventBridgeClientTest.groovy @@ -313,4 +313,44 @@ class EventBridgeClientTest extends AgentTestRunner { cleanup: injectSysConfig(GeneralConfig.DATA_STREAMS_ENABLED, "true") } + + def "test behavior with empty detail fields"() { + setup: + sqsClient.purgeQueue { it.queueUrl(testQueueURL) } + + when: + TEST_WRITER.clear() + eventBridgeClient.putEvents { req -> + req.entries( + PutEventsRequestEntry.builder() + .source("com.example") + .detailType("test-empty") + .detail('{}') + .eventBusName(testBusARN) + .build(), + ) + } + + def messages = sqsClient.receiveMessage { it.queueUrl(testQueueURL).maxNumberOfMessages(2).waitTimeSeconds(5) }.messages() + + then: + assertTraces(1) { + trace(1) { + span { + serviceName "java-aws-sdk" + operationName "aws.http" + resourceName "EventBridge.PutEvents" + spanType DDSpanTypes.HTTP_CLIENT + } + } + } + assert messages.size() == 1 + + def message = messages[0] + assert message != null + def emptyDetailBody = new JsonSlurper().parseText(message.body()) + assert emptyDetailBody["detail"]["_datadog"] != null // Datadog context should be injected + assert emptyDetailBody["detail"]["_datadog"]["x-datadog-trace-id"] != null + assert emptyDetailBody["detail"]["_datadog"]["x-datadog-parent-id"] != null + } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java index e171320f981..8da026f224f 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java @@ -25,7 +25,6 @@ import datadog.trace.api.EndpointTracker; import datadog.trace.api.IdGenerationStrategy; import datadog.trace.api.InstrumenterConfig; -import datadog.trace.api.JsonAdapter; import datadog.trace.api.StatsDClient; import datadog.trace.api.TraceConfig; import datadog.trace.api.TracePropagationStyle; @@ -90,7 +89,6 @@ import datadog.trace.core.taginterceptor.RuleFlags; import datadog.trace.core.taginterceptor.TagInterceptor; import datadog.trace.lambda.LambdaHandler; -import datadog.trace.lambda.MoshiJsonAdapter; import datadog.trace.relocate.api.RatelimitedLogger; import datadog.trace.util.AgentTaskScheduler; import java.io.IOException; @@ -238,11 +236,6 @@ public void updatePreferredServiceName(String serviceName) { ServiceNameCollector.get().addService(serviceName); } - @Override - public JsonAdapter getJsonAdapter() { - return new MoshiJsonAdapter(); - } - PropagationTags.Factory getPropagationTagsFactory() { return propagationTagsFactory; } diff --git a/dd-trace-core/src/main/java/datadog/trace/lambda/MoshiJsonAdapter.java b/dd-trace-core/src/main/java/datadog/trace/lambda/MoshiJsonAdapter.java deleted file mode 100644 index 3ed51d8b241..00000000000 --- a/dd-trace-core/src/main/java/datadog/trace/lambda/MoshiJsonAdapter.java +++ /dev/null @@ -1,35 +0,0 @@ -package datadog.trace.lambda; - -import com.squareup.moshi.Moshi; -import datadog.trace.api.JsonAdapter; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -public class MoshiJsonAdapter implements JsonAdapter { - private static final com.squareup.moshi.JsonAdapter ADAPTER = - new Moshi.Builder() - .add(ByteArrayInputStream.class, new ReadFromInputStreamJsonAdapter()) - .add(SkipUnsupportedTypeJsonAdapter.newFactory()) - .build() - .adapter(Object.class); - - @Override - public Map fromJson(String json) throws IOException { - if (json == null || json.isEmpty()) { - return new HashMap<>(); - } - @SuppressWarnings("unchecked") - Map result = (Map) ADAPTER.fromJson(json); - return result != null ? result : new HashMap<>(); - } - - @Override - public String toJson(Map map) { - if (map == null || map.isEmpty()) { - return ""; - } - return ADAPTER.toJson(map); - } -} diff --git a/internal-api/src/main/java/datadog/trace/api/JsonAdapter.java b/internal-api/src/main/java/datadog/trace/api/JsonAdapter.java deleted file mode 100644 index 50686319538..00000000000 --- a/internal-api/src/main/java/datadog/trace/api/JsonAdapter.java +++ /dev/null @@ -1,10 +0,0 @@ -package datadog.trace.api; - -import java.io.IOException; -import java.util.Map; - -public interface JsonAdapter { - Map fromJson(String json) throws IOException; - - String toJson(Map map); -} diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java index 972c854f113..c5ea96a35d4 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java @@ -7,7 +7,6 @@ import datadog.trace.api.DDTraceId; import datadog.trace.api.EndpointCheckpointer; import datadog.trace.api.EndpointTracker; -import datadog.trace.api.JsonAdapter; import datadog.trace.api.TraceConfig; import datadog.trace.api.TracePropagationStyle; import datadog.trace.api.experimental.DataStreamsCheckpointer; @@ -292,8 +291,6 @@ default SpanBuilder buildSpan(CharSequence spanName) { * @param serviceName */ void updatePreferredServiceName(String serviceName); - - JsonAdapter getJsonAdapter(); } public interface SpanBuilder { @@ -528,11 +525,6 @@ public AgentHistogram newHistogram(double relativeAccuracy, int maxNumBins) { public void updatePreferredServiceName(String serviceName) { // no ops } - - @Override - public JsonAdapter getJsonAdapter() { - return null; - } } public static final class BlackholeAgentSpan extends NoopAgentSpan {