Skip to content

Commit c498ef2

Browse files
vpellanericfirth
authored andcommitted
Update Stable Config native extension to support libdatadog 21 and add support for debug logs
1 parent 33019aa commit c498ef2

File tree

7 files changed

+158
-51
lines changed

7 files changed

+158
-51
lines changed

ext/libdatadog_api/library_config.c

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ static VALUE _native_configurator_get(VALUE self);
1111
static VALUE _native_configurator_with_local_path(DDTRACE_UNUSED VALUE _self, VALUE rb_configurator, VALUE path);
1212
static VALUE _native_configurator_with_fleet_path(DDTRACE_UNUSED VALUE _self, VALUE rb_configurator, VALUE path);
1313

14-
static VALUE config_vec_class = Qnil;
14+
static VALUE config_logged_result_class = Qnil;
1515

1616
// ddog_Configurator memory management
1717
static void configurator_free(void *configurator_ptr) {
@@ -29,29 +29,29 @@ static const rb_data_type_t configurator_typed_data = {
2929
.flags = RUBY_TYPED_FREE_IMMEDIATELY
3030
};
3131

32-
// ddog_Vec_LibraryConfig memory management
33-
static void config_vec_free(void *config_vec_ptr) {
34-
ddog_Vec_LibraryConfig *config_vec = (ddog_Vec_LibraryConfig *)config_vec_ptr;
32+
// ddog_LibraryConfigLoggedResult memory management
33+
static void config_logged_result_free(void *config_logged_result_ptr) {
34+
ddog_LibraryConfigLoggedResult *config_logged_result = (ddog_LibraryConfigLoggedResult *)config_logged_result_ptr;
3535

36-
ddog_library_config_drop(*config_vec);
37-
ruby_xfree(config_vec_ptr);
36+
ddog_library_config_drop(*config_logged_result);
37+
ruby_xfree(config_logged_result_ptr);
3838
}
3939

40-
static const rb_data_type_t config_vec_typed_data = {
41-
.wrap_struct_name = "Datadog::Core::Configuration::StableConfigVec",
40+
static const rb_data_type_t config_logged_result_typed_data = {
41+
.wrap_struct_name = "Datadog::Core::Configuration::StableConfigLoggedResult",
4242
.function = {
43-
.dfree = config_vec_free,
43+
.dfree = config_logged_result_free,
4444
.dsize = NULL,
4545
},
4646
.flags = RUBY_TYPED_FREE_IMMEDIATELY
4747
};
4848

4949
void library_config_init(VALUE core_module) {
50-
rb_global_variable(&config_vec_class);
50+
rb_global_variable(&config_logged_result_class);
5151
VALUE configuration_module = rb_define_module_under(core_module, "Configuration");
5252
VALUE stable_config_module = rb_define_module_under(configuration_module, "StableConfig");
5353
VALUE configurator_class = rb_define_class_under(stable_config_module, "Configurator", rb_cObject);
54-
config_vec_class = rb_define_class_under(configuration_module, "StableConfigVec", rb_cObject);
54+
config_logged_result_class = rb_define_class_under(configuration_module, "StableConfigLoggedResult", rb_cObject);
5555

5656
rb_define_alloc_func(configurator_class, _native_configurator_new);
5757
rb_define_method(configurator_class, "get", _native_configurator_get, 0);
@@ -61,11 +61,12 @@ void library_config_init(VALUE core_module) {
6161
rb_define_singleton_method(testing_module, "with_local_path", _native_configurator_with_local_path, 2);
6262
rb_define_singleton_method(testing_module, "with_fleet_path", _native_configurator_with_fleet_path, 2);
6363

64-
rb_undef_alloc_func(config_vec_class); // It cannot be created from Ruby code and only serves as an intermediate object for the Ruby GC
64+
rb_undef_alloc_func(config_logged_result_class); // It cannot be created from Ruby code and only serves as an intermediate object for the Ruby GC
6565
}
6666

6767
static VALUE _native_configurator_new(VALUE klass) {
68-
ddog_Configurator *configurator = ddog_library_configurator_new(false, DDOG_CHARSLICE_C("ruby"));
68+
// We always collect debug logs, so if DD_TRACE_DEBUG is set by stable config, we'll be able to log them.
69+
ddog_Configurator *configurator = ddog_library_configurator_new(true, DDOG_CHARSLICE_C("ruby"));
6970

7071
ddog_library_configurator_with_detect_process_info(configurator);
7172

@@ -98,27 +99,33 @@ static VALUE _native_configurator_get(VALUE self) {
9899
ddog_Configurator *configurator;
99100
TypedData_Get_Struct(self, ddog_Configurator, &configurator_typed_data, configurator);
100101

101-
ddog_Result_VecLibraryConfig configurator_result = ddog_library_configurator_get(configurator);
102+
// Wrapping config_logged_result into a Ruby object enables the Ruby GC to manage its memory
103+
// We need to allocate memory for config_logged_result because once it is out of scope, it will be freed (at the end of this function)
104+
// So we cannot reference it with &config_logged_result
105+
// We are doing this in case one of the ruby API raises an exception before the end of this function,
106+
// so the allocated memory will still be freed
107+
ddog_LibraryConfigLoggedResult *configurator_logged_result = (ddog_LibraryConfigLoggedResult *)ruby_xmalloc(sizeof(ddog_LibraryConfigLoggedResult));
108+
*configurator_logged_result = ddog_library_configurator_get(configurator);
109+
VALUE config_logged_result_rb = TypedData_Wrap_Struct(config_logged_result_class, &config_logged_result_typed_data, configurator_logged_result);
102110

103-
if (configurator_result.tag == DDOG_RESULT_VEC_LIBRARY_CONFIG_ERR_VEC_LIBRARY_CONFIG) {
104-
ddog_Error err = configurator_result.err;
111+
if (configurator_logged_result->tag == DDOG_LIBRARY_CONFIG_LOGGED_RESULT_ERR) {
112+
ddog_Error err = configurator_logged_result->err;
105113
VALUE message = get_error_details_and_drop(&err);
106114
if (is_config_loaded()) {
107115
log_warning(message);
108116
} else {
109117
log_warning_without_config(message);
110118
}
119+
RB_GC_GUARD(config_logged_result_rb);
111120
return rb_hash_new();
112121
}
113122

114-
// Wrapping config_vec into a Ruby object enables the Ruby GC to manage its memory
115-
// We need to allocate memory for config_vec because once it is out of scope, it will be freed (at the end of this function)
116-
// So we cannot reference it with &config_vec
117-
// We are doing this in case one of the ruby API raises an exception before the end of this function,
118-
// so the allocated memory will still be freed
119-
ddog_Vec_LibraryConfig *config_vec = ruby_xmalloc(sizeof(ddog_Vec_LibraryConfig));
120-
*config_vec = configurator_result.ok;
121-
VALUE config_vec_rb = TypedData_Wrap_Struct(config_vec_class, &config_vec_typed_data, config_vec);
123+
VALUE logs = Qnil;
124+
if (configurator_logged_result->ok.logs.length > 0) {
125+
logs = rb_utf8_str_new_cstr(configurator_logged_result->ok.logs.ptr);
126+
}
127+
128+
ddog_Vec_LibraryConfig config_vec = configurator_logged_result->ok.value;
122129

123130
VALUE local_config_hash = rb_hash_new();
124131
VALUE fleet_config_hash = rb_hash_new();
@@ -127,8 +134,8 @@ static VALUE _native_configurator_get(VALUE self) {
127134
bool fleet_config_id_set = false;
128135
VALUE local_hash = rb_hash_new();
129136
VALUE fleet_hash = rb_hash_new();
130-
for (uintptr_t i = 0; i < config_vec->len; i++) {
131-
ddog_LibraryConfig config = config_vec->ptr[i];
137+
for (uintptr_t i = 0; i < config_vec.len; i++) {
138+
ddog_LibraryConfig config = config_vec.ptr[i];
132139
VALUE selected_hash;
133140
if (config.source == DDOG_LIBRARY_CONFIG_SOURCE_LOCAL_STABLE_CONFIG) {
134141
selected_hash = local_config_hash;
@@ -156,9 +163,10 @@ static VALUE _native_configurator_get(VALUE self) {
156163
rb_hash_aset(fleet_hash, ID2SYM(rb_intern("config")), fleet_config_hash);
157164

158165
VALUE result = rb_hash_new();
166+
rb_hash_aset(result, ID2SYM(rb_intern("logs")), logs);
159167
rb_hash_aset(result, ID2SYM(rb_intern("local")), local_hash);
160168
rb_hash_aset(result, ID2SYM(rb_intern("fleet")), fleet_hash);
161169

162-
RB_GC_GUARD(config_vec_rb);
170+
RB_GC_GUARD(config_logged_result_rb);
163171
return result;
164172
}

lib/datadog/core/configuration.rb

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,10 @@ def logger
172172
end
173173
end
174174

175-
def config_init_logger
176-
configuration? ? logger : logger_without_configuration
175+
# During stable config initialization, we also have to check for DD_TRACE_DEBUG in local and fleet config.
176+
# We pass the debug source value
177+
def config_init_logger(debug_source_value = DATADOG_ENV[Ext::Diagnostics::ENV_DEBUG_ENABLED])
178+
configuration? ? logger : logger_without_configuration(debug_source_value)
177179
end
178180

179181
# Gracefully shuts down all components.
@@ -290,17 +292,17 @@ def logger_without_components
290292
end
291293
end
292294

293-
def logger_without_configuration
295+
def logger_without_configuration(debug_source_value)
294296
# There's rare cases where we need to use logger during configuration initialization,
295297
# such as reading stable config. In this case we cannot access configuration.
296298

297299
@temp_config_logger ||= begin
298-
debug_env_value = DATADOG_ENV[Ext::Diagnostics::ENV_DEBUG_ENABLED]&.strip&.downcase
299-
debug_value = debug_env_value == 'true' || debug_env_value == '1'
300+
debug_value = debug_source_value&.strip&.downcase
301+
debug = debug_value == 'true' || debug_value == '1'
300302

301303
logger = Core::Logger.new($stdout)
302304
# We cannot access config and the default level is INFO, so we need to set the level manually
303-
logger.level = debug_value ? ::Logger::DEBUG : ::Logger::INFO
305+
logger.level = debug ? ::Logger::DEBUG : ::Logger::INFO
304306
logger
305307
end
306308
end

lib/datadog/core/configuration/stable_config.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,18 @@ def self.extract_configuration
1010
Datadog.config_init_logger.debug("Cannot enable stable config: #{libdatadog_api_failure}")
1111
return {}
1212
end
13-
Configurator.new.get
13+
config = Configurator.new.get
14+
# Take into account stable config values for DD_TRACE_DEBUG
15+
if config[:logs]
16+
# Priority : Fleet > Environment > Local
17+
debug_source_value =
18+
config.dig(:fleet, :config, Ext::Diagnostics::ENV_DEBUG_ENABLED) ||
19+
DATADOG_ENV[Ext::Diagnostics::ENV_DEBUG_ENABLED] ||
20+
config.dig(:local, :config, Ext::Diagnostics::ENV_DEBUG_ENABLED) ||
21+
'false'
22+
Datadog.config_init_logger(debug_source_value).debug(config[:logs])
23+
end
24+
config
1425
end
1526

1627
def self.configuration

sig/datadog/core/configuration.rbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module Datadog
99

1010
def configuration: () -> untyped # The DSL methods have to be explicitly declared for this to be typed
1111

12-
def config_init_logger: () -> Datadog::Core::Logger
12+
def config_init_logger: (?String debug_source_value) -> Datadog::Core::Logger
1313

1414
def shutdown!: -> void
1515
end

sig/datadog/core/configuration/stable_config.rbs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ module Datadog
33
module Configuration
44
module StableConfig
55

6-
type stable_config = Hash[Symbol, Hash[String, String]]
6+
type stable_config = {
7+
?logs: String?,
8+
?local: Hash[String, String],
9+
?fleet: Hash[String, String]
10+
}
711

812
class Configurator
913
def initialize: -> void

spec/datadog/core/configuration/stable_config_spec.rb

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,36 @@
1818
describe '#configuration', skip: !LibdatadogHelpers.supported? do
1919
let(:tmpdir) { Dir.mktmpdir }
2020
before do
21+
Datadog::Core::Configuration::StableConfig.instance_variable_set(:@configuration, nil)
22+
2123
File.write(
2224
File.join(tmpdir, 'local_config.yaml'),
2325
local_config_content
24-
)
26+
) if defined?(local_config_content)
2527
File.write(
2628
File.join(tmpdir, 'fleet_config.yaml'),
2729
fleet_config_content
28-
)
30+
) if defined?(fleet_config_content)
2931

3032
test_configurator = Datadog::Core::Configuration::StableConfig::Configurator.new
3133
Datadog::Core::Configuration::StableConfig::Testing.with_local_path(
3234
test_configurator,
3335
File.join(tmpdir, 'local_config.yaml')
34-
)
36+
) if defined?(local_config_content)
3537
Datadog::Core::Configuration::StableConfig::Testing.with_fleet_path(
3638
test_configurator,
3739
File.join(tmpdir, 'fleet_config.yaml')
38-
)
40+
) if defined?(fleet_config_content)
3941

40-
allow(Datadog::Core::Configuration::StableConfig).to receive(:extract_configuration).and_return(
42+
allow_any_instance_of(Datadog::Core::Configuration::StableConfig::Configurator).to receive(:get).and_return(
4143
test_configurator.get
4244
)
4345
end
4446

4547
after do
4648
FileUtils.remove_entry_secure(tmpdir)
49+
50+
Datadog::Core::Configuration::StableConfig.instance_variable_set(:@configuration, nil)
4751
end
4852

4953
context 'when libdatadog API is available' do
@@ -65,12 +69,13 @@
6569
end
6670

6771
it 'returns the configuration' do
68-
expect(described_class.configuration).to eq(
72+
expect(described_class.configuration).to include(
6973
{
7074
local: {id: "12345", config: {"DD_LOGS_INJECTION" => "false"}},
7175
fleet: {id: "56789", config: {"DD_APPSEC_ENABLED" => "true"}}
7276
}
7377
)
78+
expect(described_class.configuration[:logs]).to be_a(String)
7479
end
7580
end
7681

@@ -90,12 +95,84 @@
9095
end
9196

9297
it 'returns the configuration' do
93-
expect(described_class.configuration).to eq(
98+
expect(described_class.configuration).to include(
9499
{
95100
local: {config: {"DD_LOGS_INJECTION" => "false"}},
96101
fleet: {config: {"DD_APPSEC_ENABLED" => "true"}}
97102
}
98103
)
104+
expect(described_class.configuration[:logs]).to be_a(String)
105+
end
106+
end
107+
108+
context 'with DD_TRACE_DEBUG set during configuration initialization' do
109+
# Global configuration has already been set up before the test, so we need to simulate that it was not set up
110+
# Same for temporary logger
111+
before do
112+
allow(Datadog).to receive(:configuration?).and_return(false)
113+
Datadog.instance_variable_set(:@temp_config_logger, nil)
114+
end
115+
116+
after do
117+
Datadog.instance_variable_set(:@temp_config_logger, nil)
118+
end
119+
120+
context 'to true in fleet config' do
121+
let(:fleet_config_content) do
122+
<<~YAML
123+
apm_configuration_default:
124+
DD_TRACE_DEBUG: true
125+
YAML
126+
end
127+
128+
it 'prints debug logs' do
129+
expect { described_class.configuration }.to output(/Reading stable configuration from files/).to_stdout
130+
end
131+
end
132+
133+
context 'to true in env' do
134+
around do |example|
135+
ClimateControl.modify(Datadog::Core::Configuration::Ext::Diagnostics::ENV_DEBUG_ENABLED => 'true') do
136+
example.run
137+
end
138+
end
139+
140+
it 'prints debug logs' do
141+
expect { described_class.configuration }.to output(/Reading stable configuration from files/).to_stdout
142+
end
143+
end
144+
145+
context 'to true in local config' do
146+
let(:local_config_content) do
147+
<<~YAML
148+
apm_configuration_default:
149+
DD_TRACE_DEBUG: true
150+
YAML
151+
end
152+
153+
it 'prints debug logs' do
154+
expect { described_class.configuration }.to output(/Reading stable configuration from files/).to_stdout
155+
end
156+
end
157+
158+
context 'to true in local config and false in fleet config' do
159+
let(:local_config_content) do
160+
<<~YAML
161+
apm_configuration_default:
162+
DD_TRACE_DEBUG: true
163+
YAML
164+
end
165+
166+
let(:fleet_config_content) do
167+
<<~YAML
168+
apm_configuration_default:
169+
DD_TRACE_DEBUG: false
170+
YAML
171+
end
172+
end
173+
174+
it 'respects priority and does not print debug logs' do
175+
expect { described_class.configuration }.not_to output(/Reading stable configuration from files/).to_stdout
99176
end
100177
end
101178
end

spec/datadog/core/configuration_spec.rb

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -448,20 +448,25 @@
448448
end
449449

450450
describe '#logger_without_configuration' do
451-
subject(:logger_without_configuration) { test_class.send(:logger_without_configuration) }
452-
context 'when configuration is not initialized and DD_TRACE_DEBUG is not set' do
451+
subject(:logger_without_configuration) { test_class.send(:logger_without_configuration, debug_source_value) }
452+
453+
context 'when configuration is not initialized and debug_source_value is not set' do
454+
let(:debug_source_value) { nil }
455+
453456
it { expect(logger_without_configuration.level).to be ::Logger::INFO }
454457
end
455458

456-
context 'when configuration is not initialized and DD_TRACE_DEBUG is set' do
457-
around do |example|
458-
ClimateControl.modify('DD_TRACE_DEBUG' => 'true') do
459-
example.run
460-
end
461-
end
459+
context 'when configuration is not initialized and debug_source_value is set to true' do
460+
let(:debug_source_value) { 'true' }
462461

463462
it { expect(logger_without_configuration.level).to be ::Logger::DEBUG }
464463
end
464+
465+
context 'when configuration is not initialized and debug_source_value is set to false' do
466+
let(:debug_source_value) { 'false' }
467+
468+
it { expect(logger_without_configuration.level).to be ::Logger::INFO }
469+
end
465470
end
466471

467472
describe '#runtime_metrics' do

0 commit comments

Comments
 (0)