Skip to content

Commit 070e7d8

Browse files
bm1549claude
andcommitted
Derive OTLP export flags in Config and honor MultiWriter
Move the three derived booleans out of StatusLogger.toJson and into Config accessors: isOtlpTracesExportEnabled, isOtlpMetricsExportEnabled, and isOtlpLogsExportEnabled. Every other field toJson emits forwards a Config getter, so keeping the conjunctions inline made this method the only place in the file that derives a value it writes. Fix otlp_traces_export_enabled. It compared dd.writer.type to OtlpWriter for equality, so MultiWriter:OtlpWriter,DDAgentWriter reported false even though WriterFactory builds a real OtlpWriter for it. isOtlpTracesExportEnabled now mirrors WriterFactory's dispatch: a writer type that does not start with MultiWriter is compared to OtlpWriter directly, and one that does has every literal MultiWriter: removed before the remainder is split on commas and each sub-type compared. Removing every occurrence rather than a single leading prefix matches what MultiWriter itself does, so a repeated prefix such as MultiWriter:DDAgentWriter,MultiWriter:OtlpWriter resolves the same way in both places. Drop the two comment blocks in StatusLogger explaining writer precedence and span-metrics routing, and the three in StatusLoggerTest. StatusLogger carries no comments anywhere else, and the reasoning belongs in the commit message and pull request, which both record it. Expand StatusLoggerTest to 13 tests and assert with assertFalse and assertTrue instead of assertEquals against boolean literals. The new cases pin the writer type grammar: a MultiWriter list that includes OtlpWriter, one that excludes it, a repeated MultiWriter prefix, a padded sub-type, a sub-type that only prefixes OtlpWriter, a comma-separated list without the MultiWriter prefix, and a TraceStructureWriter value that WriterFactory dispatches before it ever reaches the MultiWriter branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 6d1065b commit 070e7d8

3 files changed

Lines changed: 126 additions & 52 deletions

File tree

dd-trace-core/src/main/java/datadog/trace/core/StatusLogger.java

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import static datadog.trace.api.Config.isDatadogProfilerEnablementOverridden;
44
import static datadog.trace.api.Config.isDatadogProfilerSafeInCurrentEnvironment;
5-
import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.OTLP_WRITER_TYPE;
65
import static java.util.concurrent.TimeUnit.MILLISECONDS;
76

