datadog 2.8.0 → 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 +36 -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 +219 -122
- 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 +3 -0
- 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 -54
- 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/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/context.rb +54 -0
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +7 -7
- 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/rule_loader.rb +0 -3
- data/lib/datadog/appsec.rb +3 -3
- 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 +4 -2
- data/lib/datadog/core/configuration.rb +1 -1
- data/lib/datadog/{tracing → core}/contrib/rails/utils.rb +1 -3
- data/lib/datadog/core/crashtracking/component.rb +1 -3
- data/lib/datadog/core/telemetry/event.rb +87 -3
- 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 +7 -4
- data/lib/datadog/di/component.rb +17 -11
- data/lib/datadog/di/configuration/settings.rb +11 -1
- 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 +39 -18
- data/lib/datadog/di/{init.rb → preload.rb} +2 -4
- data/lib/datadog/di/probe_manager.rb +4 -4
- data/lib/datadog/di/probe_notification_builder.rb +16 -2
- data/lib/datadog/di/probe_notifier_worker.rb +5 -6
- data/lib/datadog/di/remote.rb +4 -4
- data/lib/datadog/di/transport.rb +2 -4
- data/lib/datadog/di.rb +5 -108
- data/lib/datadog/kit/appsec/events.rb +3 -3
- data/lib/datadog/kit/identity.rb +4 -4
- data/lib/datadog/profiling/component.rb +55 -53
- data/lib/datadog/profiling/http_transport.rb +1 -26
- 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/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/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 +1 -1
- metadata +19 -10
- 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
@@ -41,6 +41,18 @@ module Datadog
|
|
41
41
|
}
|
42
42
|
end
|
43
43
|
|
44
|
+
def ==(other)
|
45
|
+
other.is_a?(self.class) &&
|
46
|
+
name == other.name &&
|
47
|
+
values == other.values && tags == other.tags && common == other.common && type == other.type
|
48
|
+
end
|
49
|
+
|
50
|
+
alias eql? ==
|
51
|
+
|
52
|
+
def hash
|
53
|
+
[self.class, name, values, tags, common, type].hash
|
54
|
+
end
|
55
|
+
|
44
56
|
private
|
45
57
|
|
46
58
|
def tags_to_array(tags)
|
@@ -71,6 +83,16 @@ module Datadog
|
|
71
83
|
res[:interval] = interval
|
72
84
|
res
|
73
85
|
end
|
86
|
+
|
87
|
+
def ==(other)
|
88
|
+
super && interval == other.interval
|
89
|
+
end
|
90
|
+
|
91
|
+
alias eql? ==
|
92
|
+
|
93
|
+
def hash
|
94
|
+
[super, interval].hash
|
95
|
+
end
|
74
96
|
end
|
75
97
|
|
76
98
|
# Count metric adds up all the submitted values in a time interval. This would be suitable for a
|
@@ -97,6 +97,8 @@ module Datadog
|
|
97
97
|
return if events.empty?
|
98
98
|
return if !enabled? || !sent_started_event?
|
99
99
|
|
100
|
+
events = deduplicate_logs(events)
|
101
|
+
|
100
102
|
Datadog.logger.debug { "Sending #{events&.count} telemetry events" }
|
101
103
|
send_event(Event::MessageBatch.new(events))
|
102
104
|
end
|
@@ -167,6 +169,37 @@ module Datadog
|
|
167
169
|
Datadog.logger.debug('Agent does not support telemetry; disabling future telemetry events.')
|
168
170
|
disable!
|
169
171
|
end
|
172
|
+
|
173
|
+
# Deduplicate logs by counting the number of repeated occurrences of the same log
|
174
|
+
# entry and replacing them with a single entry with the calculated `count` value.
|
175
|
+
# Non-log events are unchanged.
|
176
|
+
def deduplicate_logs(events)
|
177
|
+
return events if events.empty?
|
178
|
+
|
179
|
+
all_logs = []
|
180
|
+
other_events = events.reject do |event|
|
181
|
+
if event.is_a?(Event::Log)
|
182
|
+
all_logs << event
|
183
|
+
true
|
184
|
+
else
|
185
|
+
false
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
return events if all_logs.empty?
|
190
|
+
|
191
|
+
uniq_logs = all_logs.group_by(&:itself).map do |_, logs|
|
192
|
+
log = logs.first
|
193
|
+
if logs.size > 1
|
194
|
+
# New log event with a count of repeated occurrences
|
195
|
+
Event::Log.new(message: log.message, level: log.level, stack_trace: log.stack_trace, count: logs.size)
|
196
|
+
else
|
197
|
+
log
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
other_events + uniq_logs
|
202
|
+
end
|
170
203
|
end
|
171
204
|
end
|
172
205
|
end
|
@@ -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
|
@@ -87,11 +89,12 @@ module Datadog
|
|
87
89
|
# rescue any exceptions that might not be handled to not break said
|
88
90
|
# customer applications.
|
89
91
|
rescue => exc
|
90
|
-
#
|
91
|
-
#
|
92
|
-
|
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
|
93
96
|
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
94
|
-
component.logger.
|
97
|
+
component.logger.debug { "di: unhandled exception in script_compiled trace point: #{exc.class}: #{exc}" }
|
95
98
|
component.telemetry&.report(exc, description: "Unhandled exception in script_compiled trace point")
|
96
99
|
# TODO test this path
|
97
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,22 +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|
|
28
30
|
DI.add_current_component(component)
|
29
31
|
end
|
30
32
|
end
|
31
33
|
|
32
|
-
def build!(settings, agent_settings, telemetry: nil)
|
34
|
+
def build!(settings, agent_settings, logger, telemetry: nil)
|
33
35
|
unless settings.respond_to?(:dynamic_instrumentation) && settings.dynamic_instrumentation.enabled
|
34
36
|
raise "Requested DI component but DI is not enabled in settings"
|
35
37
|
end
|
@@ -38,27 +40,31 @@ module Datadog
|
|
38
40
|
raise "Requested DI component but remote config is not enabled in settings"
|
39
41
|
end
|
40
42
|
|
41
|
-
unless environment_supported?(settings)
|
43
|
+
unless environment_supported?(settings, logger)
|
42
44
|
raise "DI does not support the environment (development or Ruby version too low or not MRI)"
|
43
45
|
end
|
44
46
|
|
45
|
-
new(settings, agent_settings,
|
47
|
+
new(settings, agent_settings, logger, code_tracker: DI.code_tracker, telemetry: telemetry)
|
46
48
|
end
|
47
49
|
|
48
50
|
# Checks whether the runtime environment is supported by
|
49
51
|
# dynamic instrumentation. Currently we only require that, if Rails
|
50
52
|
# is used, that Rails environment is not development because
|
51
53
|
# DI does not currently support code unloading and reloading.
|
52
|
-
def environment_supported?(settings)
|
54
|
+
def environment_supported?(settings, logger)
|
53
55
|
# TODO add tests?
|
54
56
|
unless settings.dynamic_instrumentation.internal.development
|
55
57
|
if Datadog::Core::Environment::Execution.development?
|
56
|
-
|
58
|
+
logger.warn("di: development environment detected; not enabling dynamic instrumentation")
|
57
59
|
return false
|
58
60
|
end
|
59
61
|
end
|
60
|
-
if RUBY_ENGINE != 'ruby'
|
61
|
-
|
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}")
|
62
68
|
return false
|
63
69
|
end
|
64
70
|
true
|
@@ -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
|
@@ -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
|
@@ -115,22 +115,21 @@ module Datadog
|
|
115
115
|
depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
|
116
116
|
attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
|
117
117
|
end
|
118
|
-
|
118
|
+
start_time = Core::Utils::Time.get_time
|
119
119
|
# Under Ruby 2.6 we cannot just call super(*args, **kwargs)
|
120
120
|
# for methods defined via method_missing.
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
super(*args, **kwargs, &target_block)
|
125
|
-
else
|
126
|
-
super(*args, &target_block)
|
127
|
-
end
|
128
|
-
elsif kwargs.any?
|
129
|
-
super(**kwargs, &target_block)
|
121
|
+
rv = if args.any?
|
122
|
+
if kwargs.any?
|
123
|
+
super(*args, **kwargs, &target_block)
|
130
124
|
else
|
131
|
-
super(&target_block)
|
125
|
+
super(*args, &target_block)
|
132
126
|
end
|
127
|
+
elsif kwargs.any?
|
128
|
+
super(**kwargs, &target_block)
|
129
|
+
else
|
130
|
+
super(&target_block)
|
133
131
|
end
|
132
|
+
duration = Core::Utils::Time.get_time - start_time
|
134
133
|
# The method itself is not part of the stack trace because
|
135
134
|
# we are getting the stack trace from outside of the method.
|
136
135
|
# Add the method in manually as the top frame.
|
@@ -246,11 +245,12 @@ module Datadog
|
|
246
245
|
#
|
247
246
|
# If the requested file is not in code tracker's registry,
|
248
247
|
# or the code tracker does not exist at all,
|
249
|
-
# do not attempt to
|
248
|
+
# do not attempt to instrument now.
|
250
249
|
# The caller should add the line to the list of pending lines
|
251
250
|
# to instrument and install the hook when the file in
|
252
251
|
# question is loaded (and hopefully, by then code tracking
|
253
252
|
# is active, otherwise the line will never be instrumented.)
|
253
|
+
raise_if_probe_in_loaded_features(probe)
|
254
254
|
raise Error::DITargetNotDefined, "File not in code tracker registry: #{probe.file}"
|
255
255
|
end
|
256
256
|
end
|
@@ -258,6 +258,7 @@ module Datadog
|
|
258
258
|
# Same as previous comment, if untargeted trace points are not
|
259
259
|
# explicitly defined, and we do not have code tracking, do not
|
260
260
|
# instrument the method.
|
261
|
+
raise_if_probe_in_loaded_features(probe)
|
261
262
|
raise Error::DITargetNotDefined, "File not in code tracker registry: #{probe.file}"
|
262
263
|
end
|
263
264
|
|
@@ -303,13 +304,13 @@ module Datadog
|
|
303
304
|
end
|
304
305
|
rescue => exc
|
305
306
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
306
|
-
logger.
|
307
|
+
logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc}" }
|
307
308
|
telemetry&.report(exc, description: "Unhandled exception in line trace point")
|
308
309
|
# TODO test this path
|
309
310
|
end
|
310
311
|
rescue => exc
|
311
312
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
312
|
-
logger.
|
313
|
+
logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc}" }
|
313
314
|
telemetry&.report(exc, description: "Unhandled exception in line trace point")
|
314
315
|
# TODO test this path
|
315
316
|
end
|
@@ -355,7 +356,7 @@ module Datadog
|
|
355
356
|
hook_line(probe, &block)
|
356
357
|
else
|
357
358
|
# TODO add test coverage for this path
|
358
|
-
logger.
|
359
|
+
logger.debug { "di: unknown probe type to hook: #{probe}" }
|
359
360
|
end
|
360
361
|
end
|
361
362
|
|
@@ -366,7 +367,7 @@ module Datadog
|
|
366
367
|
unhook_line(probe)
|
367
368
|
else
|
368
369
|
# TODO add test coverage for this path
|
369
|
-
logger.
|
370
|
+
logger.debug { "di: unknown probe type to unhook: #{probe}" }
|
370
371
|
end
|
371
372
|
end
|
372
373
|
|
@@ -374,6 +375,26 @@ module Datadog
|
|
374
375
|
|
375
376
|
attr_reader :lock
|
376
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
|
+
|
377
398
|
# TODO test that this resolves qualified names e.g. A::B
|
378
399
|
def symbolize_class_name(cls_name)
|
379
400
|
Object.const_get(cls_name)
|
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Require 'datadog/di/
|
3
|
+
# Require 'datadog/di/preload' early in the application boot process to
|
4
4
|
# enable dynamic instrumentation for third-party libraries used by the
|
5
5
|
# application.
|
6
6
|
|
7
|
-
require_relative '
|
8
|
-
require_relative '../tracing/contrib'
|
9
|
-
require_relative '../di'
|
7
|
+
require_relative 'base'
|
10
8
|
|
11
9
|
# Code tracking is required for line probes to work; see the comments
|
12
10
|
# on the activate_tracking methods in di.rb for further details.
|
@@ -32,7 +32,7 @@ module Datadog
|
|
32
32
|
install_pending_method_probes(tp.self)
|
33
33
|
rescue => exc
|
34
34
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
35
|
-
logger.
|
35
|
+
logger.debug { "di: unhandled exception in definition trace point: #{exc.class}: #{exc}" }
|
36
36
|
telemetry&.report(exc, description: "Unhandled exception in definition trace point")
|
37
37
|
# TODO test this path
|
38
38
|
end
|
@@ -120,7 +120,7 @@ module Datadog
|
|
120
120
|
# In "propagate all exceptions" mode we will try to instrument again.
|
121
121
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
122
122
|
|
123
|
-
logger.
|
123
|
+
logger.debug { "di: error processing probe configuration: #{exc.class}: #{exc}" }
|
124
124
|
telemetry&.report(exc, description: "Error processing probe configuration")
|
125
125
|
# TODO report probe as failed to agent since we won't attempt to
|
126
126
|
# install it again.
|
@@ -160,7 +160,7 @@ module Datadog
|
|
160
160
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
161
161
|
# Silence all exceptions?
|
162
162
|
# TODO should we propagate here and rescue upstream?
|
163
|
-
logger.
|
163
|
+
logger.debug { "di: error removing probe #{probe.id}: #{exc.class}: #{exc}" }
|
164
164
|
telemetry&.report(exc, description: "Error removing probe")
|
165
165
|
end
|
166
166
|
end
|
@@ -190,7 +190,7 @@ module Datadog
|
|
190
190
|
rescue => exc
|
191
191
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
192
192
|
|
193
|
-
logger.
|
193
|
+
logger.debug { "di: error installing probe after class is defined: #{exc.class}: #{exc}" }
|
194
194
|
telemetry&.report(exc, description: "Error installing probe after class is defined")
|
195
195
|
end
|
196
196
|
end
|
@@ -141,8 +141,8 @@ module Datadog
|
|
141
141
|
version: 2,
|
142
142
|
},
|
143
143
|
# TODO add tests that the trace/span id is correctly propagated
|
144
|
-
"dd.trace_id":
|
145
|
-
"dd.span_id":
|
144
|
+
"dd.trace_id": active_trace&.id&.to_s,
|
145
|
+
"dd.span_id": active_span&.id&.to_s,
|
146
146
|
ddsource: 'dd_debugger',
|
147
147
|
message: probe.template && evaluate_template(probe.template,
|
148
148
|
duration: duration ? duration * 1000 : 0),
|
@@ -150,6 +150,8 @@ module Datadog
|
|
150
150
|
}
|
151
151
|
end
|
152
152
|
|
153
|
+
private
|
154
|
+
|
153
155
|
def build_status(probe, message:, status:)
|
154
156
|
{
|
155
157
|
service: settings.service,
|
@@ -200,6 +202,18 @@ module Datadog
|
|
200
202
|
map[name] = value
|
201
203
|
end
|
202
204
|
end
|
205
|
+
|
206
|
+
def active_trace
|
207
|
+
if defined?(Datadog::Tracing)
|
208
|
+
Datadog::Tracing.active_trace
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def active_span
|
213
|
+
if defined?(Datadog::Tracing)
|
214
|
+
Datadog::Tracing.active_span
|
215
|
+
end
|
216
|
+
end
|
203
217
|
end
|
204
218
|
end
|
205
219
|
end
|
@@ -77,7 +77,7 @@ module Datadog
|
|
77
77
|
rescue => exc
|
78
78
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
79
79
|
|
80
|
-
logger.
|
80
|
+
logger.debug { "di: error in probe notifier worker: #{exc.class}: #{exc} (at #{exc.backtrace.first})" }
|
81
81
|
telemetry&.report(exc, description: "Error in probe notifier worker")
|
82
82
|
end
|
83
83
|
@lock.synchronize do
|
@@ -183,9 +183,8 @@ module Datadog
|
|
183
183
|
define_method("add_#{event_type}") do |event|
|
184
184
|
@lock.synchronize do
|
185
185
|
queue = send("#{event_type}_queue")
|
186
|
-
|
187
|
-
|
188
|
-
logger.warn("#{self.class.name}: dropping #{event_type} because queue is full")
|
186
|
+
if queue.length > settings.dynamic_instrumentation.internal.snapshot_queue_capacity
|
187
|
+
logger.debug { "di: #{self.class.name}: dropping #{event_type} because queue is full" }
|
189
188
|
else
|
190
189
|
queue << event
|
191
190
|
end
|
@@ -242,7 +241,7 @@ module Datadog
|
|
242
241
|
end
|
243
242
|
rescue => exc
|
244
243
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
245
|
-
logger.
|
244
|
+
logger.debug { "di: failed to send #{event_name}: #{exc.class}: #{exc} (at #{exc.backtrace.first})" }
|
246
245
|
# Should we report this error to telemetry? Most likely failure
|
247
246
|
# to send is due to a network issue, and trying to send a
|
248
247
|
# telemetry message would also fail.
|
@@ -253,7 +252,7 @@ module Datadog
|
|
253
252
|
# Normally the queue should only be consumed in this method,
|
254
253
|
# however if anyone consumes it elsewhere we don't want to block
|
255
254
|
# while consuming it here. Rescue ThreadError and return.
|
256
|
-
logger.
|
255
|
+
logger.debug { "di: unexpected #{event_name} queue underflow - consumed elsewhere?" }
|
257
256
|
telemetry&.report(exc, description: "Unexpected #{event_name} queue underflow")
|
258
257
|
ensure
|
259
258
|
@lock.synchronize do
|