8
8
import static datadog .remoteconfig .Capabilities .CAPABILITY_APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION ;
9
9
import static datadog .remoteconfig .Capabilities .CAPABILITY_APM_TRACING_ENABLE_EXCEPTION_REPLAY ;
10
10
import static datadog .remoteconfig .Capabilities .CAPABILITY_APM_TRACING_ENABLE_LIVE_DEBUGGING ;
11
+ import static datadog .remoteconfig .Capabilities .CAPABILITY_APM_TRACING_MULTICONFIG ;
11
12
import static datadog .remoteconfig .Capabilities .CAPABILITY_APM_TRACING_SAMPLE_RATE ;
12
13
import static datadog .remoteconfig .Capabilities .CAPABILITY_APM_TRACING_SAMPLE_RULES ;
13
14
import static datadog .remoteconfig .Capabilities .CAPABILITY_APM_TRACING_TRACING_ENABLED ;
34
35
import java .io .ByteArrayInputStream ;
35
36
import java .io .IOException ;
36
37
import java .util .Collections ;
38
+ import java .util .Comparator ;
37
39
import java .util .HashMap ;
38
40
import java .util .Iterator ;
39
41
import java .util .List ;
@@ -74,7 +76,8 @@ public void start(Config config, SharedCommunicationObjects sco) {
74
76
| CAPABILITY_APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION
75
77
| CAPABILITY_APM_TRACING_ENABLE_EXCEPTION_REPLAY
76
78
| 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 );
78
81
}
79
82
stopPolling = new Updater ().register (config , configPoller );
80
83
}
@@ -87,14 +90,17 @@ public void stop() {
87
90
88
91
final class Updater implements ProductListener {
89
92
private final JsonAdapter <ConfigOverrides > CONFIG_OVERRIDES_ADAPTER ;
93
+ private final JsonAdapter <LibConfig > LIB_CONFIG_ADAPTER ;
90
94
private final JsonAdapter <TracingSamplingRule > TRACE_SAMPLING_RULE ;
91
95
92
96
{
93
97
Moshi MOSHI = new Moshi .Builder ().add (new TracingSamplingRulesAdapter ()).build ();
94
98
CONFIG_OVERRIDES_ADAPTER = MOSHI .adapter (ConfigOverrides .class );
99
+ LIB_CONFIG_ADAPTER = MOSHI .adapter (LibConfig .class );
95
100
TRACE_SAMPLING_RULE = MOSHI .adapter (TracingSamplingRule .class );
96
101
}
97
102
103
+ private final Map <String , ConfigOverrides > configs = new HashMap <>();
98
104
private boolean receivedOverrides = false ;
99
105
100
106
public Runnable register (Config config , ConfigurationPoller poller ) {
@@ -115,27 +121,46 @@ public void accept(ConfigKey configKey, byte[] content, PollingRateHinter hinter
115
121
Okio .buffer (Okio .source (new ByteArrayInputStream (content ))));
116
122
117
123
if (null != overrides && null != overrides .libConfig ) {
118
- receivedOverrides = true ;
119
- applyConfigOverrides (checkConfig (overrides .libConfig ));
124
+ configs .put (configKey .getConfigId (), overrides );
120
125
if (log .isDebugEnabled ()) {
121
126
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 ());
123
130
}
124
131
} else {
125
132
log .debug ("No APM_TRACING overrides" );
126
133
}
127
134
}
128
135
129
136
@ Override
130
- public void remove (ConfigKey configKey , PollingRateHinter hinter ) {}
137
+ public void remove (ConfigKey configKey , PollingRateHinter hinter ) {
138
+ configs .remove (configKey .getConfigId ());
139
+ }
131
140
132
141
@ Override
133
142
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 ()) {
135
162
removeConfigOverrides ();
136
163
log .debug ("Removed APM_TRACING overrides" );
137
- } else {
138
- receivedOverrides = false ;
139
164
}
140
165
}
141
166
@@ -263,6 +288,77 @@ private Map<String, String> parseTagListToMap(List<String> input) {
263
288
static final class ConfigOverrides {
264
289
@ Json (name = "lib_config" )
265
290
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 ;
266
362
}
267
363
268
364
static final class LibConfig {
@@ -307,6 +403,71 @@ static final class LibConfig {
307
403
308
404
@ Json (name = "live_debugging_enabled" )
309
405
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
+ }
310
471
}
311
472
312
473
/** Holds the raw JSON string and the parsed rule data. */
0 commit comments