From 141f376d96ee90b547d6e23796c93a7fabf7ce8f Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 24 Apr 2026 12:43:03 -0400 Subject: [PATCH 1/7] Create new OTLP writer type, enabled by default when OTEL_TRACES_EXPORTER=otlp --- internal-api/src/main/java/datadog/trace/api/Config.java | 9 ++++++--- .../bootstrap/instrumentation/api/WriterConstants.java | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index ba69c353761..5276e8aa1bf 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -686,6 +686,7 @@ import static datadog.trace.api.config.TracerConfig.WRITER_LINKS_INJECT; import static datadog.trace.api.config.TracerConfig.WRITER_TYPE; import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY; +import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.OTLP_WRITER_TYPE; import static datadog.trace.util.CollectionUtils.tryMakeImmutableList; import static datadog.trace.util.CollectionUtils.tryMakeImmutableSet; import static datadog.trace.util.ConfigStrings.propertyNameToEnvironmentVariableName; @@ -1433,7 +1434,11 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins integrationSynapseLegacyOperationName = configProvider.getBoolean(INTEGRATION_SYNAPSE_LEGACY_OPERATION_NAME, false); - writerType = configProvider.getString(WRITER_TYPE, DEFAULT_AGENT_WRITER_TYPE); + traceOtelExporter = configProvider.getString(TRACE_OTEL_EXPORTER); + writerType = + configProvider.getString( + WRITER_TYPE, + isTraceOtlpExporterEnabled() ? OTLP_WRITER_TYPE : DEFAULT_AGENT_WRITER_TYPE); boolean isDatadogTraceWriter = !isTraceOtlpExporterEnabled(); injectBaggageAsTagsEnabled = configProvider.getBoolean(WRITER_BAGGAGE_INJECT, isDatadogTraceWriter); @@ -1983,8 +1988,6 @@ private Config(final ConfigProvider configProvider, final InstrumenterConfig ins OtlpConfig.Temporality.class, OtlpConfig.Temporality.DELTA); - traceOtelExporter = configProvider.getString(TRACE_OTEL_EXPORTER); - otlpTimeout = configProvider.getInteger(OTLP_TRACES_TIMEOUT, DEFAULT_OTLP_TRACES_TIMEOUT); if (otlpTimeout < 0) { log.warn("Invalid OTLP traces timeout: {}. The value must be positive", otlpTimeout); diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/WriterConstants.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/WriterConstants.java index 0c283d129f1..724e9296455 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/WriterConstants.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/WriterConstants.java @@ -7,6 +7,7 @@ public final class WriterConstants { public static final String PRINTING_WRITER_TYPE = "PrintingWriter"; public static final String TRACE_STRUCTURE_WRITER_TYPE = "TraceStructureWriter"; public static final String MULTI_WRITER_TYPE = "MultiWriter"; + public static final String OTLP_WRITER_TYPE = "OtlpWriter"; private WriterConstants() {} } From 460833a9e131ac4df78382be2529f203a73e9d3b Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 24 Apr 2026 13:50:45 -0400 Subject: [PATCH 2/7] Implement OtlpPayloadDispatcher.java --- .../common/writer/OtlpPayloadDispatcher.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 dd-trace-core/src/main/java/datadog/trace/common/writer/OtlpPayloadDispatcher.java diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/OtlpPayloadDispatcher.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/OtlpPayloadDispatcher.java new file mode 100644 index 00000000000..62d78c97cfc --- /dev/null +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/OtlpPayloadDispatcher.java @@ -0,0 +1,67 @@ +package datadog.trace.common.writer; + +import datadog.trace.core.CoreSpan; +import datadog.trace.core.DDSpanContext; +import datadog.trace.core.otlp.common.OtlpPayload; +import datadog.trace.core.otlp.common.OtlpSender; +import datadog.trace.core.otlp.trace.OtlpTraceCollector; +import datadog.trace.core.otlp.trace.OtlpTraceProtoCollector; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +final class OtlpPayloadDispatcher implements PayloadDispatcher { + private final OtlpTraceCollector collector; + private final OtlpSender sender; + + OtlpPayloadDispatcher(OtlpSender sender) { + this(sender, OtlpTraceProtoCollector.INSTANCE); + } + + OtlpPayloadDispatcher(OtlpSender sender, OtlpTraceCollector collector) { + this.sender = sender; + this.collector = collector; + } + + @Override + public void addTrace(List> trace) { + List> sampled = null; + for (CoreSpan span : trace) { + if (shouldExport(span)) { + if (sampled == null) { + sampled = new ArrayList<>(trace.size()); + } + sampled.add(span); + } + } + if (sampled != null) { + collector.addTrace(sampled); + } + } + + @Override + public void flush() { + OtlpPayload payload = collector.collectTraces(); + if (payload.getContentLength() > 0) { + sender.send(payload); + } + } + + @Override + public void onDroppedTrace(int spanCount) { + // TODO: surface drop counts via HealthMetrics + } + + @Override + public Collection getApis() { + return Collections.emptyList(); + } + + private static boolean shouldExport(CoreSpan span) { + if (span.samplingPriority() > 0) { + return true; + } + return span.getTag(DDSpanContext.SPAN_SAMPLING_MECHANISM_TAG) != null; + } +} From 69df037f171c99c9ac730dca31f8ef0efa0d54b8 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 24 Apr 2026 14:43:27 -0400 Subject: [PATCH 3/7] Introduce OtlpPayloadDispatcherTest.java --- .../writer/OtlpPayloadDispatcherTest.java | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 dd-trace-core/src/test/java/datadog/trace/common/writer/OtlpPayloadDispatcherTest.java diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/OtlpPayloadDispatcherTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/OtlpPayloadDispatcherTest.java new file mode 100644 index 00000000000..98cd6a96fb0 --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/OtlpPayloadDispatcherTest.java @@ -0,0 +1,137 @@ +package datadog.trace.common.writer; + +import static datadog.trace.core.DDSpanContext.SPAN_SAMPLING_MECHANISM_TAG; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import datadog.trace.core.CoreSpan; +import datadog.trace.core.otlp.common.OtlpPayload; +import datadog.trace.core.otlp.common.OtlpSender; +import datadog.trace.core.otlp.trace.OtlpTraceCollector; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Collections; +import java.util.Deque; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class OtlpPayloadDispatcherTest { + + @Mock OtlpSender sender; + @Mock OtlpTraceCollector collector; + + @Test + @SuppressWarnings({"unchecked", "rawtypes"}) + void sampledTraceForwardsAllSpans() { + OtlpPayloadDispatcher dispatcher = new OtlpPayloadDispatcher(sender, collector); + List> trace = Arrays.asList(sampledSpan(), sampledSpan()); + + dispatcher.addTrace(trace); + + ArgumentCaptor captor = ArgumentCaptor.forClass(List.class); + verify(collector).addTrace(captor.capture()); + assertEquals(trace, captor.getValue()); + verifyNoInteractions(sender); + } + + @Test + void droppedTraceWithoutSingleSpanSamplingForwardsNothing() { + OtlpPayloadDispatcher dispatcher = new OtlpPayloadDispatcher(sender, collector); + + dispatcher.addTrace(Arrays.asList(droppedSpan(), droppedSpan())); + + verifyNoInteractions(collector, sender); + } + + @Test + @SuppressWarnings({"unchecked", "rawtypes"}) + void droppedTraceWithSingleSpanSampledForwardsOnlyThoseSpans() { + OtlpPayloadDispatcher dispatcher = new OtlpPayloadDispatcher(sender, collector); + CoreSpan keep = singleSpanSampledSpan(); + CoreSpan drop1 = droppedSpan(); + CoreSpan drop2 = droppedSpan(); + + dispatcher.addTrace(Arrays.asList(drop1, keep, drop2)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(List.class); + verify(collector).addTrace(captor.capture()); + assertEquals(Collections.singletonList(keep), captor.getValue()); + } + + @Test + void emptyTraceForwardsNothing() { + OtlpPayloadDispatcher dispatcher = new OtlpPayloadDispatcher(sender, collector); + + dispatcher.addTrace(Collections.emptyList()); + + verifyNoInteractions(collector, sender); + } + + @Test + void flushSendsNonEmptyPayload() { + Deque chunks = new ArrayDeque<>(); + chunks.add(new byte[] {1, 2, 3}); + OtlpPayload payload = new OtlpPayload(chunks, 3, "application/x-protobuf"); + when(collector.collectTraces()).thenReturn(payload); + OtlpPayloadDispatcher dispatcher = new OtlpPayloadDispatcher(sender, collector); + + dispatcher.flush(); + + verify(sender).send(payload); + } + + @Test + void flushSkipsEmptyPayload() { + when(collector.collectTraces()).thenReturn(OtlpPayload.EMPTY); + OtlpPayloadDispatcher dispatcher = new OtlpPayloadDispatcher(sender, collector); + + dispatcher.flush(); + + verifyNoInteractions(sender); + } + + @Test + void getApisIsEmpty() { + OtlpPayloadDispatcher dispatcher = new OtlpPayloadDispatcher(sender, collector); + + assertTrue(dispatcher.getApis().isEmpty()); + } + + @Test + void onDroppedTraceDoesNothing() { + OtlpPayloadDispatcher dispatcher = new OtlpPayloadDispatcher(sender, collector); + + dispatcher.onDroppedTrace(5); + + verifyNoInteractions(collector, sender); + } + + private static CoreSpan sampledSpan() { + CoreSpan span = mock(CoreSpan.class); + when(span.samplingPriority()).thenReturn(1); + return span; + } + + private static CoreSpan droppedSpan() { + CoreSpan span = mock(CoreSpan.class); + when(span.samplingPriority()).thenReturn(0); + when(span.getTag(SPAN_SAMPLING_MECHANISM_TAG)).thenReturn(null); + return span; + } + + private static CoreSpan singleSpanSampledSpan() { + CoreSpan span = mock(CoreSpan.class); + when(span.samplingPriority()).thenReturn(0); + when(span.getTag(SPAN_SAMPLING_MECHANISM_TAG)).thenReturn(8); + return span; + } +} From 1d8b3deced7b8b75d0cca48f73e44c96cf5eef2b Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 24 Apr 2026 15:37:27 -0400 Subject: [PATCH 4/7] Implement OtlpWriter --- .../datadog/trace/api/ConfigDefaults.java | 8 +- .../trace/common/writer/OtlpWriter.java | 151 ++++++++++++++++++ 2 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 dd-trace-core/src/main/java/datadog/trace/common/writer/OtlpWriter.java diff --git a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java index fd6c384de21..dd2ae3a82d8 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java @@ -108,12 +108,12 @@ public final class ConfigDefaults { static final int DEFAULT_METRICS_OTEL_TIMEOUT = 7_500; // ms static final int DEFAULT_METRICS_OTEL_CARDINALITY_LIMIT = 2_000; - static final int DEFAULT_OTLP_TRACES_TIMEOUT = 10_000; // ms + public static final int DEFAULT_OTLP_TRACES_TIMEOUT = 10_000; // ms static final String DEFAULT_OTLP_HTTP_METRICS_ENDPOINT = "v1/metrics"; - static final String DEFAULT_OTLP_HTTP_TRACES_ENDPOINT = "v1/traces"; - static final String DEFAULT_OTLP_HTTP_PORT = "4318"; - static final String DEFAULT_OTLP_GRPC_PORT = "4317"; + public static final String DEFAULT_OTLP_HTTP_TRACES_ENDPOINT = "v1/traces"; + public static final String DEFAULT_OTLP_HTTP_PORT = "4318"; + public static final String DEFAULT_OTLP_GRPC_PORT = "4317"; static final int DEFAULT_DOGSTATSD_START_DELAY = 15; // seconds diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/OtlpWriter.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/OtlpWriter.java new file mode 100644 index 00000000000..976a34a863a --- /dev/null +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/OtlpWriter.java @@ -0,0 +1,151 @@ +package datadog.trace.common.writer; + +import static datadog.trace.api.ConfigDefaults.DEFAULT_OTLP_HTTP_PORT; +import static datadog.trace.api.ConfigDefaults.DEFAULT_OTLP_HTTP_TRACES_ENDPOINT; + +import datadog.communication.ddagent.DroppingPolicy; +import datadog.trace.api.config.OtlpConfig; +import datadog.trace.common.sampling.SingleSpanSampler; +import datadog.trace.common.writer.ddagent.Prioritization; +import datadog.trace.core.monitor.HealthMetrics; +import datadog.trace.core.otlp.common.OtlpGrpcSender; +import datadog.trace.core.otlp.common.OtlpHttpSender; +import datadog.trace.core.otlp.common.OtlpSender; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class OtlpWriter extends RemoteWriter { + + private static final int BUFFER_SIZE = 1024; + private static final String TRACES_SIGNAL_PATH = "/" + DEFAULT_OTLP_HTTP_TRACES_ENDPOINT; + private static final String DEFAULT_OTLP_HTTP_ENDPOINT = + "http://localhost:" + DEFAULT_OTLP_HTTP_PORT + TRACES_SIGNAL_PATH; + + public static OtlpWriterBuilder builder() { + return new OtlpWriterBuilder(); + } + + private final OtlpSender sender; + + OtlpWriter( + TraceProcessingWorker worker, + PayloadDispatcher dispatcher, + OtlpSender sender, + HealthMetrics healthMetrics, + int flushTimeout, + TimeUnit flushTimeoutUnit, + boolean alwaysFlush) { + super(worker, dispatcher, healthMetrics, flushTimeout, flushTimeoutUnit, alwaysFlush); + this.sender = sender; + } + + @Override + public void close() { + super.close(); + sender.shutdown(); + } + + public static class OtlpWriterBuilder { + private String endpoint = DEFAULT_OTLP_HTTP_ENDPOINT; + private Map headers = Collections.emptyMap(); + private int timeoutMillis = (int) TimeUnit.SECONDS.toMillis(10); + private OtlpConfig.Protocol protocol = OtlpConfig.Protocol.HTTP_PROTOBUF; + private OtlpConfig.Compression compression = OtlpConfig.Compression.NONE; + private int traceBufferSize = BUFFER_SIZE; + private HealthMetrics healthMetrics = HealthMetrics.NO_OP; + private int flushIntervalMilliseconds = 1000; + private int flushTimeout = 1; + private TimeUnit flushTimeoutUnit = TimeUnit.SECONDS; + private boolean alwaysFlush = false; + private SingleSpanSampler singleSpanSampler; + private OtlpSender sender; + + public OtlpWriterBuilder endpoint(String endpoint) { + this.endpoint = endpoint; + return this; + } + + public OtlpWriterBuilder headers(Map headers) { + this.headers = headers; + return this; + } + + public OtlpWriterBuilder timeoutMillis(int timeoutMillis) { + this.timeoutMillis = timeoutMillis; + return this; + } + + public OtlpWriterBuilder protocol(OtlpConfig.Protocol protocol) { + this.protocol = protocol; + return this; + } + + public OtlpWriterBuilder compression(OtlpConfig.Compression compression) { + this.compression = compression; + return this; + } + + public OtlpWriterBuilder traceBufferSize(int traceBufferSize) { + this.traceBufferSize = traceBufferSize; + return this; + } + + public OtlpWriterBuilder healthMetrics(HealthMetrics healthMetrics) { + this.healthMetrics = healthMetrics; + return this; + } + + public OtlpWriterBuilder flushIntervalMilliseconds(int flushIntervalMilliseconds) { + this.flushIntervalMilliseconds = flushIntervalMilliseconds; + return this; + } + + public OtlpWriterBuilder flushTimeout(int flushTimeout, TimeUnit flushTimeoutUnit) { + this.flushTimeout = flushTimeout; + this.flushTimeoutUnit = flushTimeoutUnit; + return this; + } + + public OtlpWriterBuilder alwaysFlush(boolean alwaysFlush) { + this.alwaysFlush = alwaysFlush; + return this; + } + + public OtlpWriterBuilder spanSamplingRules(SingleSpanSampler singleSpanSampler) { + this.singleSpanSampler = singleSpanSampler; + return this; + } + + OtlpWriterBuilder sender(OtlpSender sender) { + this.sender = sender; + return this; + } + + public OtlpWriter build() { + if (sender == null) { + sender = + protocol == OtlpConfig.Protocol.GRPC + ? new OtlpGrpcSender( + endpoint, TRACES_SIGNAL_PATH, headers, timeoutMillis, compression) + : new OtlpHttpSender( + endpoint, TRACES_SIGNAL_PATH, headers, timeoutMillis, compression); + } + + final OtlpPayloadDispatcher dispatcher = new OtlpPayloadDispatcher(sender); + final TraceProcessingWorker worker = + new TraceProcessingWorker( + traceBufferSize, + healthMetrics, + dispatcher, + DroppingPolicy.DISABLED, + Prioritization.ENSURE_TRACE, + flushIntervalMilliseconds, + TimeUnit.MILLISECONDS, + singleSpanSampler); + + return new OtlpWriter( + worker, dispatcher, sender, healthMetrics, flushTimeout, flushTimeoutUnit, alwaysFlush); + } + } +} From d9649e4ebecc4325fec234103115412e7f48c5a3 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 24 Apr 2026 15:42:42 -0400 Subject: [PATCH 5/7] Introduce OtlpWriterTest.java --- .../trace/common/writer/OtlpWriterTest.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 dd-trace-core/src/test/java/datadog/trace/common/writer/OtlpWriterTest.java diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/OtlpWriterTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/OtlpWriterTest.java new file mode 100644 index 00000000000..d7f915edd6b --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/OtlpWriterTest.java @@ -0,0 +1,63 @@ +package datadog.trace.common.writer; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.verify; + +import datadog.trace.api.config.OtlpConfig; +import datadog.trace.core.otlp.common.OtlpSender; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class OtlpWriterTest { + + @Mock OtlpSender sender; + + @Test + void closeShutsDownSender() { + OtlpWriter writer = OtlpWriter.builder().sender(sender).build(); + writer.start(); + + writer.close(); + + verify(sender).shutdown(); + } + + @Test + void getApisIsEmpty() { + OtlpWriter writer = OtlpWriter.builder().sender(sender).build(); + writer.start(); + try { + assertTrue(writer.getApis().isEmpty()); + } finally { + writer.close(); + } + } + + @Test + void builderRespectsInjectedSenderAcrossProtocols() { + for (OtlpConfig.Protocol protocol : OtlpConfig.Protocol.values()) { + OtlpWriter writer = OtlpWriter.builder().protocol(protocol).sender(sender).build(); + writer.start(); + writer.close(); + } + // One shutdown per writer we built. + verify(sender, org.mockito.Mockito.times(OtlpConfig.Protocol.values().length)).shutdown(); + } + + @Test + void buildWithDefaultsDoesNotThrow() { + // Exercises the default-path sender construction (real OtlpHttpSender) without + // actually sending anything — start/close only. Guards against a default config + // that would otherwise fail at construction time. + assertDoesNotThrow( + () -> { + OtlpWriter writer = OtlpWriter.builder().build(); + writer.start(); + writer.close(); + }); + } +} From bc365d8d36a7c4a436d0c480167aa41ce3c641fc Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 24 Apr 2026 15:46:57 -0400 Subject: [PATCH 6/7] Introduce OtlpWriterCombinedTest.java with a TODO --- .../common/writer/OtlpWriterCombinedTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 dd-trace-core/src/test/java/datadog/trace/common/writer/OtlpWriterCombinedTest.java diff --git a/dd-trace-core/src/test/java/datadog/trace/common/writer/OtlpWriterCombinedTest.java b/dd-trace-core/src/test/java/datadog/trace/common/writer/OtlpWriterCombinedTest.java new file mode 100644 index 00000000000..630dcfe3127 --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/common/writer/OtlpWriterCombinedTest.java @@ -0,0 +1,18 @@ +package datadog.trace.common.writer; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * End-to-end test for {@link OtlpWriter} over a real HTTP transport. + * + *

Mirrors {@code DDAgentWriterCombinedTest} / {@code DDIntakeWriterCombinedTest} — see {@code + * dd-trace-core/src/test/groovy/datadog/trace/common/writer/DDAgentWriterCombinedTest.groovy} for + * the established pattern. + */ +class OtlpWriterCombinedTest { + + @Test + @Disabled("TODO: implement — see class Javadoc for scope") + void endToEndOverHttp() {} +} From 3946a7ed25296dc6f7458da31324d9b5790a4ff5 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 24 Apr 2026 15:58:37 -0400 Subject: [PATCH 7/7] WriterFactory: Add OTLP branch for OtlpWriter; include a TODO for a test --- .../trace/common/writer/WriterFactory.java | 15 ++++++++++++++- .../trace/common/writer/WriterFactoryTest.groovy | 7 +++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/common/writer/WriterFactory.java b/dd-trace-core/src/main/java/datadog/trace/common/writer/WriterFactory.java index 709de57b69f..d01619f839c 100644 --- a/dd-trace-core/src/main/java/datadog/trace/common/writer/WriterFactory.java +++ b/dd-trace-core/src/main/java/datadog/trace/common/writer/WriterFactory.java @@ -5,6 +5,7 @@ import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.DD_INTAKE_WRITER_TYPE; import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.LOGGING_WRITER_TYPE; import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.MULTI_WRITER_TYPE; +import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.OTLP_WRITER_TYPE; import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.PRINTING_WRITER_TYPE; import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.TRACE_STRUCTURE_WRITER_TYPE; import static datadog.trace.common.writer.ddagent.Prioritization.ENSURE_TRACE; @@ -53,6 +54,8 @@ public static Writer createWriter( final HealthMetrics healthMetrics, String configuredType) { + int flushIntervalMilliseconds = Math.round(config.getTraceFlushIntervalSeconds() * 1000); + if (LOGGING_WRITER_TYPE.equals(configuredType)) { return new LoggingWriter(); } else if (PRINTING_WRITER_TYPE.equals(configuredType)) { @@ -63,6 +66,17 @@ public static Writer createWriter( } else if (configuredType.startsWith(MULTI_WRITER_TYPE)) { return new MultiWriter( config, commObjects, sampler, singleSpanSampler, healthMetrics, configuredType); + } else if (OTLP_WRITER_TYPE.equals(configuredType)) { + return OtlpWriter.builder() + .endpoint(config.getOtlpTracesEndpoint()) + .headers(config.getOtlpTracesHeaders()) + .protocol(config.getOtlpTracesProtocol()) + .compression(config.getOtlpTracesCompression()) + .timeoutMillis(config.getOtlpTracesTimeout()) + .healthMetrics(healthMetrics) + .spanSamplingRules(singleSpanSampler) + .flushIntervalMilliseconds(flushIntervalMilliseconds) + .build(); } if (!DD_AGENT_WRITER_TYPE.equals(configuredType) @@ -80,7 +94,6 @@ public static Writer createWriter( "Using 'EnsureTrace' prioritization type. (Do not use this type if your application is running in production mode)"); } - int flushIntervalMilliseconds = Math.round(config.getTraceFlushIntervalSeconds() * 1000); DDAgentFeaturesDiscovery featuresDiscovery = commObjects.featuresDiscovery(config); // The AgentWriter doesn't support the CI Visibility protocol. If CI Visibility is diff --git a/dd-trace-core/src/test/groovy/datadog/trace/common/writer/WriterFactoryTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/common/writer/WriterFactoryTest.groovy index 70124a330d0..6ee3dd9af08 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/common/writer/WriterFactoryTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/common/writer/WriterFactoryTest.groovy @@ -23,6 +23,7 @@ import okhttp3.Protocol import okhttp3.Request import okhttp3.Response import okhttp3.ResponseBody +import spock.lang.Ignore class WriterFactoryTest extends DDSpecification { @@ -165,6 +166,12 @@ class WriterFactoryTest extends DDSpecification { "DDIntakeWriter" | false | false | true | DDIntakeWriter | [DDIntakeApi] } + @Ignore("TODO: implement — verify that writerType=OtlpWriter plus OTLP config getters on Config produce an OtlpWriter with the expected endpoint/protocol/compression/headers/timeout wired through") + def "test writer creation for OtlpWriter"() { + expect: + true + } + Response buildHttpResponse(boolean hasEvpProxy, boolean evpProxySupportsCompression, HttpUrl agentUrl) { def endpoints = [] if (hasEvpProxy && evpProxySupportsCompression) {