datadog 2.46.0 → 2.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -2
- data/ext/datadog_profiling_native_extension/collectors_stack.c +31 -14
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +1 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -0
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +29 -2
- data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +8 -7
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +14 -2
- data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +8 -7
- data/lib/datadog/appsec/security_engine/engine.rb +2 -1
- data/lib/datadog/core/configuration/components.rb +11 -10
- data/lib/datadog/core/configuration/components_state.rb +8 -1
- data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
- data/lib/datadog/core/remote/client.rb +44 -3
- data/lib/datadog/core/remote/dispatcher.rb +34 -3
- data/lib/datadog/core/workers/runtime_metrics.rb +1 -1
- data/lib/datadog/di/capture_expression_evaluator.rb +1 -1
- data/lib/datadog/di/component.rb +70 -0
- data/lib/datadog/di/remote.rb +46 -2
- data/lib/datadog/di/serializer.rb +27 -11
- data/lib/datadog/open_feature/configuration.rb +6 -0
- data/lib/datadog/open_feature/hooks/evaluation_telemetry_opt_out_hook.rb +25 -0
- data/lib/datadog/open_feature/provider.rb +22 -3
- data/lib/datadog/profiling/collectors/info.rb +37 -6
- data/lib/datadog/profiling/component.rb +22 -20
- data/lib/datadog/profiling/exporter.rb +3 -5
- data/lib/datadog/tracing/component.rb +7 -2
- data/lib/datadog/tracing/configuration/ext.rb +2 -0
- data/lib/datadog/tracing/configuration/settings.rb +11 -0
- data/lib/datadog/tracing/context_provider.rb +8 -0
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +10 -5
- data/lib/datadog/tracing/contrib/excon/middleware.rb +10 -3
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +10 -4
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +16 -1
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +8 -2
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +10 -4
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +10 -3
- data/lib/datadog/tracing/contrib/utils/quantization/http.rb +102 -0
- data/lib/datadog/tracing/remote.rb +50 -19
- data/lib/datadog/version.rb +1 -1
- metadata +8 -8
- data/lib/datadog/appsec/utils/hash_coercion.rb +0 -23
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f4a11a75d5b532014ee82ed0628621a2586ca9b05ab6b00c7136357b7b0a5445
|
|
4
|
+
data.tar.gz: d51c04201a7f931546374be3adc2c6445e523e98713b60c3e7167398da2dede2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 344ce6f6622f0c6879f598917bab789aef9345a0498d3164b7481df27fdd916b8a4b65b69893fda1527db16ad01da921340441e35a880df4207ae6806966c31a
|
|
7
|
+
data.tar.gz: 2a77c07e5af10203211755104d3b4e117a70ef64d673d04330769b01a350d2bc8de024b5f7fad2954d95c50a99fa3526c1a473f169653437ad73cb51cea7e542
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.47.0] - 2026-10-26
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
* Tracing: Add quantized request paths to outbound HTTP client resource names, such as `GET /users/*`; enable with `DD_TRACE_HTTP_CLIENT_RESOURCE_NAME_QUANTIZE=true`. ([#6298][])
|
|
10
|
+
* Open Feature: Let evaluations opt out of flag evaluation events, metrics, and span tags with `dd_evaluation_telemetry_opt_out: true` in the evaluation context; enable it with `DD_FEATURE_FLAGS_EVALUATION_TELEMETRY_PER_SUBJECT_OPT_OUT_ENABLED=true`. ([#6486][])
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
* AppSec: Update `libddwaf` to 1.30.0.1.0. ([#6548][])
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
* Core: Fix forked processes, such as Puma and Unicorn workers, reporting no runtime metrics when the parent process traced before forking. ([#6500][]) ([@dduugg][])
|
|
19
|
+
* Profiling: Fix the "no signals" workaround being enabled when `mysql2` uses MariaDB Connector/C 3.x. ([#6543][])
|
|
20
|
+
* AppSec: Fix ruleset updates with Remote Configuration. ([#6547][])
|
|
21
|
+
* AppSec: Fix duplicate JSON serialization of Rails and Sinatra response objects when application security is enabled. ([#6548][])
|
|
22
|
+
|
|
5
23
|
## [2.46.0] - 2026-10-19
|
|
6
24
|
|
|
7
25
|
### Added
|
|
@@ -3851,7 +3869,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
|
|
|
3851
3869
|
Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
3852
3870
|
|
|
3853
3871
|
|
|
3854
|
-
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.
|
|
3872
|
+
[Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.47.0...master
|
|
3873
|
+
[2.47.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.46.0...v2.47.0
|
|
3855
3874
|
[2.46.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.45.0...v2.46.0
|
|
3856
3875
|
[2.45.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.44.0...v2.45.0
|
|
3857
3876
|
[2.44.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.43.0...v2.44.0
|
|
@@ -5694,6 +5713,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5694
5713
|
[#6261]: https://github.com/DataDog/dd-trace-rb/issues/6261
|
|
5695
5714
|
[#6286]: https://github.com/DataDog/dd-trace-rb/issues/6286
|
|
5696
5715
|
[#6291]: https://github.com/DataDog/dd-trace-rb/issues/6291
|
|
5716
|
+
[#6298]: https://github.com/DataDog/dd-trace-rb/issues/6298
|
|
5697
5717
|
[#6316]: https://github.com/DataDog/dd-trace-rb/issues/6316
|
|
5698
5718
|
[#6320]: https://github.com/DataDog/dd-trace-rb/issues/6320
|
|
5699
5719
|
[#6339]: https://github.com/DataDog/dd-trace-rb/issues/6339
|
|
@@ -5721,7 +5741,12 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5721
5741
|
[#6474]: https://github.com/DataDog/dd-trace-rb/issues/6474
|
|
5722
5742
|
[#6481]: https://github.com/DataDog/dd-trace-rb/issues/6481
|
|
5723
5743
|
[#6485]: https://github.com/DataDog/dd-trace-rb/issues/6485
|
|
5744
|
+
[#6486]: https://github.com/DataDog/dd-trace-rb/issues/6486
|
|
5724
5745
|
[#6488]: https://github.com/DataDog/dd-trace-rb/issues/6488
|
|
5746
|
+
[#6500]: https://github.com/DataDog/dd-trace-rb/issues/6500
|
|
5747
|
+
[#6543]: https://github.com/DataDog/dd-trace-rb/issues/6543
|
|
5748
|
+
[#6547]: https://github.com/DataDog/dd-trace-rb/issues/6547
|
|
5749
|
+
[#6548]: https://github.com/DataDog/dd-trace-rb/issues/6548
|
|
5725
5750
|
[@AdrianLC]: https://github.com/AdrianLC
|
|
5726
5751
|
[@Azure7111]: https://github.com/Azure7111
|
|
5727
5752
|
[@BabyGroot]: https://github.com/BabyGroot
|
|
@@ -5878,4 +5903,4 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
|
|
|
5878
5903
|
[@y-yagi]: https://github.com/y-yagi
|
|
5879
5904
|
[@yujideveloper]: https://github.com/yujideveloper
|
|
5880
5905
|
[@yukimurasawa]: https://github.com/yukimurasawa
|
|
5881
|
-
[@zachmccormick]: https://github.com/zachmccormick
|
|
5906
|
+
[@zachmccormick]: https://github.com/zachmccormick
|
|
@@ -41,7 +41,7 @@ static const char *get_or_compute_native_filename(void *function, st_table *nati
|
|
|
41
41
|
static void initialize_static_ruby_actual_filename(VALUE profiling_module);
|
|
42
42
|
static void add_truncated_frames_placeholder(ddog_prof_Location *locations);
|
|
43
43
|
static void record_placeholder_stack_in_native_code(VALUE recorder_instance, sample_values values, sample_labels labels);
|
|
44
|
-
static void maybe_trim_template_random_ids(ddog_CharSlice *
|
|
44
|
+
static void maybe_trim_template_random_ids(ddog_CharSlice *frame_name, ddog_CharSlice method_name, ddog_CharSlice filename);
|
|
45
45
|
|
|
46
46
|
// NULL if we weren't able to get the native filename for the Ruby VM.
|
|
47
47
|
// On a static Ruby without libruby.so, `get_or_compute_native_filename` returns `ruby_native_filename` but
|
|
@@ -438,7 +438,7 @@ void sample_thread(
|
|
|
438
438
|
frame_name = name_slice;
|
|
439
439
|
}
|
|
440
440
|
|
|
441
|
-
maybe_trim_template_random_ids(&frame_name,
|
|
441
|
+
maybe_trim_template_random_ids(&frame_name, name_slice, filename_slice);
|
|
442
442
|
|
|
443
443
|
int libdatadog_stores_stacks_flipped_from_rb_profile_frames_index = top_of_stack_position - i;
|
|
444
444
|
|
|
@@ -548,33 +548,50 @@ static const char *get_or_compute_native_filename(void *function, st_table *nati
|
|
|
548
548
|
// the extra "_".)
|
|
549
549
|
//
|
|
550
550
|
// This method trims these suffixes, so that we keep less data + the names correctly aggregate together.
|
|
551
|
-
|
|
551
|
+
//
|
|
552
|
+
// We match on `method_name` because it always ends with the ids, while `frame_name` can have DDTRACE_BLOCK_SUFFIX
|
|
553
|
+
// after them ("Foo#_app_views_articles_index_html_erb__123_456 (block)"); in that case we move the suffix left over
|
|
554
|
+
// the ids. A `frame_name` that is a Ruby String ends with the ids we just matched, so that write only ever lands on
|
|
555
|
+
// a name we composed in the sample's own buffer.
|
|
556
|
+
static void maybe_trim_template_random_ids(ddog_CharSlice *frame_name, ddog_CharSlice method_name, ddog_CharSlice filename) {
|
|
552
557
|
// Check filename doesn't end with ".rb"; templates are usually along the lines of .html.erb/.html.haml/...
|
|
553
|
-
if (
|
|
558
|
+
if (filename.len < 3 || memcmp(filename.ptr + filename.len - 3, ".rb", 3) == 0) return;
|
|
554
559
|
|
|
555
|
-
if (
|
|
556
|
-
if (
|
|
560
|
+
if (method_name.len > 1024) return;
|
|
561
|
+
if (method_name.len == 0) return;
|
|
557
562
|
|
|
558
|
-
int pos = ((int)
|
|
563
|
+
int pos = ((int) method_name.len) - 1;
|
|
559
564
|
|
|
560
565
|
// Let's match on something__number_number:
|
|
561
566
|
// Find start of id suffix from the end...
|
|
562
|
-
if (
|
|
567
|
+
if (method_name.ptr[pos] < '0' || method_name.ptr[pos] > '9') return;
|
|
563
568
|
|
|
564
569
|
// ...now match a bunch of numbers and interspersed underscores
|
|
565
570
|
for (int underscores = 0; pos >= 0 && underscores < 2; pos--) {
|
|
566
|
-
if (
|
|
567
|
-
else if (
|
|
571
|
+
if (method_name.ptr[pos] == '_') underscores++;
|
|
572
|
+
else if (method_name.ptr[pos] < '0' || method_name.ptr[pos] > '9') return;
|
|
568
573
|
}
|
|
569
574
|
|
|
570
575
|
// Make sure there's something left before the underscores (hence the <= instead of <) + match the last underscore
|
|
571
|
-
if (pos <= 0 ||
|
|
576
|
+
if (pos <= 0 || method_name.ptr[pos] != '_') return;
|
|
572
577
|
|
|
573
578
|
// Does it have the optional third underscore? If so, remove it as well
|
|
574
|
-
if (pos > 1 &&
|
|
579
|
+
if (pos > 1 && method_name.ptr[pos-1] == '_') pos--;
|
|
580
|
+
|
|
581
|
+
// If we got here, we matched on our pattern. Let's remove the ids from the end of the frame name.
|
|
582
|
+
size_t random_ids_len = method_name.len - pos;
|
|
583
|
+
|
|
584
|
+
static const char block_suffix[] = DDTRACE_BLOCK_SUFFIX;
|
|
585
|
+
size_t block_suffix_len = sizeof(block_suffix) - 1;
|
|
586
|
+
|
|
587
|
+
if (
|
|
588
|
+
frame_name->len >= random_ids_len + block_suffix_len &&
|
|
589
|
+
memcmp(frame_name->ptr + frame_name->len - block_suffix_len, block_suffix, block_suffix_len) == 0
|
|
590
|
+
) {
|
|
591
|
+
memcpy((char *) frame_name->ptr + frame_name->len - block_suffix_len - random_ids_len, block_suffix, block_suffix_len);
|
|
592
|
+
}
|
|
575
593
|
|
|
576
|
-
|
|
577
|
-
name_slice->len = pos;
|
|
594
|
+
frame_name->len -= random_ids_len;
|
|
578
595
|
}
|
|
579
596
|
|
|
580
597
|
static void add_truncated_frames_placeholder(ddog_prof_Location *locations) {
|
|
@@ -1136,7 +1136,7 @@ static ssize_t calculate_iseq_label(VALUE owner, const rb_iseq_t *iseq, char *bu
|
|
|
1136
1136
|
// We use "Foo#bar (block)" because it's much faster and looks better in the UI.
|
|
1137
1137
|
const rb_iseq_t* method_iseq = ISEQ_BODY(iseq)->local_iseq;
|
|
1138
1138
|
|
|
1139
|
-
static const char suffix[] =
|
|
1139
|
+
static const char suffix[] = DDTRACE_BLOCK_SUFFIX;
|
|
1140
1140
|
size_t suffix_len = sizeof(suffix) - 1;
|
|
1141
1141
|
|
|
1142
1142
|
// Only use the result if there is enough space for both parts
|
|
@@ -102,6 +102,9 @@ VALUE ddtrace_alloc_free_rb_mod_name(VALUE mod);
|
|
|
102
102
|
// if the module does not have a permanent name (e.g. `#<Module:0x0123>::Foo`).
|
|
103
103
|
VALUE ddtrace_permanent_mod_name(VALUE mod);
|
|
104
104
|
|
|
105
|
+
// Suffix ddtrace_location_label() adds to blocks, e.g. "Foo#bar (block)"
|
|
106
|
+
#define DDTRACE_BLOCK_SUFFIX " (block)"
|
|
107
|
+
|
|
105
108
|
ssize_t ddtrace_location_label(const rb_callable_method_entry_t *cme, const rb_iseq_t *iseq, char *buf, size_t buf_size);
|
|
106
109
|
VALUE ddtrace_location_base_label(const rb_callable_method_entry_t *cme, const rb_iseq_t *iseq);
|
|
107
110
|
void* ddtrace_cme_cfunc_func(const rb_callable_method_entry_t *cme);
|
|
@@ -11,7 +11,7 @@ module Datadog
|
|
|
11
11
|
module LibdatadogExtconfHelpers
|
|
12
12
|
# Used to make sure the correct gem version gets loaded, as extconf.rb does not get run with "bundle exec" and thus
|
|
13
13
|
# may see multiple libdatadog versions. See https://github.com/DataDog/dd-trace-rb/pull/2531 for the horror story.
|
|
14
|
-
LIBDATADOG_VERSION = "~> 46.
|
|
14
|
+
LIBDATADOG_VERSION = "~> 46.1.0.1.1"
|
|
15
15
|
|
|
16
16
|
# Used as an workaround for a limitation with how dynamic linking works in environments where the datadog gem and
|
|
17
17
|
# libdatadog are moved after the extension gets compiled.
|
|
@@ -66,10 +66,23 @@ module Datadog
|
|
|
66
66
|
gateway.watch("rails.response.body.json") do |stack, container|
|
|
67
67
|
context = container.context
|
|
68
68
|
|
|
69
|
+
body_bytesize = container.data.bytesize
|
|
70
|
+
limit = Datadog.configuration.appsec.body_parsing_size_limit
|
|
71
|
+
|
|
69
72
|
persistent_data = {
|
|
70
|
-
"server.response.body" =>
|
|
73
|
+
"server.response.body.byte_length" => body_bytesize,
|
|
71
74
|
}
|
|
72
|
-
|
|
75
|
+
|
|
76
|
+
if limit > 0 && body_bytesize <= limit
|
|
77
|
+
json = extract_json(container.data)
|
|
78
|
+
persistent_data["server.response.body"] = WAF::JSONString.new(json) if json
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
result = begin
|
|
82
|
+
context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
|
|
83
|
+
rescue WAF::ConversionError
|
|
84
|
+
next stack.call(container)
|
|
85
|
+
end
|
|
73
86
|
|
|
74
87
|
if result.match?
|
|
75
88
|
context.events.push(
|
|
@@ -85,6 +98,20 @@ module Datadog
|
|
|
85
98
|
stack.call(container)
|
|
86
99
|
end
|
|
87
100
|
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def extract_json(body)
|
|
105
|
+
return body unless body.start_with?("/**/")
|
|
106
|
+
return unless body.end_with?(")")
|
|
107
|
+
|
|
108
|
+
# NOTE: Best-effort support for standard Rails JSONP callbacks.
|
|
109
|
+
# Callback expressions with parentheses may prevent extraction
|
|
110
|
+
opening_parenthesis_index = body.b.index("(")
|
|
111
|
+
return unless opening_parenthesis_index
|
|
112
|
+
|
|
113
|
+
body.byteslice(opening_parenthesis_index + 1, body.bytesize - opening_parenthesis_index - 2) #: String
|
|
114
|
+
end
|
|
88
115
|
end
|
|
89
116
|
end
|
|
90
117
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../../../utils/hash_coercion"
|
|
4
3
|
require_relative "../../../instrumentation/gateway/argument"
|
|
5
4
|
|
|
6
5
|
module Datadog
|
|
@@ -8,8 +7,8 @@ module Datadog
|
|
|
8
7
|
module Contrib
|
|
9
8
|
module Rails
|
|
10
9
|
module Patches
|
|
11
|
-
# A patch
|
|
12
|
-
#
|
|
10
|
+
# A patch for `AbstractController::Rendering#render_to_body` that captures
|
|
11
|
+
# the serialized JSON response body for AppSec inspection
|
|
13
12
|
module RenderToBodyPatch
|
|
14
13
|
def render_to_body(options = {})
|
|
15
14
|
return super unless options.key?(:json)
|
|
@@ -17,13 +16,15 @@ module Datadog
|
|
|
17
16
|
context = request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
|
|
18
17
|
return super unless context
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
# NOTE: Custom renderers can return streaming bodies, which we
|
|
20
|
+
# currently do not inspect
|
|
21
|
+
body = super
|
|
22
|
+
return body unless body.is_a?(String)
|
|
22
23
|
|
|
23
|
-
container = Instrumentation::Gateway::DataContainer.new(
|
|
24
|
+
container = Instrumentation::Gateway::DataContainer.new(body, context: context)
|
|
24
25
|
Instrumentation.gateway.push("rails.response.body.json", container)
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
body
|
|
27
28
|
end
|
|
28
29
|
end
|
|
29
30
|
end
|
|
@@ -101,10 +101,22 @@ module Datadog
|
|
|
101
101
|
gateway.watch("sinatra.response.body.json") do |stack, container|
|
|
102
102
|
context = container.context # : Context
|
|
103
103
|
|
|
104
|
+
body_bytesize = container.data.bytesize
|
|
105
|
+
limit = Datadog.configuration.appsec.body_parsing_size_limit
|
|
106
|
+
|
|
104
107
|
persistent_data = {
|
|
105
|
-
"server.response.body" =>
|
|
108
|
+
"server.response.body.byte_length" => body_bytesize,
|
|
106
109
|
}
|
|
107
|
-
|
|
110
|
+
|
|
111
|
+
if limit > 0 && body_bytesize <= limit
|
|
112
|
+
persistent_data["server.response.body"] = WAF::JSONString.new(container.data)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
result = begin
|
|
116
|
+
context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
|
|
117
|
+
rescue WAF::ConversionError
|
|
118
|
+
next stack.call(container)
|
|
119
|
+
end
|
|
108
120
|
|
|
109
121
|
if result.match?
|
|
110
122
|
context.events.push(
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../../../utils/hash_coercion"
|
|
4
3
|
require_relative "../../../instrumentation/gateway/argument"
|
|
5
4
|
|
|
6
5
|
module Datadog
|
|
@@ -8,20 +7,22 @@ module Datadog
|
|
|
8
7
|
module Contrib
|
|
9
8
|
module Sinatra
|
|
10
9
|
module Patches
|
|
11
|
-
# A patch
|
|
12
|
-
# body
|
|
10
|
+
# A patch for `Sinatra::JSON#json` that captures the serialized
|
|
11
|
+
# response body for AppSec inspection
|
|
13
12
|
module JsonPatch
|
|
14
13
|
def json(object, options = {})
|
|
15
14
|
context = @request.env[Datadog::AppSec::Ext::CONTEXT_KEY] # : Context?
|
|
16
15
|
return super unless context
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
# NOTE: Custom renderers can return streaming bodies, which we
|
|
18
|
+
# currently do not inspect
|
|
19
|
+
body = super
|
|
20
|
+
return body unless body.is_a?(String)
|
|
20
21
|
|
|
21
|
-
container = Instrumentation::Gateway::DataContainer.new(
|
|
22
|
+
container = Instrumentation::Gateway::DataContainer.new(body, context: context)
|
|
22
23
|
Instrumentation.gateway.push("sinatra.response.body.json", container)
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
body
|
|
25
26
|
end
|
|
26
27
|
end
|
|
27
28
|
end
|
|
@@ -71,7 +71,8 @@ module Datadog
|
|
|
71
71
|
if is_ruleset_update &&
|
|
72
72
|
(diagnostics.key?("error") ||
|
|
73
73
|
diagnostics.dig("rules", "error") ||
|
|
74
|
-
diagnostics.dig("processors", "
|
|
74
|
+
diagnostics.dig("processors", "error"))
|
|
75
|
+
@waf_builder.remove_config_at_path(path)
|
|
75
76
|
diagnostics = load_default_config(telemetry: AppSec.telemetry)
|
|
76
77
|
@reconfigured_ruleset_version = diagnostics["ruleset_version"]
|
|
77
78
|
report_configuration_diagnostics(diagnostics, action: "update", telemetry: AppSec.telemetry)
|
|
@@ -55,7 +55,7 @@ module Datadog
|
|
|
55
55
|
# extension, remote config off, non-MRI). Under the always-build model
|
|
56
56
|
# it is non-nil even when the DI setting defaults to off, so a built
|
|
57
57
|
# SymbolDatabase component is inert until an upload is actually
|
|
58
|
-
# permitted: the component's own gate
|
|
58
|
+
# permitted: the component's own gate, SymbolDatabase::Component#upload_allowed?,
|
|
59
59
|
# only extracts and uploads when DI is truly active or the customer
|
|
60
60
|
# opted in explicitly. This mirrors DI advertising/building a component
|
|
61
61
|
# by default while installing no probes until it is enabled.
|
|
@@ -288,12 +288,20 @@ module Datadog
|
|
|
288
288
|
end
|
|
289
289
|
end
|
|
290
290
|
|
|
291
|
+
if dynamic_instrumentation && old_state
|
|
292
|
+
dynamic_instrumentation.record_rc_enablement_signal_paths(old_state.rc_enablement_signal_paths)
|
|
293
|
+
end
|
|
294
|
+
|
|
291
295
|
if remote && old_state&.remote_started?
|
|
292
296
|
# The library was reconfigured and previously it already started
|
|
293
297
|
# the remote component (i.e., it received at least one request
|
|
294
298
|
# through the installed Rack middleware which started the remote).
|
|
295
299
|
# If the new configuration also has remote enabled, start the
|
|
296
300
|
# new remote right away.
|
|
301
|
+
#
|
|
302
|
+
# The DI signal-path restore above must precede this: the
|
|
303
|
+
# replacement client's first sync can dispatch the APM_TRACING
|
|
304
|
+
# merge, whose withdrawal branch reads the recorded paths.
|
|
297
305
|
remote.start
|
|
298
306
|
end
|
|
299
307
|
|
|
@@ -405,19 +413,12 @@ module Datadog
|
|
|
405
413
|
# di_implicitly_enabled distinguishes RC-driven start from explicit
|
|
406
414
|
# start (env var or programmatic) so that an explicit
|
|
407
415
|
# `enabled = false` on reconfiguration can take effect.
|
|
408
|
-
|
|
409
|
-
# using_default? — not `!enabled` — because Datadog.configure has
|
|
410
|
-
# already mutated the singleton settings by the time #state runs,
|
|
411
|
-
# so `!enabled` would treat a customer's explicit
|
|
412
|
-
# `enabled = false` as "implicitly enabled" and restart DI.
|
|
413
|
-
# using_default? asks "did the customer ever touch this setting",
|
|
414
|
-
# which is the right question for RC carry-over.
|
|
415
|
-
di_implicit = dynamic_instrumentation&.started? &&
|
|
416
|
-
@settings.dynamic_instrumentation.using_default?(:enabled)
|
|
416
|
+
di_implicit = dynamic_instrumentation&.implicitly_enabled?
|
|
417
417
|
ComponentsState.new(
|
|
418
418
|
telemetry_enabled: telemetry.enabled,
|
|
419
419
|
remote_started: remote&.started?,
|
|
420
420
|
di_implicitly_enabled: di_implicit || false,
|
|
421
|
+
rc_enablement_signal_paths: dynamic_instrumentation&.rc_enablement_signal_paths || [],
|
|
421
422
|
)
|
|
422
423
|
end
|
|
423
424
|
|
|
@@ -5,10 +5,12 @@ module Datadog
|
|
|
5
5
|
module Configuration
|
|
6
6
|
# Stores the state of component tree when replacing the tree.
|
|
7
7
|
class ComponentsState
|
|
8
|
-
def initialize(telemetry_enabled:, remote_started:, di_implicitly_enabled: false
|
|
8
|
+
def initialize(telemetry_enabled:, remote_started:, di_implicitly_enabled: false,
|
|
9
|
+
rc_enablement_signal_paths: [])
|
|
9
10
|
@telemetry_enabled = !!telemetry_enabled
|
|
10
11
|
@remote_started = !!remote_started
|
|
11
12
|
@di_implicitly_enabled = !!di_implicitly_enabled
|
|
13
|
+
@rc_enablement_signal_paths = rc_enablement_signal_paths || []
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def telemetry_enabled?
|
|
@@ -22,6 +24,11 @@ module Datadog
|
|
|
22
24
|
def di_implicitly_enabled?
|
|
23
25
|
@di_implicitly_enabled
|
|
24
26
|
end
|
|
27
|
+
|
|
28
|
+
# The RC enable-signal carrier paths recorded by the replaced tree's DI component.
|
|
29
|
+
#
|
|
30
|
+
# @return [Array<Core::Remote::Configuration::Path>]
|
|
31
|
+
attr_reader :rc_enablement_signal_paths
|
|
25
32
|
end
|
|
26
33
|
end
|
|
27
34
|
end
|
|
@@ -72,6 +72,7 @@ module Datadog
|
|
|
72
72
|
"DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS",
|
|
73
73
|
"DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS",
|
|
74
74
|
"DD_FEATURE_FLAGS_ENABLED",
|
|
75
|
+
"DD_FEATURE_FLAGS_EVALUATION_TELEMETRY_PER_SUBJECT_OPT_OUT_ENABLED",
|
|
75
76
|
"DD_FLAGGING_EVALUATION_COUNTS_ENABLED",
|
|
76
77
|
"DD_GIT_COMMIT_SHA",
|
|
77
78
|
"DD_GIT_REPOSITORY_URL",
|
|
@@ -242,6 +243,7 @@ module Datadog
|
|
|
242
243
|
"DD_TRACE_HTTP_ANALYTICS_ENABLED",
|
|
243
244
|
"DD_TRACE_HTTP_ANALYTICS_SAMPLE_RATE",
|
|
244
245
|
"DD_TRACE_HTTP_CLIENT_ERROR_STATUSES",
|
|
246
|
+
"DD_TRACE_HTTP_CLIENT_RESOURCE_NAME_QUANTIZE",
|
|
245
247
|
"DD_TRACE_HTTP_DISTRIBUTED_TRACING",
|
|
246
248
|
"DD_TRACE_HTTP_ENABLED",
|
|
247
249
|
"DD_TRACE_HTTP_ERROR_STATUS_CODES",
|
|
@@ -16,6 +16,12 @@ module Datadog
|
|
|
16
16
|
|
|
17
17
|
attr_reader :transport, :repository, :id, :dispatcher, :settings, :logger
|
|
18
18
|
|
|
19
|
+
# Whether the initial reconcile is still pending, to run on the first
|
|
20
|
+
# sync whose response contains targets.
|
|
21
|
+
#
|
|
22
|
+
# @return [Boolean]
|
|
23
|
+
attr_reader :pending_initial_reconcile
|
|
24
|
+
|
|
19
25
|
def initialize(transport, capabilities, settings:, logger:, repository: Configuration::Repository.new)
|
|
20
26
|
@transport = transport
|
|
21
27
|
@settings = settings
|
|
@@ -25,27 +31,52 @@ module Datadog
|
|
|
25
31
|
@id = SecureRandom.uuid
|
|
26
32
|
@capabilities = capabilities
|
|
27
33
|
@dispatcher = Dispatcher.new(@capabilities.receivers)
|
|
34
|
+
@pending_initial_reconcile = true
|
|
28
35
|
end
|
|
29
36
|
|
|
37
|
+
# Polls the agent for remote configuration, applies the response to
|
|
38
|
+
# the repository and dispatches the resulting changes to the receivers.
|
|
39
|
+
#
|
|
40
|
+
# The agent sends an empty response when it holds no targets newer
|
|
41
|
+
# than the targets version the client reported. Until the client
|
|
42
|
+
# applies a response that carries targets, it reports the initial
|
|
43
|
+
# targets version, so an empty response then means the agent has not
|
|
44
|
+
# received targets from the backend. The initial reconcile therefore
|
|
45
|
+
# waits for a response that carries targets.
|
|
46
|
+
#
|
|
47
|
+
# @return [void]
|
|
48
|
+
# @raise [TransportError] when the agent reports an internal error
|
|
49
|
+
# @raise [SyncError] when the response cannot be applied to the repository
|
|
30
50
|
def sync
|
|
31
51
|
# TODO: Skip sync if no capabilities are registered
|
|
32
52
|
response = transport.send_config(payload)
|
|
33
53
|
|
|
34
54
|
if response.ok?
|
|
35
|
-
process_response(response)
|
|
55
|
+
changes = process_response(response)
|
|
56
|
+
if pending_initial_reconcile && !response.empty?
|
|
57
|
+
dispatcher.reconcile(changes, repository)
|
|
58
|
+
@pending_initial_reconcile = false
|
|
59
|
+
end
|
|
36
60
|
elsif response.internal_error?
|
|
37
61
|
raise TransportError, response.to_s
|
|
38
62
|
end
|
|
63
|
+
nil
|
|
39
64
|
end
|
|
40
65
|
|
|
41
66
|
private
|
|
42
67
|
|
|
68
|
+
# Applies an OK response to the repository and returns the resulting
|
|
69
|
+
# change set, which is empty when the response is empty.
|
|
70
|
+
#
|
|
71
|
+
# @param response [Transport::Config::Response] the response returned by the agent
|
|
72
|
+
# @return [Configuration::Repository::ChangeSet] the changes applied by this response
|
|
73
|
+
# @raise [SyncError] when the response cannot be applied to the repository
|
|
43
74
|
def process_response(response)
|
|
44
75
|
# when response is completely empty, do nothing as in: leave as is
|
|
45
76
|
if response.empty?
|
|
46
77
|
logger.debug { "remote: empty response => NOOP" }
|
|
47
78
|
|
|
48
|
-
return
|
|
79
|
+
return Configuration::Repository::ChangeSet.new
|
|
49
80
|
end
|
|
50
81
|
|
|
51
82
|
begin
|
|
@@ -61,7 +92,7 @@ module Datadog
|
|
|
61
92
|
end
|
|
62
93
|
|
|
63
94
|
# To make sure steep does not complain
|
|
64
|
-
return unless paths && targets && contents
|
|
95
|
+
return Configuration::Repository::ChangeSet.new unless paths && targets && contents
|
|
65
96
|
|
|
66
97
|
# TODO: sometimes it can strangely be so that paths.empty?
|
|
67
98
|
# TODO: sometimes it can strangely be so that targets.empty?
|
|
@@ -69,6 +100,14 @@ module Datadog
|
|
|
69
100
|
apply_config(paths, targets, contents)
|
|
70
101
|
end
|
|
71
102
|
|
|
103
|
+
# Applies the response to the repository in a transaction, dispatches the
|
|
104
|
+
# resulting changes to the receivers and returns them.
|
|
105
|
+
#
|
|
106
|
+
# @param paths [Array<Configuration::Path>] ingress paths from the response
|
|
107
|
+
# @param targets [Configuration::TargetMap] targets keyed by path
|
|
108
|
+
# @param contents [Configuration::ContentList] contents from the response
|
|
109
|
+
# @return [Configuration::Repository::ChangeSet] the changes applied by the transaction
|
|
110
|
+
# @raise [SyncError] when a path has no matching target or content
|
|
72
111
|
def apply_config(paths, targets, contents)
|
|
73
112
|
changes = repository.transaction do |current, transaction|
|
|
74
113
|
# paths to be removed: previously applied paths minus ingress paths
|
|
@@ -121,6 +160,8 @@ module Datadog
|
|
|
121
160
|
else
|
|
122
161
|
dispatcher.dispatch(changes, repository)
|
|
123
162
|
end
|
|
163
|
+
|
|
164
|
+
changes
|
|
124
165
|
end
|
|
125
166
|
|
|
126
167
|
def payload # standard:disable Metrics/MethodLength
|
|
@@ -23,16 +23,39 @@ module Datadog
|
|
|
23
23
|
|
|
24
24
|
def dispatch(changes, repository)
|
|
25
25
|
receivers.each do |receiver|
|
|
26
|
-
|
|
26
|
+
matching = matching_changes(changes, receiver)
|
|
27
27
|
|
|
28
|
-
receiver.call(repository,
|
|
28
|
+
receiver.call(repository, matching) if matching.any?
|
|
29
29
|
end
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Calls each receiver that declares initial-reconcile participation and has no
|
|
34
|
+
# matching change in `changes`, passing an empty change list.
|
|
35
|
+
def reconcile(changes, repository)
|
|
36
|
+
receivers.each do |receiver|
|
|
37
|
+
next unless receiver.initial_reconcile?
|
|
38
|
+
|
|
39
|
+
receiver.call(repository, []) unless any_matching?(changes, receiver)
|
|
40
|
+
end
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def matching_changes(changes, receiver)
|
|
47
|
+
changes.select { |change| receiver.match?(change.path) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def any_matching?(changes, receiver)
|
|
51
|
+
changes.any? { |change| receiver.match?(change.path) }
|
|
30
52
|
end
|
|
31
53
|
|
|
32
54
|
# Store Matcher and block to be executed on a match
|
|
33
55
|
class Receiver
|
|
34
|
-
def initialize(matcher, &block)
|
|
56
|
+
def initialize(matcher, initial_reconcile: false, &block)
|
|
35
57
|
@block = block
|
|
58
|
+
@initial_reconcile = initial_reconcile
|
|
36
59
|
@matcher = matcher
|
|
37
60
|
end
|
|
38
61
|
|
|
@@ -40,6 +63,14 @@ module Datadog
|
|
|
40
63
|
@matcher.match?(path)
|
|
41
64
|
end
|
|
42
65
|
|
|
66
|
+
# Whether #reconcile may call this receiver with an empty change
|
|
67
|
+
# list on the client's first sync. A participating receiver must
|
|
68
|
+
# act on the repository's current contents when the change list is
|
|
69
|
+
# empty.
|
|
70
|
+
def initial_reconcile?
|
|
71
|
+
@initial_reconcile
|
|
72
|
+
end
|
|
73
|
+
|
|
43
74
|
def call(repository, changes)
|
|
44
75
|
@block.call(repository, changes)
|
|
45
76
|
end
|
|
@@ -32,7 +32,7 @@ module Datadog
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# Workers::Async::Thread settings
|
|
35
|
-
self.fork_policy = options.fetch(:fork_policy, Workers::Async::Thread::
|
|
35
|
+
self.fork_policy = options.fetch(:fork_policy, Workers::Async::Thread::FORK_POLICY_RESTART)
|
|
36
36
|
|
|
37
37
|
# Workers::IntervalLoop settings
|
|
38
38
|
self.loop_base_interval = options.fetch(:interval, DEFAULT_FLUSH_INTERVAL)
|
|
@@ -59,7 +59,7 @@ module Datadog
|
|
|
59
59
|
probe.capture_expressions.each do |capture_expression|
|
|
60
60
|
name = capture_expression.name
|
|
61
61
|
|
|
62
|
-
if
|
|
62
|
+
if Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_second) >= deadline
|
|
63
63
|
output[name] = {notCapturedReason: "timeout"}
|
|
64
64
|
telemetry&.inc(DI::TELEMETRY_NAMESPACE, "capture_expressions_skipped_by_timeout", 1)
|
|
65
65
|
guardrails_telemetry.skipped(
|