datadog 2.7.1 → 2.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -1
- data/ext/datadog_profiling_native_extension/clock_id.h +2 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +64 -54
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +1 -1
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +16 -16
- data/ext/datadog_profiling_native_extension/collectors_stack.c +7 -7
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +259 -132
- data/ext/datadog_profiling_native_extension/extconf.rb +0 -8
- data/ext/datadog_profiling_native_extension/heap_recorder.c +11 -89
- data/ext/datadog_profiling_native_extension/heap_recorder.h +1 -1
- data/ext/datadog_profiling_native_extension/http_transport.c +4 -4
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +4 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -1
- data/ext/datadog_profiling_native_extension/profiling.c +10 -8
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +8 -8
- data/ext/datadog_profiling_native_extension/stack_recorder.c +54 -88
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -1
- data/ext/datadog_profiling_native_extension/time_helpers.h +1 -1
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +47 -0
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +31 -0
- data/ext/libdatadog_api/crashtracker.c +3 -0
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +355 -157
- data/lib/datadog/appsec/assets/waf_rules/strict.json +62 -32
- data/lib/datadog/appsec/component.rb +1 -8
- data/lib/datadog/appsec/context.rb +54 -0
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +73 -0
- data/lib/datadog/appsec/contrib/active_record/integration.rb +41 -0
- data/lib/datadog/appsec/contrib/active_record/patcher.rb +53 -0
- data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +6 -6
- data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +4 -4
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +19 -28
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +5 -5
- data/lib/datadog/appsec/contrib/rack/gateway/response.rb +3 -3
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +64 -96
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +10 -10
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +5 -5
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +6 -6
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +10 -11
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +43 -49
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +21 -32
- data/lib/datadog/appsec/contrib/rails/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +6 -6
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +41 -63
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +2 -2
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +5 -5
- data/lib/datadog/appsec/event.rb +6 -6
- data/lib/datadog/appsec/ext.rb +3 -1
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +22 -32
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +5 -5
- data/lib/datadog/appsec/processor/context.rb +2 -2
- data/lib/datadog/appsec/processor/rule_loader.rb +0 -3
- data/lib/datadog/appsec/remote.rb +1 -3
- data/lib/datadog/appsec/response.rb +7 -11
- data/lib/datadog/appsec.rb +6 -5
- data/lib/datadog/auto_instrument.rb +3 -0
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +39 -11
- data/lib/datadog/core/configuration/components.rb +20 -2
- data/lib/datadog/core/configuration/settings.rb +10 -0
- data/lib/datadog/core/configuration.rb +10 -2
- data/lib/datadog/{tracing → core}/contrib/rails/utils.rb +1 -3
- data/lib/datadog/core/crashtracking/component.rb +1 -3
- data/lib/datadog/core/remote/client/capabilities.rb +6 -0
- data/lib/datadog/core/remote/client.rb +65 -59
- data/lib/datadog/core/telemetry/component.rb +9 -3
- data/lib/datadog/core/telemetry/event.rb +87 -3
- data/lib/datadog/core/telemetry/ext.rb +1 -0
- data/lib/datadog/core/telemetry/logging.rb +2 -2
- data/lib/datadog/core/telemetry/metric.rb +22 -0
- data/lib/datadog/core/telemetry/worker.rb +33 -0
- data/lib/datadog/di/base.rb +115 -0
- data/lib/datadog/di/code_tracker.rb +11 -7
- data/lib/datadog/di/component.rb +21 -11
- data/lib/datadog/di/configuration/settings.rb +11 -1
- data/lib/datadog/di/contrib/active_record.rb +1 -0
- data/lib/datadog/di/contrib/railtie.rb +15 -0
- data/lib/datadog/di/contrib.rb +26 -0
- data/lib/datadog/di/error.rb +5 -0
- data/lib/datadog/di/instrumenter.rb +111 -20
- data/lib/datadog/di/preload.rb +18 -0
- data/lib/datadog/di/probe.rb +11 -1
- data/lib/datadog/di/probe_builder.rb +1 -0
- data/lib/datadog/di/probe_manager.rb +8 -5
- data/lib/datadog/di/probe_notification_builder.rb +27 -7
- data/lib/datadog/di/probe_notifier_worker.rb +5 -6
- data/lib/datadog/di/remote.rb +124 -0
- data/lib/datadog/di/serializer.rb +14 -7
- data/lib/datadog/di/transport.rb +3 -5
- data/lib/datadog/di/utils.rb +7 -0
- data/lib/datadog/di.rb +23 -62
- data/lib/datadog/kit/appsec/events.rb +3 -3
- data/lib/datadog/kit/identity.rb +4 -4
- data/lib/datadog/profiling/component.rb +59 -69
- data/lib/datadog/profiling/http_transport.rb +1 -26
- data/lib/datadog/tracing/configuration/settings.rb +4 -8
- data/lib/datadog/tracing/contrib/action_cable/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/action_mailer/integration.rb +6 -2
- data/lib/datadog/tracing/contrib/action_pack/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/action_view/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/active_job/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/active_record/integration.rb +6 -2
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +3 -1
- data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +3 -1
- data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +16 -4
- data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +10 -0
- data/lib/datadog/tracing/contrib/active_support/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/auto_instrument.rb +2 -2
- data/lib/datadog/tracing/contrib/aws/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +4 -0
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
- data/lib/datadog/tracing/contrib/httprb/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/kafka/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/mongodb/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/opensearch/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/presto/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/rack/integration.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/framework.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/rest_client/integration.rb +3 -0
- data/lib/datadog/tracing/span.rb +12 -4
- data/lib/datadog/tracing/span_event.rb +123 -3
- data/lib/datadog/tracing/span_operation.rb +6 -0
- data/lib/datadog/tracing/transport/serializable_trace.rb +24 -6
- data/lib/datadog/version.rb +2 -2
- data/lib/datadog.rb +3 -0
- metadata +30 -17
- data/lib/datadog/appsec/processor/actions.rb +0 -49
- data/lib/datadog/appsec/reactive/operation.rb +0 -68
- data/lib/datadog/appsec/scope.rb +0 -58
- data/lib/datadog/core/crashtracking/agent_base_url.rb +0 -21
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is loaded by datadog/di/preload.rb.
|
4
|
+
# It contains just the global DI reference to the (normally one and only)
|
5
|
+
# code tracker for the current process.
|
6
|
+
# This file should not require the rest of DI, specifically none of the
|
7
|
+
# contrib code that is meant to be loaded after third-party libraries
|
8
|
+
# are loaded, and also none of the rest of datadog library which also
|
9
|
+
# has contrib code in other products.
|
10
|
+
|
11
|
+
require_relative 'code_tracker'
|
12
|
+
|
13
|
+
module Datadog
|
14
|
+
# Namespace for Datadog dynamic instrumentation.
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
module DI
|
18
|
+
LOCK = Mutex.new
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_reader :code_tracker
|
22
|
+
|
23
|
+
# Activates code tracking. Normally this method should be called
|
24
|
+
# when the application starts. If instrumenting third-party code,
|
25
|
+
# code tracking needs to be enabled before the third-party libraries
|
26
|
+
# are loaded. Any third-party code loaded before code tracking is
|
27
|
+
# activated will NOT be instrumentable using dynamic instrumentation.
|
28
|
+
#
|
29
|
+
# TODO test that activating tracker multiple times preserves
|
30
|
+
# existing mappings in the registry
|
31
|
+
def activate_tracking!
|
32
|
+
(@code_tracker ||= CodeTracker.new).start
|
33
|
+
end
|
34
|
+
|
35
|
+
# Activates code tracking if possible.
|
36
|
+
#
|
37
|
+
# This method does nothing if invoked in an environment that does not
|
38
|
+
# implement required trace points for code tracking (MRI Ruby < 2.6,
|
39
|
+
# JRuby) and rescues any exceptions that may be raised by downstream
|
40
|
+
# DI code.
|
41
|
+
def activate_tracking
|
42
|
+
# :script_compiled trace point was added in Ruby 2.6.
|
43
|
+
return unless RUBY_VERSION >= '2.6'
|
44
|
+
|
45
|
+
begin
|
46
|
+
# Activate code tracking by default because line trace points will not work
|
47
|
+
# without it.
|
48
|
+
Datadog::DI.activate_tracking!
|
49
|
+
rescue => exc
|
50
|
+
if defined?(Datadog.logger)
|
51
|
+
Datadog.logger.warn { "di: Failed to activate code tracking for DI: #{exc.class}: #{exc}" }
|
52
|
+
else
|
53
|
+
# We do not have Datadog logger potentially because DI code tracker is
|
54
|
+
# being loaded early in application boot process and the rest of datadog
|
55
|
+
# wasn't loaded yet. Output to standard error.
|
56
|
+
warn("datadog: di: Failed to activate code tracking for DI: #{exc.class}: #{exc}")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Deactivates code tracking. In normal usage of DI this method should
|
62
|
+
# never be called, however it is used by DI's test suite to reset
|
63
|
+
# state for individual tests.
|
64
|
+
#
|
65
|
+
# Note that deactivating tracking clears out the registry, losing
|
66
|
+
# the ability to look up files that have been loaded into the process
|
67
|
+
# already.
|
68
|
+
def deactivate_tracking!
|
69
|
+
code_tracker&.stop
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns whether code tracking is available.
|
73
|
+
# This method should be used instead of querying #code_tracker
|
74
|
+
# because the latter one may be nil.
|
75
|
+
def code_tracking_active?
|
76
|
+
code_tracker&.active? || false
|
77
|
+
end
|
78
|
+
|
79
|
+
# DI code tracker is instantiated globally before the regular set of
|
80
|
+
# components is created, but the code tracker needs to call out to the
|
81
|
+
# "current" DI component to perform instrumentation when application
|
82
|
+
# code is loaded. Because this call may happen prior to Datadog
|
83
|
+
# components having been initialized, we maintain the "current component"
|
84
|
+
# which contains a reference to the most recently instantiated
|
85
|
+
# DI::Component. This way, if a DI component hasn't been instantiated,
|
86
|
+
# we do not try to reference Datadog.components.
|
87
|
+
# In other words, this method exists so that we never attempt to call
|
88
|
+
# Datadog.components from the code tracker.
|
89
|
+
def current_component
|
90
|
+
LOCK.synchronize do
|
91
|
+
@current_components&.last
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# To avoid potential races with DI::Component being added and removed,
|
96
|
+
# we maintain a list of the components. Normally the list should contain
|
97
|
+
# either zero or one component depending on whether DI is enabled in
|
98
|
+
# Datadog configuration. However, if a new instance of DI::Component
|
99
|
+
# is created while the previous instance is still running, we are
|
100
|
+
# guaranteed to not end up with no component when one is running.
|
101
|
+
def add_current_component(component)
|
102
|
+
LOCK.synchronize do
|
103
|
+
@current_components ||= []
|
104
|
+
@current_components << component
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def remove_current_component(component)
|
109
|
+
LOCK.synchronize do
|
110
|
+
@current_components&.delete(component)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
# rubocop:disable Lint/AssignmentInCondition
|
4
4
|
|
5
|
+
require_relative 'error'
|
6
|
+
|
5
7
|
module Datadog
|
6
8
|
module DI
|
7
9
|
# Tracks loaded Ruby code by source file and maintains a map from
|
@@ -78,19 +80,21 @@ module Datadog
|
|
78
80
|
registry_lock.synchronize do
|
79
81
|
registry[path] = tp.instruction_sequence
|
80
82
|
end
|
81
|
-
end
|
82
|
-
|
83
|
-
DI.component&.probe_manager&.install_pending_line_probes(path)
|
84
83
|
|
84
|
+
# Also, pending line probes should only be installed for
|
85
|
+
# non-eval'd code.
|
86
|
+
DI.current_component&.probe_manager&.install_pending_line_probes(path)
|
87
|
+
end
|
85
88
|
# Since this method normally is called from customer applications,
|
86
89
|
# rescue any exceptions that might not be handled to not break said
|
87
90
|
# customer applications.
|
88
91
|
rescue => exc
|
89
|
-
#
|
90
|
-
#
|
91
|
-
|
92
|
+
# Code tracker may be loaded without the rest of DI,
|
93
|
+
# in which case DI.component will not yet be defined,
|
94
|
+
# but we will have DI.current_component (set to nil).
|
95
|
+
if component = DI.current_component
|
92
96
|
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
93
|
-
component.logger.
|
97
|
+
component.logger.debug { "di: unhandled exception in script_compiled trace point: #{exc.class}: #{exc}" }
|
94
98
|
component.telemetry&.report(exc, description: "Unhandled exception in script_compiled trace point")
|
95
99
|
# TODO test this path
|
96
100
|
else
|
data/lib/datadog/di/component.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../core'
|
4
|
+
|
3
5
|
module Datadog
|
4
6
|
module DI
|
5
7
|
# Component for dynamic instrumentation.
|
@@ -14,20 +16,22 @@ module Datadog
|
|
14
16
|
# resources and installed tracepoints upon shutdown.
|
15
17
|
class Component
|
16
18
|
class << self
|
17
|
-
def build(settings, agent_settings, telemetry: nil)
|
19
|
+
def build(settings, agent_settings, logger, telemetry: nil)
|
18
20
|
return unless settings.respond_to?(:dynamic_instrumentation) && settings.dynamic_instrumentation.enabled
|
19
21
|
|
20
22
|
unless settings.respond_to?(:remote) && settings.remote.enabled
|
21
|
-
|
23
|
+
logger.warn("di: dynamic instrumentation could not be enabled because Remote Configuration Management is not available. To enable Remote Configuration, see https://docs.datadoghq.com/agent/remote_config")
|
22
24
|
return
|
23
25
|
end
|
24
26
|
|
25
|
-
return unless environment_supported?(settings)
|
27
|
+
return unless environment_supported?(settings, logger)
|
26
28
|
|
27
|
-
new(settings, agent_settings,
|
29
|
+
new(settings, agent_settings, logger, code_tracker: DI.code_tracker, telemetry: telemetry).tap do |component|
|
30
|
+
DI.add_current_component(component)
|
31
|
+
end
|
28
32
|
end
|
29
33
|
|
30
|
-
def build!(settings, agent_settings, telemetry: nil)
|
34
|
+
def build!(settings, agent_settings, logger, telemetry: nil)
|
31
35
|
unless settings.respond_to?(:dynamic_instrumentation) && settings.dynamic_instrumentation.enabled
|
32
36
|
raise "Requested DI component but DI is not enabled in settings"
|
33
37
|
end
|
@@ -36,27 +40,31 @@ module Datadog
|
|
36
40
|
raise "Requested DI component but remote config is not enabled in settings"
|
37
41
|
end
|
38
42
|
|
39
|
-
unless environment_supported?(settings)
|
43
|
+
unless environment_supported?(settings, logger)
|
40
44
|
raise "DI does not support the environment (development or Ruby version too low or not MRI)"
|
41
45
|
end
|
42
46
|
|
43
|
-
new(settings, agent_settings,
|
47
|
+
new(settings, agent_settings, logger, code_tracker: DI.code_tracker, telemetry: telemetry)
|
44
48
|
end
|
45
49
|
|
46
50
|
# Checks whether the runtime environment is supported by
|
47
51
|
# dynamic instrumentation. Currently we only require that, if Rails
|
48
52
|
# is used, that Rails environment is not development because
|
49
53
|
# DI does not currently support code unloading and reloading.
|
50
|
-
def environment_supported?(settings)
|
54
|
+
def environment_supported?(settings, logger)
|
51
55
|
# TODO add tests?
|
52
56
|
unless settings.dynamic_instrumentation.internal.development
|
53
57
|
if Datadog::Core::Environment::Execution.development?
|
54
|
-
|
58
|
+
logger.warn("di: development environment detected; not enabling dynamic instrumentation")
|
55
59
|
return false
|
56
60
|
end
|
57
61
|
end
|
58
|
-
if RUBY_ENGINE != 'ruby'
|
59
|
-
|
62
|
+
if RUBY_ENGINE != 'ruby'
|
63
|
+
logger.warn("di: cannot enable dynamic instrumentation: MRI is required, but running on #{RUBY_ENGINE}")
|
64
|
+
return false
|
65
|
+
end
|
66
|
+
if RUBY_VERSION < '2.6'
|
67
|
+
logger.warn("di: cannot enable dynamic instrumentation: Ruby 2.6+ is required, but running on #{RUBY_VERSION}")
|
60
68
|
return false
|
61
69
|
end
|
62
70
|
true
|
@@ -99,6 +107,8 @@ module Datadog
|
|
99
107
|
# was replaced by a new instance, the new instance of it wouldn't have
|
100
108
|
# any of the already loaded code tracked.
|
101
109
|
def shutdown!(replacement = nil)
|
110
|
+
DI.remove_current_component(self)
|
111
|
+
|
102
112
|
probe_manager.clear_hooks
|
103
113
|
probe_manager.close
|
104
114
|
probe_notifier_worker.stop
|
@@ -166,10 +166,20 @@ module Datadog
|
|
166
166
|
# being sent out by the probe notifier worker) and creates a
|
167
167
|
# possibility of dropping payloads if the queue gets too long.
|
168
168
|
option :min_send_interval do |o|
|
169
|
-
o.type :
|
169
|
+
o.type :float
|
170
170
|
o.default 3
|
171
171
|
end
|
172
172
|
|
173
|
+
# Number of snapshots that can be stored in the probe
|
174
|
+
# notifier worker queue. Larger capacity runs the risk of
|
175
|
+
# creating snapshots that exceed the agent's request size
|
176
|
+
# limit. Smaller capacity increases the risk of dropping
|
177
|
+
# snapshots.
|
178
|
+
option :snapshot_queue_capacity do |o|
|
179
|
+
o.type :int
|
180
|
+
o.default 100
|
181
|
+
end
|
182
|
+
|
173
183
|
# Enable dynamic instrumentation in development environments.
|
174
184
|
# Currently DI does not fully implement support for code
|
175
185
|
# unloading and reloading, and is not supported in
|
@@ -5,6 +5,7 @@ Datadog::DI::Serializer.register(condition: lambda { |value| ActiveRecord::Base
|
|
5
5
|
# steep:ignore:start
|
6
6
|
value_to_serialize = {
|
7
7
|
attributes: value.attributes,
|
8
|
+
new_record: value.new_record?,
|
8
9
|
}
|
9
10
|
serializer.serialize_value(value_to_serialize, depth: depth ? depth - 1 : nil, type: value.class)
|
10
11
|
# steep:ignore:end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module DI
|
5
|
+
module Contrib
|
6
|
+
# Railtie class initializes dynamic instrumentation contrib code
|
7
|
+
# in Rails environments.
|
8
|
+
class Railtie < Rails::Railtie
|
9
|
+
initializer 'datadog.dynamic_instrumentation.initialize' do |app|
|
10
|
+
Contrib.load_now
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../core/contrib/rails/utils'
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module DI
|
7
|
+
module Contrib
|
8
|
+
module_function def load_now_or_later
|
9
|
+
if Datadog::Core::Contrib::Rails::Utils.railtie_supported?
|
10
|
+
require_relative 'contrib/railtie'
|
11
|
+
else
|
12
|
+
load_now
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# This method can be called more than once, to attempt to load
|
17
|
+
# DI components that depend on third-party libraries after additional
|
18
|
+
# dependencies are loaded (or potentially loaded).
|
19
|
+
module_function def load_now
|
20
|
+
if defined?(ActiveRecord::Base)
|
21
|
+
require_relative 'contrib/active_record'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/datadog/di/error.rb
CHANGED
@@ -27,6 +27,11 @@ module Datadog
|
|
27
27
|
class DITargetNotDefined < Error
|
28
28
|
end
|
29
29
|
|
30
|
+
# Attempting to instrument a line and the file containing the line
|
31
|
+
# was loaded prior to code tracking being enabled.
|
32
|
+
class DITargetNotInRegistry < Error
|
33
|
+
end
|
34
|
+
|
30
35
|
# Raised when trying to install a probe whose installation failed
|
31
36
|
# earlier in the same process. This exception should contain the
|
32
37
|
# original exception report from initial installation attempt.
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative '../core/utils/time'
|
4
4
|
|
5
|
-
|
5
|
+
# rubocop:disable Lint/AssignmentInCondition
|
6
6
|
|
7
7
|
module Datadog
|
8
8
|
module DI
|
@@ -92,34 +92,88 @@ module Datadog
|
|
92
92
|
cls = symbolize_class_name(probe.type_name)
|
93
93
|
serializer = self.serializer
|
94
94
|
method_name = probe.method_name
|
95
|
-
|
96
|
-
|
95
|
+
loc = begin
|
96
|
+
cls.instance_method(method_name).source_location
|
97
|
+
rescue NameError
|
98
|
+
# The target method is not defined.
|
99
|
+
# This could be because it will be explicitly defined later
|
100
|
+
# (since classes can be reopened in Ruby)
|
101
|
+
# or the method is virtual (provided by a method_missing handler).
|
102
|
+
# In these cases we do not have a source location for the
|
103
|
+
# target method here.
|
104
|
+
end
|
97
105
|
rate_limiter = probe.rate_limiter
|
106
|
+
settings = self.settings
|
98
107
|
|
99
108
|
mod = Module.new do
|
100
|
-
define_method(method_name) do |*args, **kwargs| # steep:ignore
|
109
|
+
define_method(method_name) do |*args, **kwargs, &target_block| # steep:ignore
|
101
110
|
if rate_limiter.nil? || rate_limiter.allow?
|
102
111
|
# Arguments may be mutated by the method, therefore
|
103
112
|
# they need to be serialized prior to method invocation.
|
104
113
|
entry_args = if probe.capture_snapshot?
|
105
|
-
serializer.serialize_args(args, kwargs
|
114
|
+
serializer.serialize_args(args, kwargs,
|
115
|
+
depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
|
116
|
+
attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
|
106
117
|
end
|
107
|
-
|
108
|
-
|
109
|
-
|
118
|
+
start_time = Core::Utils::Time.get_time
|
119
|
+
# Under Ruby 2.6 we cannot just call super(*args, **kwargs)
|
120
|
+
# for methods defined via method_missing.
|
121
|
+
rv = if args.any?
|
122
|
+
if kwargs.any?
|
123
|
+
super(*args, **kwargs, &target_block)
|
124
|
+
else
|
125
|
+
super(*args, &target_block)
|
126
|
+
end
|
127
|
+
elsif kwargs.any?
|
128
|
+
super(**kwargs, &target_block)
|
129
|
+
else
|
130
|
+
super(&target_block)
|
110
131
|
end
|
132
|
+
duration = Core::Utils::Time.get_time - start_time
|
111
133
|
# The method itself is not part of the stack trace because
|
112
134
|
# we are getting the stack trace from outside of the method.
|
113
135
|
# Add the method in manually as the top frame.
|
114
|
-
method_frame =
|
115
|
-
|
136
|
+
method_frame = if loc
|
137
|
+
[Location.new(loc.first, loc.last, method_name)]
|
138
|
+
else
|
139
|
+
# For virtual and lazily-defined methods, we do not have
|
140
|
+
# the original source location here, and they won't be
|
141
|
+
# included in the stack trace currently.
|
142
|
+
# TODO when begin/end trace points are added for local
|
143
|
+
# variable capture in method probes, we should be able
|
144
|
+
# to obtain actual method execution location and use
|
145
|
+
# that location here.
|
146
|
+
[]
|
147
|
+
end
|
148
|
+
caller_locs = method_frame + caller_locations # steep:ignore
|
116
149
|
# TODO capture arguments at exit
|
117
150
|
# & is to stop steep complaints, block is always present here.
|
118
151
|
block&.call(probe: probe, rv: rv, duration: duration, caller_locations: caller_locs,
|
119
152
|
serialized_entry_args: entry_args)
|
120
153
|
rv
|
121
154
|
else
|
122
|
-
|
155
|
+
# stop standard from trying to mess up my code
|
156
|
+
_ = 42
|
157
|
+
|
158
|
+
# The necessity to invoke super in each of these specific
|
159
|
+
# ways is very difficult to test.
|
160
|
+
# Existing tests, even though I wrote many, still don't
|
161
|
+
# cause a failure if I replace all of the below with a
|
162
|
+
# simple super(*args, **kwargs, &target_block).
|
163
|
+
# But, let's be safe and go through the motions in case
|
164
|
+
# there is actually a legitimate need for the breakdown.
|
165
|
+
# TODO figure out how to test this properly.
|
166
|
+
if args.any?
|
167
|
+
if kwargs.any?
|
168
|
+
super(*args, **kwargs, &target_block)
|
169
|
+
else
|
170
|
+
super(*args, &target_block)
|
171
|
+
end
|
172
|
+
elsif kwargs.any?
|
173
|
+
super(**kwargs, &target_block)
|
174
|
+
else
|
175
|
+
super(&target_block)
|
176
|
+
end
|
123
177
|
end
|
124
178
|
end
|
125
179
|
end
|
@@ -191,11 +245,12 @@ module Datadog
|
|
191
245
|
#
|
192
246
|
# If the requested file is not in code tracker's registry,
|
193
247
|
# or the code tracker does not exist at all,
|
194
|
-
# do not attempt to
|
248
|
+
# do not attempt to instrument now.
|
195
249
|
# The caller should add the line to the list of pending lines
|
196
250
|
# to instrument and install the hook when the file in
|
197
251
|
# question is loaded (and hopefully, by then code tracking
|
198
252
|
# is active, otherwise the line will never be instrumented.)
|
253
|
+
raise_if_probe_in_loaded_features(probe)
|
199
254
|
raise Error::DITargetNotDefined, "File not in code tracker registry: #{probe.file}"
|
200
255
|
end
|
201
256
|
end
|
@@ -203,6 +258,7 @@ module Datadog
|
|
203
258
|
# Same as previous comment, if untargeted trace points are not
|
204
259
|
# explicitly defined, and we do not have code tracking, do not
|
205
260
|
# instrument the method.
|
261
|
+
raise_if_probe_in_loaded_features(probe)
|
206
262
|
raise Error::DITargetNotDefined, "File not in code tracker registry: #{probe.file}"
|
207
263
|
end
|
208
264
|
|
@@ -222,12 +278,25 @@ module Datadog
|
|
222
278
|
# overhead of targeted trace points is minimal, don't worry about
|
223
279
|
# this optimization just yet and create a trace point for each probe.
|
224
280
|
|
225
|
-
|
281
|
+
types = if iseq
|
282
|
+
# When targeting trace points we can target the 'end' line of a method.
|
283
|
+
# However, by adding the :return trace point we lose diagnostics
|
284
|
+
# for lines that contain no executable code (e.g. comments only)
|
285
|
+
# and thus cannot actually be instrumented.
|
286
|
+
[:line, :return, :b_return]
|
287
|
+
else
|
288
|
+
[:line]
|
289
|
+
end
|
290
|
+
tp = TracePoint.new(*types) do |tp|
|
226
291
|
begin
|
227
292
|
# If trace point is not targeted, we must verify that the invocation
|
228
293
|
# is the file & line that we want, because untargeted trace points
|
229
294
|
# are invoked for *each* line of Ruby executed.
|
230
|
-
|
295
|
+
# TODO find out exactly when the path in trace point is relative.
|
296
|
+
# Looks like this is the case when line trace point is not targeted?
|
297
|
+
if iseq || tp.lineno == probe.line_no && (
|
298
|
+
probe.file == tp.path || probe.file_matches?(tp.path)
|
299
|
+
)
|
231
300
|
if rate_limiter.nil? || rate_limiter.allow?
|
232
301
|
# & is to stop steep complaints, block is always present here.
|
233
302
|
block&.call(probe: probe, trace_point: tp, caller_locations: caller_locations)
|
@@ -235,13 +304,13 @@ module Datadog
|
|
235
304
|
end
|
236
305
|
rescue => exc
|
237
306
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
238
|
-
logger.
|
307
|
+
logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc}" }
|
239
308
|
telemetry&.report(exc, description: "Unhandled exception in line trace point")
|
240
309
|
# TODO test this path
|
241
310
|
end
|
242
311
|
rescue => exc
|
243
|
-
raise if settings.dynamic_instrumentation.propagate_all_exceptions
|
244
|
-
logger.
|
312
|
+
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
313
|
+
logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc}" }
|
245
314
|
telemetry&.report(exc, description: "Unhandled exception in line trace point")
|
246
315
|
# TODO test this path
|
247
316
|
end
|
@@ -266,7 +335,9 @@ module Datadog
|
|
266
335
|
else
|
267
336
|
tp.enable
|
268
337
|
end
|
338
|
+
# TracePoint#enable returns false when it succeeds.
|
269
339
|
end
|
340
|
+
true
|
270
341
|
end
|
271
342
|
|
272
343
|
def unhook_line(probe)
|
@@ -285,7 +356,7 @@ module Datadog
|
|
285
356
|
hook_line(probe, &block)
|
286
357
|
else
|
287
358
|
# TODO add test coverage for this path
|
288
|
-
logger.
|
359
|
+
logger.debug { "di: unknown probe type to hook: #{probe}" }
|
289
360
|
end
|
290
361
|
end
|
291
362
|
|
@@ -296,7 +367,7 @@ module Datadog
|
|
296
367
|
unhook_line(probe)
|
297
368
|
else
|
298
369
|
# TODO add test coverage for this path
|
299
|
-
logger.
|
370
|
+
logger.debug { "di: unknown probe type to unhook: #{probe}" }
|
300
371
|
end
|
301
372
|
end
|
302
373
|
|
@@ -304,6 +375,26 @@ module Datadog
|
|
304
375
|
|
305
376
|
attr_reader :lock
|
306
377
|
|
378
|
+
def raise_if_probe_in_loaded_features(probe)
|
379
|
+
return unless probe.file
|
380
|
+
|
381
|
+
# If the probe file is in the list of loaded files
|
382
|
+
# (as per $LOADED_FEATURES, using either exact or suffix match),
|
383
|
+
# raise an error indicating that
|
384
|
+
# code tracker is missing the loaded file because the file
|
385
|
+
# won't be loaded again (DI only works in production environments
|
386
|
+
# that do not normally reload code).
|
387
|
+
if $LOADED_FEATURES.include?(probe.file)
|
388
|
+
raise Error::DITargetNotInRegistry, "File loaded but is not in code tracker registry: #{probe.file}"
|
389
|
+
end
|
390
|
+
# Ths is an expensive check
|
391
|
+
$LOADED_FEATURES.each do |path|
|
392
|
+
if Utils.path_matches_suffix?(path, probe.file)
|
393
|
+
raise Error::DITargetNotInRegistry, "File matching probe path (#{probe.file}) was loaded and is not in code tracker registry: #{path}"
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
307
398
|
# TODO test that this resolves qualified names e.g. A::B
|
308
399
|
def symbolize_class_name(cls_name)
|
309
400
|
Object.const_get(cls_name)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Require 'datadog/di/preload' early in the application boot process to
|
4
|
+
# enable dynamic instrumentation for third-party libraries used by the
|
5
|
+
# application.
|
6
|
+
|
7
|
+
require_relative 'base'
|
8
|
+
|
9
|
+
# Code tracking is required for line probes to work; see the comments
|
10
|
+
# on the activate_tracking methods in di.rb for further details.
|
11
|
+
#
|
12
|
+
# Unlike di.rb which conditionally activates tracking only if the
|
13
|
+
# DD_DYNAMIC_INSTRUMENTATION_ENABLED environment variable is set, this file
|
14
|
+
# always activates tracking. This is because this file is explicitly loaded
|
15
|
+
# by customer applications for the purpose of enabling code tracking
|
16
|
+
# early in application boot process (i.e., before datadog library itself
|
17
|
+
# is loaded).
|
18
|
+
Datadog::DI.activate_tracking
|
data/lib/datadog/di/probe.rb
CHANGED
@@ -36,7 +36,9 @@ module Datadog
|
|
36
36
|
|
37
37
|
def initialize(id:, type:,
|
38
38
|
file: nil, line_no: nil, type_name: nil, method_name: nil,
|
39
|
-
template: nil, capture_snapshot: false, max_capture_depth: nil,
|
39
|
+
template: nil, capture_snapshot: false, max_capture_depth: nil,
|
40
|
+
max_capture_attribute_count: nil,
|
41
|
+
rate_limit: nil)
|
40
42
|
# Perform some sanity checks here to detect unexpected attribute
|
41
43
|
# combinations, in order to not do them in subsequent code.
|
42
44
|
unless KNOWN_TYPES.include?(type)
|
@@ -64,6 +66,7 @@ module Datadog
|
|
64
66
|
@template = template
|
65
67
|
@capture_snapshot = !!capture_snapshot
|
66
68
|
@max_capture_depth = max_capture_depth
|
69
|
+
@max_capture_attribute_count = max_capture_attribute_count
|
67
70
|
|
68
71
|
# These checks use instance methods that have more complex logic
|
69
72
|
# than checking a single argument value. To avoid duplicating
|
@@ -91,6 +94,10 @@ module Datadog
|
|
91
94
|
# the global default will be used.
|
92
95
|
attr_reader :max_capture_depth
|
93
96
|
|
97
|
+
# Configured maximum capture attribute count. Can be nil in which case
|
98
|
+
# the global default will be used.
|
99
|
+
attr_reader :max_capture_attribute_count
|
100
|
+
|
94
101
|
# Rate limit in effect, in invocations per second. Always present.
|
95
102
|
attr_reader :rate_limit
|
96
103
|
|
@@ -154,6 +161,9 @@ module Datadog
|
|
154
161
|
# If file is not an absolute path, the path matches if the file is its suffix,
|
155
162
|
# at a path component boundary.
|
156
163
|
def file_matches?(path)
|
164
|
+
if path.nil?
|
165
|
+
raise ArgumentError, "Cannot match against a nil path"
|
166
|
+
end
|
157
167
|
unless file
|
158
168
|
raise ArgumentError, "Probe does not have a file to match against"
|
159
169
|
end
|
@@ -37,6 +37,7 @@ module Datadog
|
|
37
37
|
template: config["template"],
|
38
38
|
capture_snapshot: !!config["captureSnapshot"],
|
39
39
|
max_capture_depth: config["capture"]&.[]("maxReferenceDepth"),
|
40
|
+
max_capture_attribute_count: config["capture"]&.[]("maxFieldCount"),
|
40
41
|
rate_limit: config["sampling"]&.[]("snapshotsPerSecond"),
|
41
42
|
)
|
42
43
|
rescue KeyError => exc
|