datadog 2.9.0 → 2.10.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 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +2 -2
- data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +2 -5
- data/ext/datadog_profiling_native_extension/heap_recorder.c +50 -92
- data/ext/datadog_profiling_native_extension/heap_recorder.h +1 -1
- data/ext/datadog_profiling_native_extension/stack_recorder.c +9 -22
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -1
- data/lib/datadog/appsec/actions_handler.rb +27 -0
- data/lib/datadog/appsec/component.rb +14 -8
- data/lib/datadog/appsec/configuration/settings.rb +9 -0
- data/lib/datadog/appsec/context.rb +28 -8
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +6 -2
- data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +1 -7
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +4 -5
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +15 -12
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +3 -3
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +11 -22
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +5 -4
- data/lib/datadog/appsec/contrib/rails/patcher.rb +3 -13
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +1 -1
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +10 -8
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +3 -26
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +1 -1
- data/lib/datadog/appsec/ext.rb +6 -1
- data/lib/datadog/appsec/metrics/collector.rb +38 -0
- data/lib/datadog/appsec/metrics/exporter.rb +35 -0
- data/lib/datadog/appsec/metrics/telemetry.rb +23 -0
- data/lib/datadog/appsec/metrics.rb +13 -0
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +5 -4
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +1 -1
- data/lib/datadog/appsec/processor.rb +4 -3
- data/lib/datadog/appsec/response.rb +18 -80
- data/lib/datadog/appsec/security_engine/result.rb +67 -0
- data/lib/datadog/appsec/security_engine/runner.rb +88 -0
- data/lib/datadog/appsec/security_engine.rb +9 -0
- data/lib/datadog/appsec.rb +14 -5
- data/lib/datadog/di/component.rb +2 -0
- data/lib/datadog/di/probe_notification_builder.rb +6 -0
- data/lib/datadog/di/redactor.rb +0 -1
- data/lib/datadog/di/remote.rb +26 -5
- data/lib/datadog/tracing/contrib/aws/integration.rb +1 -1
- data/lib/datadog/tracing/contrib/extensions.rb +15 -3
- data/lib/datadog/tracing/contrib/http/integration.rb +3 -0
- data/lib/datadog/version.rb +1 -1
- metadata +32 -18
- data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
- data/lib/datadog/appsec/processor/context.rb +0 -107
@@ -24,7 +24,7 @@ module Datadog
|
|
24
24
|
engine = AppSec::Reactive::Engine.new
|
25
25
|
|
26
26
|
Monitor::Reactive::SetUser.subscribe(engine, context) do |result|
|
27
|
-
if result.
|
27
|
+
if result.match?
|
28
28
|
# TODO: should this hash be an Event instance instead?
|
29
29
|
event = {
|
30
30
|
waf_result: result,
|
@@ -37,12 +37,13 @@ module Datadog
|
|
37
37
|
# We want to keep the trace in case of security event
|
38
38
|
context.trace.keep! if context.trace
|
39
39
|
Datadog::AppSec::Event.tag_and_keep!(context, result)
|
40
|
-
context.
|
40
|
+
context.events << event
|
41
|
+
|
42
|
+
Datadog::AppSec::ActionsHandler.handle(result.actions)
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
44
|
-
|
45
|
-
throw(Datadog::AppSec::Ext::INTERRUPT, event[:actions]) if block
|
46
|
+
Monitor::Reactive::SetUser.publish(engine, user)
|
46
47
|
|
47
48
|
stack.call(user)
|
48
49
|
end
|
@@ -32,7 +32,7 @@ module Datadog
|
|
32
32
|
waf_timeout = Datadog.configuration.appsec.waf_timeout
|
33
33
|
result = context.run_waf(persistent_data, {}, waf_timeout)
|
34
34
|
|
35
|
-
next
|
35
|
+
next unless result.match?
|
36
36
|
|
37
37
|
yield result
|
38
38
|
throw(:block, true) unless result.actions.empty?
|
@@ -1,10 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'security_engine/runner'
|
4
4
|
|
5
5
|
module Datadog
|
6
6
|
module AppSec
|
7
7
|
# Processor integrates libddwaf into datadog/appsec
|
8
|
+
# NOTE: This class will be moved under AppSec::SecurityEngine namespace
|
8
9
|
class Processor
|
9
10
|
attr_reader :diagnostics, :addresses
|
10
11
|
|
@@ -29,8 +30,8 @@ module Datadog
|
|
29
30
|
@handle.finalize
|
30
31
|
end
|
31
32
|
|
32
|
-
def
|
33
|
-
|
33
|
+
def new_runner
|
34
|
+
SecurityEngine::Runner.new(@handle, telemetry: @telemetry)
|
34
35
|
end
|
35
36
|
|
36
37
|
private
|
@@ -19,100 +19,38 @@ module Datadog
|
|
19
19
|
[status, headers, body]
|
20
20
|
end
|
21
21
|
|
22
|
-
def to_sinatra_response
|
23
|
-
::Sinatra::Response.new(body, status, headers)
|
24
|
-
end
|
25
|
-
|
26
|
-
def to_action_dispatch_response
|
27
|
-
::ActionDispatch::Response.new(status, headers, body)
|
28
|
-
end
|
29
|
-
|
30
22
|
class << self
|
31
|
-
def
|
32
|
-
|
33
|
-
configured_response = nil
|
34
|
-
actions.each do |type, parameters|
|
35
|
-
# Need to use next to make steep happy :(
|
36
|
-
# I rather use break to stop the execution
|
37
|
-
next if configured_response
|
38
|
-
|
39
|
-
configured_response = case type
|
40
|
-
when 'block_request'
|
41
|
-
block_response(env, parameters)
|
42
|
-
when 'redirect_request'
|
43
|
-
redirect_response(env, parameters)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
configured_response || default_response(env)
|
48
|
-
end
|
49
|
-
|
50
|
-
def graphql_response(gateway_multiplex)
|
51
|
-
multiplex_return = []
|
52
|
-
gateway_multiplex.queries.each do |query|
|
53
|
-
# This method is only called in places where GraphQL-Ruby is already required
|
54
|
-
query_result = ::GraphQL::Query::Result.new(
|
55
|
-
query: query,
|
56
|
-
values: JSON.parse(content('application/json'))
|
57
|
-
)
|
58
|
-
multiplex_return << query_result
|
59
|
-
end
|
23
|
+
def from_interrupt_params(interrupt_params, http_accept_header)
|
24
|
+
return redirect_response(interrupt_params) if interrupt_params['location']
|
60
25
|
|
61
|
-
|
26
|
+
block_response(interrupt_params, http_accept_header)
|
62
27
|
end
|
63
28
|
|
64
29
|
private
|
65
30
|
|
66
|
-
def
|
67
|
-
content_type =
|
68
|
-
|
69
|
-
|
70
|
-
|
31
|
+
def block_response(interrupt_params, http_accept_header)
|
32
|
+
content_type = case interrupt_params['type']
|
33
|
+
when nil, 'auto' then content_type(http_accept_header)
|
34
|
+
else FORMAT_TO_CONTENT_TYPE.fetch(interrupt_params['type'], DEFAULT_CONTENT_TYPE)
|
35
|
+
end
|
71
36
|
|
72
37
|
Response.new(
|
73
|
-
status: 403,
|
38
|
+
status: interrupt_params['status_code']&.to_i || 403,
|
74
39
|
headers: { 'Content-Type' => content_type },
|
75
|
-
body:
|
40
|
+
body: [content(content_type)],
|
76
41
|
)
|
77
42
|
end
|
78
43
|
|
79
|
-
def
|
80
|
-
|
81
|
-
content_type(env)
|
82
|
-
else
|
83
|
-
FORMAT_TO_CONTENT_TYPE[options['type']]
|
84
|
-
end
|
85
|
-
|
86
|
-
body = []
|
87
|
-
body << content(content_type)
|
44
|
+
def redirect_response(interrupt_params)
|
45
|
+
status_code = interrupt_params['status_code'].to_i
|
88
46
|
|
89
47
|
Response.new(
|
90
|
-
status:
|
91
|
-
headers: { '
|
92
|
-
body:
|
48
|
+
status: (status_code >= 300 && status_code < 400 ? status_code : 303),
|
49
|
+
headers: { 'Location' => interrupt_params.fetch('location') },
|
50
|
+
body: [],
|
93
51
|
)
|
94
52
|
end
|
95
53
|
|
96
|
-
def redirect_response(env, options)
|
97
|
-
if options['location'] && !options['location'].empty?
|
98
|
-
content_type = content_type(env)
|
99
|
-
|
100
|
-
headers = {
|
101
|
-
'Content-Type' => content_type,
|
102
|
-
'Location' => options['location']
|
103
|
-
}
|
104
|
-
|
105
|
-
status_code = options['status_code'].to_i
|
106
|
-
Response.new(
|
107
|
-
status: (status_code >= 300 && status_code < 400 ? status_code : 303),
|
108
|
-
headers: headers,
|
109
|
-
body: [],
|
110
|
-
)
|
111
|
-
else
|
112
|
-
default_response(env)
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
54
|
CONTENT_TYPE_TO_FORMAT = {
|
117
55
|
'application/json' => :json,
|
118
56
|
'text/html' => :html,
|
@@ -126,10 +64,10 @@ module Datadog
|
|
126
64
|
|
127
65
|
DEFAULT_CONTENT_TYPE = 'application/json'
|
128
66
|
|
129
|
-
def content_type(
|
130
|
-
return DEFAULT_CONTENT_TYPE
|
67
|
+
def content_type(http_accept_header)
|
68
|
+
return DEFAULT_CONTENT_TYPE if http_accept_header.nil?
|
131
69
|
|
132
|
-
accept_types =
|
70
|
+
accept_types = http_accept_header.split(',').map(&:strip)
|
133
71
|
|
134
72
|
accepted = accept_types.map { |m| Utils::HTTP::MediaRange.new(m) }.sort!.reverse!
|
135
73
|
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module AppSec
|
5
|
+
module SecurityEngine
|
6
|
+
# A namespace for value-objects representing the result of WAF check.
|
7
|
+
module Result
|
8
|
+
# A generic result without indication of its type.
|
9
|
+
class Base
|
10
|
+
attr_reader :events, :actions, :derivatives, :duration_ns, :duration_ext_ns
|
11
|
+
|
12
|
+
def initialize(events:, actions:, derivatives:, timeout:, duration_ns:, duration_ext_ns:)
|
13
|
+
@events = events
|
14
|
+
@actions = actions
|
15
|
+
@derivatives = derivatives
|
16
|
+
|
17
|
+
@timeout = timeout
|
18
|
+
@duration_ns = duration_ns
|
19
|
+
@duration_ext_ns = duration_ext_ns
|
20
|
+
end
|
21
|
+
|
22
|
+
def timeout?
|
23
|
+
!!@timeout
|
24
|
+
end
|
25
|
+
|
26
|
+
def match?
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# A result that indicates a security rule match
|
32
|
+
class Match < Base
|
33
|
+
def match?
|
34
|
+
true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# A result that indicates a successful security rules check without a match
|
39
|
+
class Ok < Base
|
40
|
+
def match?
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# A result that indicates an internal security library error
|
46
|
+
class Error
|
47
|
+
attr_reader :events, :actions, :derivatives, :duration_ns, :duration_ext_ns
|
48
|
+
|
49
|
+
def initialize(duration_ext_ns:)
|
50
|
+
@events = []
|
51
|
+
@actions = @derivatives = {}
|
52
|
+
@duration_ns = 0
|
53
|
+
@duration_ext_ns = duration_ext_ns
|
54
|
+
end
|
55
|
+
|
56
|
+
def timeout?
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
def match?
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'result'
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module AppSec
|
7
|
+
module SecurityEngine
|
8
|
+
# A class that check input via security engine (WAF) and respond with result.
|
9
|
+
class Runner
|
10
|
+
SUCCESSFUL_EXECUTION_CODES = [:ok, :match].freeze
|
11
|
+
|
12
|
+
def initialize(handle, telemetry:)
|
13
|
+
@mutex = Mutex.new
|
14
|
+
@context = WAF::Context.new(handle)
|
15
|
+
@telemetry = telemetry
|
16
|
+
|
17
|
+
@debug_tag = "libddwaf:#{WAF::VERSION::STRING} method:ddwaf_run"
|
18
|
+
end
|
19
|
+
|
20
|
+
def run(persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
|
21
|
+
@mutex.lock
|
22
|
+
|
23
|
+
start_ns = Core::Utils::Time.get_time(:nanosecond)
|
24
|
+
persistent_data.reject! do |_, v|
|
25
|
+
next false if v.is_a?(TrueClass) || v.is_a?(FalseClass)
|
26
|
+
|
27
|
+
v.nil? ? true : v.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
ephemeral_data.reject! do |_, v|
|
31
|
+
next false if v.is_a?(TrueClass) || v.is_a?(FalseClass)
|
32
|
+
|
33
|
+
v.nil? ? true : v.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
_code, result = try_run(persistent_data, ephemeral_data, timeout)
|
37
|
+
stop_ns = Core::Utils::Time.get_time(:nanosecond)
|
38
|
+
|
39
|
+
report_execution(result)
|
40
|
+
|
41
|
+
unless SUCCESSFUL_EXECUTION_CODES.include?(result.status)
|
42
|
+
return Result::Error.new(duration_ext_ns: stop_ns - start_ns)
|
43
|
+
end
|
44
|
+
|
45
|
+
klass = result.status == :match ? Result::Match : Result::Ok
|
46
|
+
klass.new(
|
47
|
+
events: result.events,
|
48
|
+
actions: result.actions,
|
49
|
+
derivatives: result.derivatives,
|
50
|
+
timeout: result.timeout,
|
51
|
+
duration_ns: result.total_runtime,
|
52
|
+
duration_ext_ns: (stop_ns - start_ns)
|
53
|
+
)
|
54
|
+
ensure
|
55
|
+
@mutex.unlock
|
56
|
+
end
|
57
|
+
|
58
|
+
def finalize
|
59
|
+
@context.finalize
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def try_run(persistent_data, ephemeral_data, timeout)
|
65
|
+
@context.run(persistent_data, ephemeral_data, timeout)
|
66
|
+
rescue WAF::LibDDWAF::Error => e
|
67
|
+
Datadog.logger.debug { "#{@debug_tag} execution error: #{e} backtrace: #{e.backtrace&.first(3)}" }
|
68
|
+
@telemetry.report(e, description: 'libddwaf-rb internal low-level error')
|
69
|
+
|
70
|
+
[:err_internal, WAF::Result.new(:err_internal, [], 0, false, [], [])]
|
71
|
+
end
|
72
|
+
|
73
|
+
def report_execution(result)
|
74
|
+
Datadog.logger.debug { "#{@debug_tag} execution timed out: #{result.inspect}" } if result.timeout
|
75
|
+
|
76
|
+
if SUCCESSFUL_EXECUTION_CODES.include?(result.status)
|
77
|
+
Datadog.logger.debug { "#{@debug_tag} execution result: #{result.inspect}" }
|
78
|
+
else
|
79
|
+
message = "#{@debug_tag} execution error: #{result.status.inspect}"
|
80
|
+
|
81
|
+
Datadog.logger.debug { message }
|
82
|
+
@telemetry.error(message)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/datadog/appsec.rb
CHANGED
@@ -14,19 +14,24 @@ module Datadog
|
|
14
14
|
Datadog.configuration.appsec.enabled
|
15
15
|
end
|
16
16
|
|
17
|
+
def rasp_enabled?
|
18
|
+
Datadog.configuration.appsec.rasp_enabled
|
19
|
+
end
|
20
|
+
|
17
21
|
def active_context
|
18
22
|
Datadog::AppSec::Context.active
|
19
23
|
end
|
20
24
|
|
21
|
-
def
|
22
|
-
|
25
|
+
def telemetry
|
26
|
+
components.appsec&.telemetry
|
27
|
+
end
|
23
28
|
|
24
|
-
|
29
|
+
def processor
|
30
|
+
components.appsec&.processor
|
25
31
|
end
|
26
32
|
|
27
33
|
def reconfigure(ruleset:, telemetry:)
|
28
34
|
appsec_component = components.appsec
|
29
|
-
|
30
35
|
return unless appsec_component
|
31
36
|
|
32
37
|
appsec_component.reconfigure(ruleset: ruleset, telemetry: telemetry)
|
@@ -34,12 +39,16 @@ module Datadog
|
|
34
39
|
|
35
40
|
def reconfigure_lock(&block)
|
36
41
|
appsec_component = components.appsec
|
37
|
-
|
38
42
|
return unless appsec_component
|
39
43
|
|
40
44
|
appsec_component.reconfigure_lock(&block)
|
41
45
|
end
|
42
46
|
|
47
|
+
def api_security_enabled?
|
48
|
+
Datadog.configuration.appsec.api_security.enabled &&
|
49
|
+
Datadog.configuration.appsec.api_security.sample_rate.sample?
|
50
|
+
end
|
51
|
+
|
43
52
|
private
|
44
53
|
|
45
54
|
def components
|
data/lib/datadog/di/component.rb
CHANGED
@@ -76,6 +76,7 @@ module Datadog
|
|
76
76
|
@agent_settings = agent_settings
|
77
77
|
@logger = logger
|
78
78
|
@telemetry = telemetry
|
79
|
+
@code_tracker = code_tracker
|
79
80
|
@redactor = Redactor.new(settings)
|
80
81
|
@serializer = Serializer.new(settings, redactor, telemetry: telemetry)
|
81
82
|
@instrumenter = Instrumenter.new(settings, serializer, logger, code_tracker: code_tracker, telemetry: telemetry)
|
@@ -90,6 +91,7 @@ module Datadog
|
|
90
91
|
attr_reader :agent_settings
|
91
92
|
attr_reader :logger
|
92
93
|
attr_reader :telemetry
|
94
|
+
attr_reader :code_tracker
|
93
95
|
attr_reader :instrumenter
|
94
96
|
attr_reader :transport
|
95
97
|
attr_reader :probe_notifier_worker
|
@@ -32,6 +32,12 @@ module Datadog
|
|
32
32
|
status: 'EMITTING',)
|
33
33
|
end
|
34
34
|
|
35
|
+
def build_errored(probe, exc)
|
36
|
+
build_status(probe,
|
37
|
+
message: "Instrumentation for probe #{probe.id} failed: #{exc}",
|
38
|
+
status: 'ERROR',)
|
39
|
+
end
|
40
|
+
|
35
41
|
# Duration is in seconds.
|
36
42
|
def build_executed(probe,
|
37
43
|
trace_point: nil, rv: nil, duration: nil, caller_locations: nil,
|
data/lib/datadog/di/redactor.rb
CHANGED
data/lib/datadog/di/remote.rb
CHANGED
@@ -49,28 +49,48 @@ module Datadog
|
|
49
49
|
begin
|
50
50
|
probe_spec = parse_content(content)
|
51
51
|
probe = ProbeBuilder.build_from_remote_config(probe_spec)
|
52
|
-
|
53
|
-
|
52
|
+
probe_notification_builder = component.probe_notification_builder
|
53
|
+
payload = probe_notification_builder.build_received(probe)
|
54
|
+
probe_notifier_worker = component.probe_notifier_worker
|
55
|
+
probe_notifier_worker.add_status(payload)
|
54
56
|
component.logger.debug { "di: received probe from RC: #{probe.type} #{probe.location}" }
|
55
57
|
|
56
58
|
begin
|
57
59
|
# TODO test exception capture
|
58
60
|
probe_manager.add_probe(probe)
|
59
61
|
content.applied
|
62
|
+
rescue DI::Error::DITargetNotInRegistry => exc
|
63
|
+
component.telemetry&.report(exc, description: "Line probe is targeting a loaded file that is not in code tracker")
|
64
|
+
|
65
|
+
payload = probe_notification_builder.build_errored(probe, exc)
|
66
|
+
probe_notifier_worker.add_status(payload)
|
67
|
+
|
68
|
+
# If a probe fails to install, we will mark the content
|
69
|
+
# as errored. On subsequent remote configuration application
|
70
|
+
# attemps, probe manager will raise the "previously errored"
|
71
|
+
# exception and we'll rescue it here, again marking the
|
72
|
+
# content as errored but with a somewhat different exception
|
73
|
+
# message.
|
74
|
+
# TODO assert content state (errored for this example)
|
75
|
+
content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}")
|
60
76
|
rescue => exc
|
61
77
|
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
62
78
|
|
63
79
|
component.logger.debug { "di: unhandled exception adding probe in DI remote receiver: #{exc.class}: #{exc}" }
|
64
80
|
component.telemetry&.report(exc, description: "Unhandled exception adding probe in DI remote receiver")
|
65
81
|
|
82
|
+
# TODO test this path
|
83
|
+
payload = probe_notification_builder.build_errored(probe, exc)
|
84
|
+
probe_notifier_worker.add_status(payload)
|
85
|
+
|
66
86
|
# If a probe fails to install, we will mark the content
|
67
87
|
# as errored. On subsequent remote configuration application
|
68
88
|
# attemps, probe manager will raise the "previously errored"
|
69
89
|
# exception and we'll rescue it here, again marking the
|
70
90
|
# content as errored but with a somewhat different exception
|
71
91
|
# message.
|
72
|
-
# TODO
|
73
|
-
content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}
|
92
|
+
# TODO assert content state (errored for this example)
|
93
|
+
content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}")
|
74
94
|
end
|
75
95
|
|
76
96
|
# Important: even if processing fails for this probe config,
|
@@ -84,7 +104,8 @@ module Datadog
|
|
84
104
|
component.logger.debug { "di: unhandled exception handling probe in DI remote receiver: #{exc.class}: #{exc}" }
|
85
105
|
component.telemetry&.report(exc, description: "Unhandled exception handling probe in DI remote receiver")
|
86
106
|
|
87
|
-
|
107
|
+
# TODO assert content state (errored for this example)
|
108
|
+
content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}")
|
88
109
|
end
|
89
110
|
end
|
90
111
|
end
|
@@ -110,6 +110,13 @@ module Datadog
|
|
110
110
|
module Settings
|
111
111
|
InvalidIntegrationError = Class.new(StandardError)
|
112
112
|
|
113
|
+
# Used to avoid concurrency issues between registering integrations (e.g. mutation) and reporting the
|
114
|
+
# current integrations for logging/debugging/telemetry purposes (e.g. iteration) in the
|
115
|
+
# `@instrumented_integrations` hash.
|
116
|
+
#
|
117
|
+
# See https://github.com/DataDog/dd-trace-rb/issues/2851 for details on the original issue.
|
118
|
+
INSTRUMENTED_INTEGRATIONS_LOCK = Mutex.new
|
119
|
+
|
113
120
|
def self.included(base)
|
114
121
|
base.class_eval do
|
115
122
|
settings :contrib do
|
@@ -161,7 +168,10 @@ module Datadog
|
|
161
168
|
configuration_name = options[:describes] || :default
|
162
169
|
filtered_options = options.reject { |k, _v| k == :describes }
|
163
170
|
integration.configure(configuration_name, filtered_options, &block)
|
164
|
-
|
171
|
+
INSTRUMENTED_INTEGRATIONS_LOCK.synchronize do
|
172
|
+
@instrumented_integrations ||= {}
|
173
|
+
@instrumented_integrations[integration_name] = integration
|
174
|
+
end
|
165
175
|
|
166
176
|
# Add to activation list
|
167
177
|
integrations_pending_activation << integration
|
@@ -192,14 +202,16 @@ module Datadog
|
|
192
202
|
@integrations_pending_activation ||= Set.new
|
193
203
|
end
|
194
204
|
|
205
|
+
# This method is only for logging/debugging/telemetry purposes (e.g. iteration) in the
|
206
|
+
# `@instrumented_integrations` hash.
|
195
207
|
# @!visibility private
|
196
208
|
def instrumented_integrations
|
197
|
-
@instrumented_integrations
|
209
|
+
INSTRUMENTED_INTEGRATIONS_LOCK.synchronize { (@instrumented_integrations&.dup || {}).freeze }
|
198
210
|
end
|
199
211
|
|
200
212
|
# @!visibility private
|
201
213
|
def reset!
|
202
|
-
instrumented_integrations
|
214
|
+
INSTRUMENTED_INTEGRATIONS_LOCK.synchronize { @instrumented_integrations&.clear }
|
203
215
|
super
|
204
216
|
end
|
205
217
|
|