Skip to content

Commit 690a914

Browse files
committed
support multi config with priorities
1 parent c714d16 commit 690a914

File tree

6 files changed

+576
-6
lines changed

6 files changed

+576
-6
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// <copyright file="ApmTracingConfig.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
using Datadog.Trace.Vendors.Newtonsoft.Json;
9+
10+
#pragma warning disable SA1402 // File must contain single type
11+
namespace Datadog.Trace.Configuration
12+
{
13+
/// <summary>
14+
/// Represents an APM_TRACING configuration with targeting information
15+
/// </summary>
16+
internal class ApmTracingConfig
17+
{
18+
public ApmTracingConfig(string configId, ServiceTarget? serviceTarget, LibConfig libConfig)
19+
{
20+
ConfigId = configId;
21+
ServiceTarget = serviceTarget;
22+
LibConfig = libConfig;
23+
}
24+
25+
public string ConfigId { get; }
26+
27+
public ServiceTarget? ServiceTarget { get; }
28+
29+
public LibConfig LibConfig { get; }
30+
31+
/// <summary>
32+
/// Gets the priority of this configuration based on targeting specificity.
33+
/// Higher values = higher priority.
34+
/// </summary>
35+
public int Priority
36+
{
37+
get
38+
{
39+
if (ServiceTarget == null)
40+
{
41+
return 0; // Org level (lowest priority)
42+
}
43+
44+
var hasService = !string.IsNullOrEmpty(ServiceTarget.Service) && ServiceTarget.Service != "*";
45+
var hasEnv = !string.IsNullOrEmpty(ServiceTarget.Env) && ServiceTarget.Env != "*";
46+
47+
return new ConfigurationTarget(hasService, hasEnv) switch
48+
{
49+
(true, true) => 4, // Service+env (highest priority)
50+
(true, false) => 3, // Service only
51+
(false, true) => 2, // Env only
52+
(false, false) => 1 // Cluster target or wildcard
53+
};
54+
}
55+
}
56+
57+
/// <summary>
58+
/// Checks if this configuration matches the current service and environment
59+
/// </summary>
60+
public bool Matches(string serviceName, string environment)
61+
{
62+
if (ServiceTarget == null)
63+
{
64+
return true; // Org-level config matches everything
65+
}
66+
67+
var serviceMatches = string.IsNullOrEmpty(ServiceTarget.Service) ||
68+
ServiceTarget.Service == "*" ||
69+
ServiceTarget.Service == serviceName;
70+
71+
var envMatches = string.IsNullOrEmpty(ServiceTarget.Env) ||
72+
ServiceTarget.Env == "*" ||
73+
ServiceTarget.Env == environment;
74+
75+
return serviceMatches && envMatches;
76+
}
77+
78+
/// <summary>
79+
/// Merges this configuration with another, giving priority to the higher priority config
80+
/// </summary>
81+
public ApmTracingConfig MergeWith(ApmTracingConfig other)
82+
{
83+
var higherPriority = this.Priority >= other.Priority ? this : other;
84+
var lowerPriority = this.Priority >= other.Priority ? other : this;
85+
86+
return new ApmTracingConfig(
87+
higherPriority.ConfigId,
88+
higherPriority.ServiceTarget,
89+
MergeLibConfigs(higherPriority.LibConfig, lowerPriority.LibConfig));
90+
}
91+
92+
private static LibConfig MergeLibConfigs(LibConfig higher, LibConfig lower)
93+
{
94+
return new LibConfig
95+
{
96+
TracingEnabled = higher.TracingEnabled ?? lower.TracingEnabled,
97+
LogInjectionEnabled = higher.LogInjectionEnabled ?? lower.LogInjectionEnabled,
98+
TracingSamplingRate = higher.TracingSamplingRate ?? lower.TracingSamplingRate,
99+
TracingSamplingRules = higher.TracingSamplingRules ?? lower.TracingSamplingRules,
100+
TracingHeaderTags = higher.TracingHeaderTags ?? lower.TracingHeaderTags,
101+
TracingTags = higher.TracingTags ?? lower.TracingTags,
102+
};
103+
}
104+
105+
internal record struct ConfigurationTarget(bool HasService, bool HasEnv);
106+
}
107+
108+
internal class ApmTracingConfigDto
109+
{
110+
[JsonProperty("service_target")]
111+
public ServiceTarget? ServiceTarget { get; set; }
112+
113+
[JsonProperty("lib_config")]
114+
public LibConfig? LibConfig { get; set; }
115+
}
116+
117+
/// <summary>
118+
/// Represents the service_target field in APM_TRACING configuration
119+
/// </summary>
120+
internal class ServiceTarget
121+
{
122+
[JsonProperty("service")]
123+
public string? Service { get; set; }
124+
125+
[JsonProperty("env")]
126+
public string? Env { get; set; }
127+
}
128+
129+
/// <summary>
130+
/// Represents the lib_config field in APM_TRACING configuration
131+
/// </summary>
132+
internal class LibConfig
133+
{
134+
[JsonProperty("tracing_enabled")]
135+
public bool? TracingEnabled { get; set; }
136+
137+
[JsonProperty("log_injection_enabled")]
138+
public bool? LogInjectionEnabled { get; set; }
139+
140+
[JsonProperty("tracing_sampling_rate")]
141+
public double? TracingSamplingRate { get; set; }
142+
143+
[JsonProperty("tracing_sampling_rules")]
144+
public string? TracingSamplingRules { get; set; }
145+
146+
[JsonProperty("tracing_header_tags")]
147+
public string? TracingHeaderTags { get; set; }
148+
149+
[JsonProperty("tracing_tags")]
150+
public string? TracingTags { get; set; }
151+
}
152+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// <copyright file="ApmTracingConfigMerger.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
using System;
9+
using System.Collections.Generic;
10+
using System.Linq;
11+
using System.Text;
12+
using Datadog.Trace.Logging;
13+
using Datadog.Trace.RemoteConfigurationManagement;
14+
using Datadog.Trace.Vendors.Newtonsoft.Json;
15+
using Datadog.Trace.Vendors.Newtonsoft.Json.Linq;
16+
17+
namespace Datadog.Trace.Configuration
18+
{
19+
/// <summary>
20+
/// Handles merging of multiple APM_TRACING configurations based on priority ordering
21+
/// </summary>
22+
internal static class ApmTracingConfigMerger
23+
{
24+
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(ApmTracingConfigMerger));
25+
26+
/// <summary>
27+
/// Merges multiple APM_TRACING configurations based on priority ordering.
28+
/// </summary>
29+
public static string MergeConfigurations(List<RemoteConfiguration> configs, string serviceName, string environment)
30+
{
31+
if (configs.Count == 1)
32+
{
33+
// Single config - still need to check if it matches the service/environment
34+
var singleConfigContent = Encoding.UTF8.GetString(configs[0].Contents);
35+
var singleConfig = ParseConfiguration(configs[0].Path.Id, singleConfigContent);
36+
37+
if (singleConfig == null || !singleConfig.Matches(serviceName, environment))
38+
{
39+
return "{\"lib_config\":{}}";
40+
}
41+
42+
return singleConfigContent;
43+
}
44+
45+
var parsedConfigs = new List<ApmTracingConfig>();
46+
foreach (var config in configs)
47+
{
48+
try
49+
{
50+
var jsonContent = Encoding.UTF8.GetString(config.Contents);
51+
var configData = ParseConfiguration(config.Path.Id, jsonContent);
52+
if (configData != null)
53+
{
54+
parsedConfigs.Add(configData);
55+
}
56+
}
57+
catch (Exception ex)
58+
{
59+
Log.Error(ex, "Failed to parse APM_TRACING configuration {ConfigPath}", config.Path.Path);
60+
}
61+
}
62+
63+
if (parsedConfigs.Count == 0)
64+
{
65+
Log.Warning("No valid APM_TRACING configurations found");
66+
return "{\"lib_config\":{}}";
67+
}
68+
69+
// Filter configurations that match the current service and environment
70+
var applicableConfigs = parsedConfigs
71+
.Where(c => c.Matches(serviceName, environment))
72+
.OrderByDescending(c => c.Priority)
73+
.ThenBy(c => c.ConfigId) // For deterministic ordering when priorities are equal
74+
.ToList();
75+
76+
if (applicableConfigs.Count == 0)
77+
{
78+
Log.Debug("No APM_TRACING configurations match service '{ServiceName}' and environment '{Environment}'", serviceName, environment);
79+
return "{\"lib_config\":{}}";
80+
}
81+
82+
// Merge configurations based on priority using the new MergeWith method
83+
var mergedConfig = applicableConfigs.Aggregate((current, next) => current.MergeWith(next));
84+
85+
// Wrap in the expected structure for DynamicConfigConfigurationSource
86+
var result = new { lib_config = mergedConfig.LibConfig };
87+
return JsonConvert.SerializeObject(result);
88+
}
89+
90+
/// <summary>
91+
/// Parses a single APM_TRACING configuration JSON
92+
/// </summary>
93+
private static ApmTracingConfig? ParseConfiguration(string configId, string jsonContent)
94+
{
95+
try
96+
{
97+
var configDto = JsonConvert.DeserializeObject<ApmTracingConfigDto>(jsonContent);
98+
99+
if (configDto?.LibConfig == null)
100+
{
101+
Log.Warning("APM_TRACING configuration {ConfigId} has no lib_config", configId);
102+
return null;
103+
}
104+
105+
// ServiceTarget might be null (org-level config)
106+
return new ApmTracingConfig(configId, configDto.ServiceTarget, configDto.LibConfig);
107+
}
108+
catch (Exception ex)
109+
{
110+
Log.Error(ex, "Failed to parse APM_TRACING configuration {ConfigId}", configId);
111+
return null;
112+
}
113+
}
114+
}
115+
}

