datadog 2.7.1 → 2.9.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 +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
|