88import static datadog .remoteconfig .Capabilities .CAPABILITY_APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION ;
99import static datadog .remoteconfig .Capabilities .CAPABILITY_APM_TRACING_ENABLE_EXCEPTION_REPLAY ;
1010import static datadog .remoteconfig .Capabilities .CAPABILITY_APM_TRACING_ENABLE_LIVE_DEBUGGING ;
11+ import static datadog .remoteconfig .Capabilities .CAPABILITY_APM_TRACING_MULTICONFIG ;
1112import static datadog .remoteconfig .Capabilities .CAPABILITY_APM_TRACING_SAMPLE_RATE ;
1213import static datadog .remoteconfig .Capabilities .CAPABILITY_APM_TRACING_SAMPLE_RULES ;
1314import static datadog .remoteconfig .Capabilities .CAPABILITY_APM_TRACING_TRACING_ENABLED ;
3435import java .io .ByteArrayInputStream ;
3536import java .io .IOException ;
3637import java .util .Collections ;
38+ import java .util .Comparator ;
3739import java .util .HashMap ;
3840import java .util .Iterator ;
3941import java .util .List ;
@@ -74,7 +76,8 @@ public void start(Config config, SharedCommunicationObjects sco) {
7476 | CAPABILITY_APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION
7577 | CAPABILITY_APM_TRACING_ENABLE_EXCEPTION_REPLAY
7678 | CAPABILITY_APM_TRACING_ENABLE_CODE_ORIGIN
77- | CAPABILITY_APM_TRACING_ENABLE_LIVE_DEBUGGING );
79+ | CAPABILITY_APM_TRACING_ENABLE_LIVE_DEBUGGING
80+ | CAPABILITY_APM_TRACING_MULTICONFIG );
7881 }
7982 stopPolling = new Updater ().register (config , configPoller );
8083 }
@@ -87,14 +90,17 @@ public void stop() {
8790
8891 final class Updater implements ProductListener {
8992 private final JsonAdapter <ConfigOverrides > CONFIG_OVERRIDES_ADAPTER ;
93+ private final JsonAdapter <LibConfig > LIB_CONFIG_ADAPTER ;
9094 private final JsonAdapter <TracingSamplingRule > TRACE_SAMPLING_RULE ;
9195
9296 {
9397 Moshi MOSHI = new Moshi .Builder ().add (new TracingSamplingRulesAdapter ()).build ();
9498 CONFIG_OVERRIDES_ADAPTER = MOSHI .adapter (ConfigOverrides .class );
99+ LIB_CONFIG_ADAPTER = MOSHI .adapter (LibConfig .class );
95100 TRACE_SAMPLING_RULE = MOSHI .adapter (TracingSamplingRule .class );
96101 }
97102
103+ private final Map <String , ConfigOverrides > configs = new HashMap <>();
98104 private boolean receivedOverrides = false ;
99105
100106 public Runnable register (Config config , ConfigurationPoller poller ) {
@@ -115,27 +121,46 @@ public void accept(ConfigKey configKey, byte[] content, PollingRateHinter hinter
115121 Okio .buffer (Okio .source (new ByteArrayInputStream (content ))));
116122
117123 if (null != overrides && null != overrides .libConfig ) {
118- receivedOverrides = true ;
119- applyConfigOverrides (checkConfig (overrides .libConfig ));
124+ configs .put (configKey .getConfigId (), overrides );
120125 if (log .isDebugEnabled ()) {
121126 log .debug (
122- "Applied APM_TRACING overrides: {}" , CONFIG_OVERRIDES_ADAPTER .toJson (overrides ));
127+ "Applied APM_TRACING overrides: {} - priority: {}" ,
128+ CONFIG_OVERRIDES_ADAPTER .toJson (overrides ),
129+ overrides .getOverridePriority ());
123130 }
124131 } else {
125132 log .debug ("No APM_TRACING overrides" );
126133 }
127134 }
128135
129136 @ Override
130- public void remove (ConfigKey configKey , PollingRateHinter hinter ) {}
137+ public void remove (ConfigKey configKey , PollingRateHinter hinter ) {
138+ configs .remove (configKey .getConfigId ());
139+ }
131140
132141 @ Override
133142 public void commit (PollingRateHinter hinter ) {
134- if (!receivedOverrides ) {
143+ // sort configs by override priority
144+ List <LibConfig > sortedConfigs =
145+ configs .values ().stream ()
146+ .sorted (Comparator .comparingInt (ConfigOverrides ::getOverridePriority ).reversed ())
147+ .map (config -> config .libConfig )
148+ .collect (Collectors .toList ());
149+
150+ LibConfig mergedConfig = LibConfig .mergeLibConfigs (sortedConfigs );
151+
152+ if (mergedConfig != null ) {
153+ // apply merged config
154+ if (log .isDebugEnabled ()) {
155+ log .debug (
156+ "Applying merged APM_TRACING config: {}" , LIB_CONFIG_ADAPTER .toJson (mergedConfig ));
157+ }
158+ applyConfigOverrides (checkConfig (mergedConfig ));
159+ }
160+
161+ if (sortedConfigs .isEmpty ()) {
135162 removeConfigOverrides ();
136163 log .debug ("Removed APM_TRACING overrides" );
137- } else {
138- receivedOverrides = false ;
139164 }
140165 }
141166
@@ -263,6 +288,77 @@ private Map<String, String> parseTagListToMap(List<String> input) {
263288 static final class ConfigOverrides {
264289 @ Json (name = "lib_config" )
265290 public LibConfig libConfig ;
291+
292+ @ Json (name = "service_target" )
293+ public ServiceTarget serviceTarget ;
294+
295+ @ Json (name = "k8s_target_v2" )
296+ public K8sTargetV2 k8sTargetV2 ;
297+
298+ public int getOverridePriority () {
299+ boolean isSingleEnvironment = isSingleEnvironment ();
300+ boolean isSingleService = isSingleService ();
301+ boolean isClusterTarget = isClusterTarget ();
302+
303+ // Service+ Environment level override - highest priority
304+ if (isSingleEnvironment && isSingleService ) {
305+ return 5 ;
306+ }
307+
308+ if (isSingleService ) {
309+ return 4 ;
310+ }
311+
312+ if (isSingleEnvironment ) {
313+ return 3 ;
314+ }
315+
316+ if (isClusterTarget ) {
317+ return 2 ;
318+ }
319+
320+ // Org level override - lowest priority
321+ return 1 ;
322+ }
323+
324+ // allEnvironments = serviceTarget is null or serviceTarget.env is null or '*'
325+ public boolean isSingleEnvironment () {
326+ return serviceTarget != null && serviceTarget .env != null && !"*" .equals (serviceTarget .env );
327+ }
328+
329+ public boolean isSingleService () {
330+ return serviceTarget != null
331+ && serviceTarget .service != null
332+ && !"*" .equals (serviceTarget .service );
333+ }
334+
335+ public boolean isClusterTarget () {
336+ return k8sTargetV2 != null ;
337+ }
338+ }
339+
340+ static final class ServiceTarget {
341+ @ Json (name = "service" )
342+ public String service ;
343+
344+ @ Json (name = "env" )
345+ public String env ;
346+ }
347+
348+ static final class K8sTargetV2 {
349+ @ Json (name = "cluster_targets" )
350+ public List <ClusterTarget > clusterTargets ;
351+ }
352+
353+ static final class ClusterTarget {
354+ @ Json (name = "cluster_name" )
355+ public String clusterName ;
356+
357+ @ Json (name = "enabled" )
358+ public Boolean enabled ;
359+
360+ @ Json (name = "enabled_namespaces" )
361+ public List <String > enabledNamespaces ;
266362 }
267363
268364 static final class LibConfig {
@@ -307,6 +403,71 @@ static final class LibConfig {
307403
308404 @ Json (name = "live_debugging_enabled" )
309405 public Boolean liveDebuggingEnabled ;
406+
407+ /**
408+ * Merges a list of LibConfig objects by taking the first non-null value for each field.
409+ *
410+ * @param configs the list of LibConfig objects to merge
411+ * @return a merged LibConfig object, or null if the input list is null or empty
412+ */
413+ public static LibConfig mergeLibConfigs (List <LibConfig > configs ) {
414+ if (configs == null || configs .isEmpty ()) {
415+ return null ;
416+ }
417+
418+ LibConfig merged = new LibConfig ();
419+
420+ for (LibConfig config : configs ) {
421+ if (config == null ) {
422+ continue ;
423+ }
424+
425+ if (merged .tracingEnabled == null ) {
426+ merged .tracingEnabled = config .tracingEnabled ;
427+ }
428+ if (merged .debugEnabled == null ) {
429+ merged .debugEnabled = config .debugEnabled ;
430+ }
431+ if (merged .runtimeMetricsEnabled == null ) {
432+ merged .runtimeMetricsEnabled = config .runtimeMetricsEnabled ;
433+ }
434+ if (merged .logsInjectionEnabled == null ) {
435+ merged .logsInjectionEnabled = config .logsInjectionEnabled ;
436+ }
437+ if (merged .dataStreamsEnabled == null ) {
438+ merged .dataStreamsEnabled = config .dataStreamsEnabled ;
439+ }
440+ if (merged .serviceMapping == null ) {
441+ merged .serviceMapping = config .serviceMapping ;
442+ }
443+ if (merged .headerTags == null ) {
444+ merged .headerTags = config .headerTags ;
445+ }
446+ if (merged .traceSampleRate == null ) {
447+ merged .traceSampleRate = config .traceSampleRate ;
448+ }
449+ if (merged .tracingTags == null ) {
450+ merged .tracingTags = config .tracingTags ;
451+ }
452+ if (merged .tracingSamplingRules == null ) {
453+ merged .tracingSamplingRules = config .tracingSamplingRules ;
454+ }
455+ if (merged .dynamicInstrumentationEnabled == null ) {
456+ merged .dynamicInstrumentationEnabled = config .dynamicInstrumentationEnabled ;
457+ }
458+ if (merged .exceptionReplayEnabled == null ) {
459+ merged .exceptionReplayEnabled = config .exceptionReplayEnabled ;
460+ }
461+ if (merged .codeOriginEnabled == null ) {
462+ merged .codeOriginEnabled = config .codeOriginEnabled ;
463+ }
464+ if (merged .liveDebuggingEnabled == null ) {
465+ merged .liveDebuggingEnabled = config .liveDebuggingEnabled ;
466+ }
467+ }
468+
469+ return merged ;
470+ }
310471 }
311472
312473 /** Holds the raw JSON string and the parsed rule data. */
0 commit comments