tracer/src/Datadog.Trace/Configuration/DynamicConfigurationManager.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public void Start()
4747
_subscriptionManager.SetCapability(RcmCapabilitiesIndices.ApmTracingSampleRate, true); // 12
4848
_subscriptionManager.SetCapability(RcmCapabilitiesIndices.ApmTracingTracingEnabled, true); // 19
4949
_subscriptionManager.SetCapability(RcmCapabilitiesIndices.ApmTracingSampleRules, true); // 29
50+
_subscriptionManager.SetCapability(RcmCapabilitiesIndices.ApmTracingMulticonfig, true); // 44
5051
}
5152
}
5253

@@ -138,14 +139,15 @@ private ApplyDetails[] ConfigurationUpdated(Dictionary<string, List<RemoteConfig
138139
}
139140
else
140141
{
141-
var compositeConfigurationSource = new CompositeConfigurationSource();
142+
// Get current service name and environment for configuration matching and use the merger for multiple configurations
143+
var currentSettings = Tracer.Instance.Settings;
144+
var serviceName = currentSettings.ServiceName ?? "unknown";
145+
var environment = currentSettings.Environment ?? "unknown";
142146

143-
foreach (var item in apmLibrary)
144-
{
145-
compositeConfigurationSource.Add(new DynamicConfigConfigurationSource(Encoding.UTF8.GetString(item.Contents), ConfigurationOrigins.RemoteConfig));
146-
}
147+
// APM_TRACING have also k8s_target_v2, but we don't support it in the tracer atm
147148

148-
configurationSource = compositeConfigurationSource;
149+
var mergedConfigJson = ApmTracingConfigMerger.MergeConfigurations(apmLibrary, serviceName, environment);
150+
configurationSource = new DynamicConfigConfigurationSource(mergedConfigJson, ConfigurationOrigins.RemoteConfig);
149151
}
150152