87
import com.squareup.moshi.JsonAdapter;
@@ -160,19 +159,11 @@ public void toJson(JsonWriter writer, Config config) throws IOException {
160159
writer.name("data_streams_transaction_extractors");
161160
writer.value(config.getDataStreamsTransactionExtractors());
162161
writer.name("otlp_traces_export_enabled");
163-
// Report the effective trace writer, not just the exporter selection: an explicit
164-
// dd.writer.type override wins over dd.trace.otel.exporter=otlp in WriterFactory, so traces are
165-
// only exported over OTLP when the resolved writer is actually the OtlpWriter.
166-
writer.value(OTLP_WRITER_TYPE.equals(config.getWriterType()));
162+
writer.value(config.isOtlpTracesExportEnabled());
167163
writer.name("otlp_metrics_export_enabled");
168-
// Client-side trace span-metrics are exported over OTLP whenever span metrics are enabled
169-
// (MetricsAggregatorFactory routes them through OtlpStatsMetricWriter), independently of the
170-
// OTel metrics signal, so OR that path in alongside OTel-API metrics export.
171-
writer.value(
172-
(config.isMetricsOtelEnabled() && config.isMetricsOtlpExporterEnabled())
173-
|| config.isOtelTracesSpanMetricsEnabled());
164+
writer.value(config.isOtlpMetricsExportEnabled());
174165
writer.name("otlp_logs_export_enabled");
175-
writer.value(config.isLogsOtelEnabled() && config.isLogsOtlpExporterEnabled());
166+
writer.value(config.isOtlpLogsExportEnabled());
176167

177168
writer.name("app_logs_collection_enabled");
178169
writer.value(config.isAppLogsCollectionEnabled());
Lines changed: 97 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,141 @@
11
package datadog.trace.core;
22

3-
import static org.junit.jupiter.api.Assertions.assertEquals;
3+
import static datadog.trace.api.config.OtlpConfig.LOGS_OTEL_ENABLED;
4+
import static datadog.trace.api.config.OtlpConfig.LOGS_OTEL_EXPORTER;
5+
import static datadog.trace.api.config.OtlpConfig.METRICS_OTEL_ENABLED;
6+
import static datadog.trace.api.config.OtlpConfig.METRICS_OTEL_EXPORTER;
7+
import static datadog.trace.api.config.OtlpConfig.OTEL_TRACES_SPAN_METRICS_ENABLED;
8+
import static datadog.trace.api.config.OtlpConfig.TRACE_OTEL_EXPORTER;
9+
import static datadog.trace.api.config.TracerConfig.WRITER_TYPE;
10+
import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.DD_AGENT_WRITER_TYPE;
11+
import static org.junit.jupiter.api.Assertions.assertFalse;
12+
import static org.junit.jupiter.api.Assertions.assertTrue;
413

514
import com.squareup.moshi.Moshi;
615
import datadog.trace.api.Config;
7-
import datadog.trace.api.config.OtlpConfig;
8-
import datadog.trace.api.config.TracerConfig;
916
import datadog.trace.test.junit.utils.config.WithConfig;
17+
import datadog.trace.test.util.DDJavaSpecification;
1018
import java.io.IOException;
1119
import java.util.Map;
1220
import java.util.concurrent.TimeUnit;
1321
import org.junit.jupiter.api.Test;
1422
import org.junit.jupiter.api.Timeout;
1523

1624
@Timeout(value = 10, unit = TimeUnit.SECONDS)
17-
public class StatusLoggerTest extends DDCoreJavaSpecification {
25+
public class StatusLoggerTest extends DDJavaSpecification {
1826

1927
@Test
2028
void otlpExportDisabledByDefault() throws IOException {
21-
Map<String, Object> startupLog = startupLog(Config.get());
29+
Map<String, Object> startupLog = startupLog();
2230

23-
assertEquals(false, startupLog.get("otlp_traces_export_enabled"));
24-
assertEquals(false, startupLog.get("otlp_metrics_export_enabled"));
25-
assertEquals(false, startupLog.get("otlp_logs_export_enabled"));
31+
assertFalse(flag(startupLog, "otlp_traces_export_enabled"));
32+
assertFalse(flag(startupLog, "otlp_metrics_export_enabled"));
33+
assertFalse(flag(startupLog, "otlp_logs_export_enabled"));
2634
}
2735

2836
@Test
29-
@WithConfig(key = OtlpConfig.TRACE_OTEL_EXPORTER, value = "otlp")
30-
@WithConfig(key = OtlpConfig.METRICS_OTEL_ENABLED, value = "true")
31-
@WithConfig(key = OtlpConfig.METRICS_OTEL_EXPORTER, value = "otlp")
32-
@WithConfig(key = OtlpConfig.LOGS_OTEL_ENABLED, value = "true")
33-
@WithConfig(key = OtlpConfig.LOGS_OTEL_EXPORTER, value = "otlp")
37+
@WithConfig(key = TRACE_OTEL_EXPORTER, value = "otlp")
38+
@WithConfig(key = METRICS_OTEL_ENABLED, value = "true")
39+
@WithConfig(key = METRICS_OTEL_EXPORTER, value = "otlp")
40+
@WithConfig(key = LOGS_OTEL_ENABLED, value = "true")
41+
@WithConfig(key = LOGS_OTEL_EXPORTER, value = "otlp")
3442
void otlpExportEnabledWhenConfigured() throws IOException {
35-
Map<String, Object> startupLog = startupLog(Config.get());
43+
Map<String, Object> startupLog = startupLog();
3644

37-
assertEquals(true, startupLog.get("otlp_traces_export_enabled"));
38-
assertEquals(true, startupLog.get("otlp_metrics_export_enabled"));
39-
assertEquals(true, startupLog.get("otlp_logs_export_enabled"));
45+
assertTrue(flag(startupLog, "otlp_traces_export_enabled"));
46+
assertTrue(flag(startupLog, "otlp_metrics_export_enabled"));
47+
assertTrue(flag(startupLog, "otlp_logs_export_enabled"));
4048
}
4149

4250
@Test
43-
@WithConfig(key = OtlpConfig.TRACE_OTEL_EXPORTER, value = "otlp")
44-
@WithConfig(key = OtlpConfig.METRICS_OTEL_EXPORTER, value = "otlp")
45-
@WithConfig(key = OtlpConfig.LOGS_OTEL_EXPORTER, value = "otlp")
51+
@WithConfig(key = TRACE_OTEL_EXPORTER, value = "otlp")
52+
@WithConfig(key = METRICS_OTEL_EXPORTER, value = "otlp")
53+
@WithConfig(key = LOGS_OTEL_EXPORTER, value = "otlp")
4654
void metricsAndLogsRequireOtelSignalEnabled() throws IOException {
47-
// The OTLP exporter is selected for every signal, but the metrics and logs OTel signals are
48-
// left disabled, so only trace export should be reported as enabled.
49-
Map<String, Object> startupLog = startupLog(Config.get());
55+
Map<String, Object> startupLog = startupLog();
5056

51-
assertEquals(true, startupLog.get("otlp_traces_export_enabled"));
52-
assertEquals(false, startupLog.get("otlp_metrics_export_enabled"));
53-
assertEquals(false, startupLog.get("otlp_logs_export_enabled"));
57+
assertTrue(flag(startupLog, "otlp_traces_export_enabled"));
58+
assertFalse(flag(startupLog, "otlp_metrics_export_enabled"));
59+
assertFalse(flag(startupLog, "otlp_logs_export_enabled"));
5460
}
5561

5662
@Test
57-
@WithConfig(key = OtlpConfig.TRACE_OTEL_EXPORTER, value = "otlp")
58-
@WithConfig(key = TracerConfig.WRITER_TYPE, value = "DDAgentWriter")
63+
@WithConfig(key = TRACE_OTEL_EXPORTER, value = "otlp")
64+
@WithConfig(key = WRITER_TYPE, value = DD_AGENT_WRITER_TYPE)
5965
void tracesNotExportedWhenWriterTypeOverridesOtlpExporter() throws IOException {
60-
// The OTLP trace exporter is selected, but an explicit dd.writer.type override wins in
61-
// WriterFactory, so the effective writer is the DDAgentWriter and traces are not exported over
62-
// OTLP.
63-
Map<String, Object> startupLog = startupLog(Config.get());
66+
assertFalse(flag(startupLog(), "otlp_traces_export_enabled"));
67+
}
68+
69+
@Test
70+
@WithConfig(key = WRITER_TYPE, value = "MultiWriter:OtlpWriter,DDAgentWriter")
71+
void tracesExportedWhenMultiWriterIncludesOtlpWriter() throws IOException {
72+
assertTrue(flag(startupLog(), "otlp_traces_export_enabled"));
73+
}
74+
75+
@Test
76+
@WithConfig(key = WRITER_TYPE, value = "MultiWriter:DDAgentWriter,MultiWriter:OtlpWriter")
77+
void tracesExportedWhenMultiWriterPrefixRepeats() throws IOException {
78+
assertTrue(flag(startupLog(), "otlp_traces_export_enabled"));
79+
}
80+
81+
@Test
82+
@WithConfig(key = WRITER_TYPE, value = "MultiWriter:LoggingWriter,DDAgentWriter")
83+
void tracesNotExportedWhenMultiWriterExcludesOtlpWriter() throws IOException {
84+
assertFalse(flag(startupLog(), "otlp_traces_export_enabled"));
85+
}
86+
87+
@Test
88+
@WithConfig(key = WRITER_TYPE, value = "MultiWriter: OtlpWriter")
89+
void tracesNotExportedWhenMultiWriterSubTypeIsPadded() throws IOException {
90+
assertFalse(flag(startupLog(), "otlp_traces_export_enabled"));
91+
}
92+
93+
@Test
94+
@WithConfig(key = WRITER_TYPE, value = "MultiWriter:OtlpWriterExtra")
95+
void tracesNotExportedWhenMultiWriterSubTypeOnlyPrefixesOtlpWriter() throws IOException {
96+
assertFalse(flag(startupLog(), "otlp_traces_export_enabled"));
97+
}
98+
99+
@Test
100+
@WithConfig(key = WRITER_TYPE, value = "DDAgentWriter,OtlpWriter")
101+
void tracesNotExportedWhenCommaSeparatedWithoutMultiWriterPrefix() throws IOException {
102+
assertFalse(flag(startupLog(), "otlp_traces_export_enabled"));
103+
}
64104

65-
assertEquals(false, startupLog.get("otlp_traces_export_enabled"));
105+
@Test
106+
@WithConfig(key = WRITER_TYPE, value = "TraceStructureWriter:/tmp/out,OtlpWriter")
107+
void tracesNotExportedWhenTraceStructureWriterTakesPrecedence() throws IOException {
108+
assertFalse(flag(startupLog(), "otlp_traces_export_enabled"));
66109
}
67110

68111
@Test
69-
@WithConfig(key = OtlpConfig.OTEL_TRACES_SPAN_METRICS_ENABLED, value = "true")
112+
@WithConfig(key = OTEL_TRACES_SPAN_METRICS_ENABLED, value = "true")
70113
void metricsExportedWhenSpanMetricsEnabled() throws IOException {
71-
// Span metrics are exported over OTLP via OtlpStatsMetricWriter whenever span metrics are
72-
// enabled, independently of the OTel metrics signal, so metrics export should be reported.
73-
Map<String, Object> startupLog = startupLog(Config.get());
114+
assertTrue(flag(startupLog(), "otlp_metrics_export_enabled"));
115+
}
74116

75-
assertEquals(true, startupLog.get("otlp_metrics_export_enabled"));
117+
@Test
118+
@WithConfig(key = METRICS_OTEL_ENABLED, value = "true")
119+
@WithConfig(key = METRICS_OTEL_EXPORTER, value = "otlp")
120+
@WithConfig(key = OTEL_TRACES_SPAN_METRICS_ENABLED, value = "false")
121+
void metricsExportedWhenOtelMetricsSignalEnabledWithoutSpanMetrics() throws IOException {
122+
assertTrue(flag(startupLog(), "otlp_metrics_export_enabled"));
76123
}
77124

78125
@SuppressWarnings("unchecked")
79-
private static Map<String, Object> startupLog(Config config) throws IOException {
126+
private static Map<String, Object> startupLog() throws IOException {
80127
String json =
81-
new Moshi.Builder().add(new StatusLogger()).build().adapter(Config.class).toJson(config);
128+
new Moshi.Builder()
129+
.add(new StatusLogger())
130+
.build()
131+
.adapter(Config.class)
132+
.toJson(Config.get());
82133
return (Map<String, Object>) new Moshi.Builder().build().adapter(Object.class).fromJson(json);
83134
}
135+
136+
private static boolean flag(Map<String, Object> startupLog, String name) {
137+
Object value = startupLog.get(name);
138+
assertTrue(value instanceof Boolean, name + " should be a boolean, was " + value);
139+
return (Boolean) value;
140+
}
84141
}

internal-api/src/main/java/datadog/trace/api/Config.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,7 @@
727727
import static datadog.trace.api.config.TracerConfig.WRITER_LINKS_INJECT;
728728
import static datadog.trace.api.config.TracerConfig.WRITER_TYPE;
729729
import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY;
730+
import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.MULTI_WRITER_TYPE;
730731
import static datadog.trace.bootstrap.instrumentation.api.WriterConstants.OTLP_WRITER_TYPE;
731732
import static datadog.trace.util.CollectionUtils.tryMakeImmutableList;
732733
import static datadog.trace.util.CollectionUtils.tryMakeImmutableSet;
@@ -828,6 +829,9 @@ public class Config {
828829
private static final int MAX_CODE_COVERAGE_FLAGS = 32;
829830

830831
private static final Pattern COLON = Pattern.compile(":");
832+
private static final Pattern COMMA = Pattern.compile(",");
833+
private static final Pattern MULTI_WRITER_PREFIX =
834+
Pattern.compile(MULTI_WRITER_TYPE + ":", Pattern.LITERAL);
831835

832836
// Historical conflating-Batch size; used to translate TRACER_METRICS_MAX_PENDING (configured in
833837
// legacy batch units) into the new per-SpanSnapshot inbox capacity.
@@ -5577,6 +5581,10 @@ public boolean isLogsOtlpExporterEnabled() {
55775581
return "otlp".equalsIgnoreCase(logsOtelExporter);
55785582
}
55795583

5584+
public boolean isOtlpLogsExportEnabled() {
5585+
return isLogsOtelEnabled() && isLogsOtlpExporterEnabled();
5586+
}
5587+
55805588
public int getLogsOtelInterval() {
55815589
return logsOtelInterval;
55825590
}
@@ -5621,6 +5629,11 @@ public boolean isMetricsOtlpExporterEnabled() {
56215629
return "otlp".equalsIgnoreCase(metricsOtelExporter);
56225630
}
56235631

5632+
public boolean isOtlpMetricsExportEnabled() {
5633+
return (isMetricsOtelEnabled() && isMetricsOtlpExporterEnabled())
5634+
|| isOtelTracesSpanMetricsEnabled();
5635+
}
5636+
56245637
public boolean isMetricsOtelExperimentalEnabled() {
56255638
return metricsOtelExperimentalEnabled;
56265639
}
@@ -5677,6 +5690,19 @@ public boolean isTraceOtlpExporterEnabled() {
56775690
return "otlp".equalsIgnoreCase(traceOtelExporter);
56785691
}
56795692

5693+
public boolean isOtlpTracesExportEnabled() {
5694+
if (!writerType.startsWith(MULTI_WRITER_TYPE)) {
5695+
return OTLP_WRITER_TYPE.equals(writerType);
5696+
}
5697+
String multiWriterConfig = MULTI_WRITER_PREFIX.matcher(writerType).replaceAll("");
5698+
for (String subWriterType : COMMA.split(multiWriterConfig)) {
5699+
if (OTLP_WRITER_TYPE.equals(subWriterType)) {
5700+
return true;
5701+
}
5702+
}
5703+
return false;
5704+
}
5705+
56805706
public String getOtlpTracesEndpoint() {
56815707
return otlpTracesEndpoint;
56825708
}

0 commit comments

Comments
 (0)