151153
var configurationBuilder = new ConfigurationBuilder(configurationSource, _configurationTelemetry);

tracer/src/Datadog.Trace/RemoteConfigurationManagement/RcmCapabilitiesIndices.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ internal static class RcmCapabilitiesIndices
9797

9898
public static readonly BigInteger AsmTraceTaggingRules = Create(43);
9999

100+
public static readonly BigInteger ApmTracingMulticonfig = Create(44);
101+
100102
private static BigInteger Create(int index) => new(1UL << index);
101103
}
102104
}

tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/DynamicConfigurationTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ private async Task UpdateAndValidateConfig(MockTracerAgent agent, LogEntryWatche
187187
capabilities[15].Should().BeTrue(); // APM_TRACING_CUSTOM_TAGS
188188
capabilities[19].Should().BeTrue(); // APM_TRACING_TRACING_ENABLED
189189
capabilities[29].Should().BeTrue(); // APM_TRACING_SAMPLE_RULES
190+
capabilities[44].Should().BeTrue(); // APM_TRACING_MULTICONFIG
190191

191192
request.Client.State.ConfigStates.Should().ContainSingle(f => f.Id == fileId)
192193
.Subject.ApplyState.Should().Be(ApplyStates.ACKNOWLEDGED);

0 commit comments

Comments
 (0)