datadog 2.3.0 → 2.5.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 +64 -2
- data/ext/datadog_profiling_loader/datadog_profiling_loader.c +9 -1
- data/ext/datadog_profiling_loader/extconf.rb +10 -22
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +3 -3
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +198 -41
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +4 -2
- data/ext/datadog_profiling_native_extension/collectors_stack.c +89 -46
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +645 -107
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +15 -1
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +0 -27
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -4
- data/ext/datadog_profiling_native_extension/extconf.rb +42 -25
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +50 -0
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +75 -0
- data/ext/datadog_profiling_native_extension/heap_recorder.c +194 -34
- data/ext/datadog_profiling_native_extension/heap_recorder.h +11 -0
- data/ext/datadog_profiling_native_extension/http_transport.c +38 -6
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +1 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +53 -2
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -0
- data/ext/datadog_profiling_native_extension/profiling.c +1 -1
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +14 -11
- data/ext/datadog_profiling_native_extension/stack_recorder.c +58 -22
- data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -0
- data/ext/libdatadog_api/crashtracker.c +20 -18
- data/ext/libdatadog_api/datadog_ruby_common.c +0 -27
- data/ext/libdatadog_api/datadog_ruby_common.h +0 -4
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +2184 -108
- data/lib/datadog/appsec/assets/waf_rules/strict.json +1430 -2
- data/lib/datadog/appsec/component.rb +29 -8
- data/lib/datadog/appsec/configuration/settings.rb +10 -2
- data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +1 -0
- data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +21 -0
- data/lib/datadog/appsec/contrib/devise/patcher.rb +12 -2
- data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +0 -14
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +67 -31
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +14 -15
- data/lib/datadog/appsec/contrib/graphql/integration.rb +14 -1
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +7 -20
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +2 -5
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -15
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +6 -18
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +7 -20
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +5 -18
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +3 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +3 -5
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +5 -18
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +6 -10
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +7 -20
- data/lib/datadog/appsec/event.rb +25 -1
- data/lib/datadog/appsec/ext.rb +4 -0
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +3 -5
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +7 -20
- data/lib/datadog/appsec/processor/context.rb +109 -0
- data/lib/datadog/appsec/processor/rule_loader.rb +3 -1
- data/lib/datadog/appsec/processor/rule_merger.rb +33 -15
- data/lib/datadog/appsec/processor.rb +42 -107
- data/lib/datadog/appsec/rate_limiter.rb +25 -40
- data/lib/datadog/appsec/remote.rb +7 -3
- data/lib/datadog/appsec/scope.rb +1 -4
- data/lib/datadog/appsec/utils/trace_operation.rb +15 -0
- data/lib/datadog/appsec/utils.rb +2 -0
- data/lib/datadog/appsec.rb +3 -2
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +26 -25
- data/lib/datadog/core/configuration/components.rb +4 -3
- data/lib/datadog/core/configuration/settings.rb +96 -5
- data/lib/datadog/core/configuration.rb +1 -3
- data/lib/datadog/core/crashtracking/component.rb +9 -6
- data/lib/datadog/core/environment/execution.rb +5 -5
- data/lib/datadog/core/environment/yjit.rb +5 -0
- data/lib/datadog/core/metrics/client.rb +7 -0
- data/lib/datadog/core/rate_limiter.rb +183 -0
- data/lib/datadog/core/remote/client/capabilities.rb +4 -3
- data/lib/datadog/core/remote/component.rb +4 -2
- data/lib/datadog/core/remote/negotiation.rb +4 -4
- data/lib/datadog/core/remote/tie.rb +2 -0
- data/lib/datadog/core/remote/transport/http.rb +5 -0
- data/lib/datadog/core/remote/worker.rb +1 -1
- data/lib/datadog/core/runtime/ext.rb +1 -0
- data/lib/datadog/core/runtime/metrics.rb +5 -1
- data/lib/datadog/core/semaphore.rb +35 -0
- data/lib/datadog/core/telemetry/component.rb +2 -0
- data/lib/datadog/core/telemetry/event.rb +12 -7
- data/lib/datadog/core/telemetry/logger.rb +51 -0
- data/lib/datadog/core/telemetry/logging.rb +50 -14
- data/lib/datadog/core/telemetry/request.rb +13 -1
- data/lib/datadog/core/transport/ext.rb +1 -0
- data/lib/datadog/core/utils/time.rb +12 -0
- data/lib/datadog/core/workers/async.rb +1 -1
- data/lib/datadog/di/code_tracker.rb +166 -0
- data/lib/datadog/di/configuration/settings.rb +163 -0
- data/lib/datadog/di/configuration.rb +11 -0
- data/lib/datadog/di/error.rb +31 -0
- data/lib/datadog/di/extensions.rb +16 -0
- data/lib/datadog/di/instrumenter.rb +301 -0
- data/lib/datadog/di/probe.rb +162 -0
- data/lib/datadog/di/probe_builder.rb +47 -0
- data/lib/datadog/di/probe_notification_builder.rb +207 -0
- data/lib/datadog/di/probe_notifier_worker.rb +244 -0
- data/lib/datadog/di/redactor.rb +188 -0
- data/lib/datadog/di/serializer.rb +215 -0
- data/lib/datadog/di/transport.rb +67 -0
- data/lib/datadog/di/utils.rb +39 -0
- data/lib/datadog/di.rb +57 -0
- data/lib/datadog/opentelemetry/sdk/propagator.rb +2 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +12 -10
- data/lib/datadog/profiling/collectors/info.rb +12 -3
- data/lib/datadog/profiling/collectors/thread_context.rb +32 -8
- data/lib/datadog/profiling/component.rb +21 -4
- data/lib/datadog/profiling/http_transport.rb +6 -1
- data/lib/datadog/profiling/scheduler.rb +2 -0
- data/lib/datadog/profiling/stack_recorder.rb +40 -9
- data/lib/datadog/single_step_instrument.rb +12 -0
- data/lib/datadog/tracing/component.rb +13 -0
- data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -12
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -0
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +78 -0
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +33 -0
- data/lib/datadog/tracing/contrib/action_pack/patcher.rb +2 -0
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +4 -0
- data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +3 -1
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +3 -1
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +5 -1
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +5 -0
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -0
- data/lib/datadog/tracing/contrib/excon/middleware.rb +3 -0
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +12 -0
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +24 -2
- data/lib/datadog/tracing/contrib/graphql/patcher.rb +9 -12
- data/lib/datadog/tracing/contrib/graphql/trace_patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +13 -9
- data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +6 -3
- data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +9 -0
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +22 -15
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +10 -5
- data/lib/datadog/tracing/contrib/httpclient/patcher.rb +1 -14
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +9 -0
- data/lib/datadog/tracing/contrib/httprb/patcher.rb +1 -14
- data/lib/datadog/tracing/contrib/lograge/patcher.rb +1 -2
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +13 -6
- data/lib/datadog/tracing/contrib/patcher.rb +2 -1
- data/lib/datadog/tracing/contrib/presto/patcher.rb +1 -13
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +27 -0
- data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
- data/lib/datadog/tracing/contrib/redis/tags.rb +4 -0
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +3 -0
- data/lib/datadog/tracing/contrib/sinatra/tracer.rb +4 -0
- data/lib/datadog/tracing/contrib/stripe/request.rb +3 -2
- data/lib/datadog/tracing/distributed/propagation.rb +7 -0
- data/lib/datadog/tracing/metadata/ext.rb +2 -0
- data/lib/datadog/tracing/remote.rb +5 -2
- data/lib/datadog/tracing/sampling/matcher.rb +6 -1
- data/lib/datadog/tracing/sampling/rate_sampler.rb +1 -1
- data/lib/datadog/tracing/sampling/rule.rb +2 -0
- data/lib/datadog/tracing/sampling/rule_sampler.rb +15 -9
- data/lib/datadog/tracing/sampling/span/ext.rb +1 -1
- data/lib/datadog/tracing/sampling/span/rule.rb +2 -2
- data/lib/datadog/tracing/trace_operation.rb +26 -2
- data/lib/datadog/tracing/tracer.rb +29 -22
- data/lib/datadog/tracing/transport/http/client.rb +1 -0
- data/lib/datadog/tracing/transport/http.rb +4 -0
- data/lib/datadog/tracing/transport/io/client.rb +1 -0
- data/lib/datadog/tracing/workers/trace_writer.rb +1 -1
- data/lib/datadog/tracing/workers.rb +2 -2
- data/lib/datadog/tracing/writer.rb +26 -28
- data/lib/datadog/version.rb +1 -1
- metadata +40 -15
- data/lib/datadog/tracing/sampling/rate_limiter.rb +0 -185
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module DI
|
5
|
+
# Tracks loaded Ruby code by source file and maintains a map from
|
6
|
+
# source file to the loaded code (instruction sequences).
|
7
|
+
# Also arranges for code in the loaded files to be instrumented by
|
8
|
+
# line probes that have already been received by the library.
|
9
|
+
#
|
10
|
+
# The loaded code is used to target line trace points when installing
|
11
|
+
# line probes which dramatically improves efficiency of line trace points.
|
12
|
+
#
|
13
|
+
# Note that, since most files will only be loaded one time (via the
|
14
|
+
# "require" mechanism), the code tracker needs to be global and not be
|
15
|
+
# recreated when the DI component is created.
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
class CodeTracker
|
19
|
+
def initialize
|
20
|
+
@registry = {}
|
21
|
+
@trace_point_lock = Mutex.new
|
22
|
+
@registry_lock = Mutex.new
|
23
|
+
@compiled_trace_point = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
# Starts tracking loaded code.
|
27
|
+
#
|
28
|
+
# This method should generally be called early in application boot
|
29
|
+
# process, because any code loaded before code tracking is enabled
|
30
|
+
# will not be instrumentable via line probes.
|
31
|
+
#
|
32
|
+
# Normally tracking should remain active for the lifetime of the
|
33
|
+
# process and would not be ever stopped.
|
34
|
+
def start
|
35
|
+
trace_point_lock.synchronize do
|
36
|
+
# If this code tracker is already running, we can do nothing or
|
37
|
+
# restart it (by disabling the trace point and recreating it).
|
38
|
+
# It is likely that some applications will attempt to activate
|
39
|
+
# DI more than once where the intention is to just activate DI;
|
40
|
+
# do not break such applications by clearing out the registry.
|
41
|
+
# For now, until there is a use case for recreating the trace point,
|
42
|
+
# do nothing if the code tracker has already started.
|
43
|
+
return if @compiled_trace_point
|
44
|
+
|
45
|
+
# Note: .trace enables the trace point.
|
46
|
+
@compiled_trace_point = TracePoint.trace(:script_compiled) do |tp|
|
47
|
+
# Useful attributes of the trace point object here:
|
48
|
+
# .instruction_sequence
|
49
|
+
# .instruction_sequence.path (either absolute file path for
|
50
|
+
# loaded or required code, or for eval'd code, if filename
|
51
|
+
# is specified as argument to eval, then this is the provided
|
52
|
+
# filename, otherwise this is a synthesized
|
53
|
+
# "(eval at <definition-file>:<line>)" string)
|
54
|
+
# .instruction_sequence.absolute_path (absolute file path when
|
55
|
+
# load or require are used to load code, nil for eval'd code
|
56
|
+
# regardless of whether filename was specified as an argument
|
57
|
+
# to eval on ruby 3.1+, same as path for eval'd code on ruby 3.0
|
58
|
+
# and lower)
|
59
|
+
# .method_id
|
60
|
+
# .path (refers to the code location that called the require/eval/etc.,
|
61
|
+
# not where the loaded code is; use .path on the instruction sequence
|
62
|
+
# to obtain the location of the compiled code)
|
63
|
+
# .eval_script
|
64
|
+
#
|
65
|
+
# For now just map the path to the instruction sequence.
|
66
|
+
path = tp.instruction_sequence.absolute_path
|
67
|
+
# Do not store mapping for eval'd code, since there is no way
|
68
|
+
# to target such code from dynamic instrumentation UI.
|
69
|
+
# eval'd code always sets tp.eval_script.
|
70
|
+
# When tp.eval_script is nil, code is either 'load'ed or 'require'd.
|
71
|
+
# steep, of course, complains about indexing with +path+
|
72
|
+
# without checking that it is not nil, so here, maybe there is
|
73
|
+
# some situation where path would in fact be nil and
|
74
|
+
# steep would end up saving the day.
|
75
|
+
if path && !tp.eval_script
|
76
|
+
registry_lock.synchronize do
|
77
|
+
registry[path] = tp.instruction_sequence
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns whether this code tracker has been activated and is
|
85
|
+
# tracking.
|
86
|
+
def active?
|
87
|
+
trace_point_lock.synchronize do
|
88
|
+
!!@compiled_trace_point
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns an array of RubVM::InstructionSequence (i.e. the compiled code)
|
93
|
+
# for the provided path.
|
94
|
+
#
|
95
|
+
# The argument can be a full path to a Ruby source code file or a
|
96
|
+
# suffix (basename + one or more directories preceding the basename).
|
97
|
+
# The idea with suffix matches is that file paths are likely to
|
98
|
+
# be different between development and production environments and
|
99
|
+
# the source control system uses relative paths and doesn't have
|
100
|
+
# absolute paths at all.
|
101
|
+
#
|
102
|
+
# Suffix matches are not guaranteed to be correct, meaning there may
|
103
|
+
# be multiple files with the same basename and they may all match a
|
104
|
+
# given suffix. In such cases, this method will return all matching
|
105
|
+
# paths (and all of these paths will be attempted to be instrumented
|
106
|
+
# by upstream code).
|
107
|
+
#
|
108
|
+
# If the suffix matches one of the paths completely (which requires it
|
109
|
+
# to be an absolute path), only the exactly matching path is returned.
|
110
|
+
# Otherwise all known paths that end in the suffix are returned.
|
111
|
+
# If no paths match, an empty array is returned.
|
112
|
+
def iseqs_for_path_suffix(suffix)
|
113
|
+
registry_lock.synchronize do
|
114
|
+
exact = registry[suffix]
|
115
|
+
return [exact] if exact
|
116
|
+
|
117
|
+
inexact = []
|
118
|
+
registry.each do |path, iseq|
|
119
|
+
if Utils.path_matches_suffix?(path, suffix)
|
120
|
+
inexact << iseq
|
121
|
+
end
|
122
|
+
end
|
123
|
+
inexact
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Stops tracking code that is being loaded.
|
128
|
+
#
|
129
|
+
# This method should ordinarily never be called - if a file is loaded
|
130
|
+
# when code tracking is not active, this file will not be instrumentable
|
131
|
+
# by line probes.
|
132
|
+
#
|
133
|
+
# This method is intended for test suite use only, where multiple
|
134
|
+
# code tracker instances are created, to fully clean up the old instances.
|
135
|
+
def stop
|
136
|
+
# Permit multiple stop calls.
|
137
|
+
trace_point_lock.synchronize do
|
138
|
+
@compiled_trace_point&.disable
|
139
|
+
# Clear the instance variable so that the trace point may be
|
140
|
+
# reinstated in the future.
|
141
|
+
@compiled_trace_point = nil
|
142
|
+
end
|
143
|
+
clear
|
144
|
+
end
|
145
|
+
|
146
|
+
# Clears the stored mapping from paths to compiled code.
|
147
|
+
#
|
148
|
+
# This method should normally never be called. It is meant to be
|
149
|
+
# used only by the test suite.
|
150
|
+
def clear
|
151
|
+
registry_lock.synchronize do
|
152
|
+
registry.clear
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
# Mapping from paths of loaded files to RubyVM::InstructionSequence
|
159
|
+
# objects representing compiled code of those files.
|
160
|
+
attr_reader :registry
|
161
|
+
|
162
|
+
attr_reader :trace_point_lock
|
163
|
+
attr_reader :registry_lock
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module DI
|
5
|
+
module Configuration
|
6
|
+
# Settings
|
7
|
+
module Settings
|
8
|
+
def self.extended(base)
|
9
|
+
base = base.singleton_class unless base.is_a?(Class)
|
10
|
+
add_settings!(base)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.add_settings!(base)
|
14
|
+
base.class_eval do
|
15
|
+
# The setting has "internal" prefix to prevent it from being
|
16
|
+
# prematurely turned on by customers.
|
17
|
+
settings :dynamic_instrumentation do
|
18
|
+
option :enabled do |o|
|
19
|
+
o.type :bool
|
20
|
+
# The environment variable has an "internal" prefix so that
|
21
|
+
# any customers that have the "proper" environment variable
|
22
|
+
# turned on (i.e. DD_DYNAMIC_INSTRUMENTATION_ENABLED)
|
23
|
+
# do not enable Ruby DI until the latter is ready for
|
24
|
+
# customer testing.
|
25
|
+
o.env "DD_DYNAMIC_INSTRUMENTATION_ENABLED"
|
26
|
+
o.default false
|
27
|
+
end
|
28
|
+
|
29
|
+
# This option instructs dynamic instrumentation to use
|
30
|
+
# untargeted trace points when installing line probes and
|
31
|
+
# code tracking is not active.
|
32
|
+
# WARNING: untargeted trace points carry a massive performance
|
33
|
+
# penalty for the entire file in which a line probe is placed.
|
34
|
+
#
|
35
|
+
# If this option is set to false, which is the default,
|
36
|
+
# dynamic instrumentation will add probes that reference
|
37
|
+
# unknown files to the list of pending probes, and when
|
38
|
+
# the respective files are loaded, the line probes will be
|
39
|
+
# installed using targeted trace points. If the file in
|
40
|
+
# question is already loaded when the probe is received
|
41
|
+
# (for example, it is in a third-party library loaded during
|
42
|
+
# application boot), and code tracking was not active when
|
43
|
+
# the file was loaded, such files will not be instrumentable
|
44
|
+
# via line probes.
|
45
|
+
#
|
46
|
+
# If this option is set to true
|
47
|
+
#
|
48
|
+
# activated, DI will in
|
49
|
+
# activated or because the files being targeted have beenIf true and code tracking is not enabled, dynamic instrumentation
|
50
|
+
# will use untargeted trace points.
|
51
|
+
# If false and code tracking is not enabled, dynamic
|
52
|
+
# instrumentation will not instrument any files loaded
|
53
|
+
# WARNING: these trace points will greatly degrade performance
|
54
|
+
# of all code in the instrumented files.
|
55
|
+
option :untargeted_trace_points do |o|
|
56
|
+
o.type :bool
|
57
|
+
o.default false
|
58
|
+
end
|
59
|
+
|
60
|
+
# If true, all of the catch-all rescue blocks in DI
|
61
|
+
# will propagate the exceptions onward.
|
62
|
+
# WARNING: for internal Datadog use only - this will break
|
63
|
+
# the DI product and potentially the library in general in
|
64
|
+
# a multitude of ways, cause resource leakage, permanent
|
65
|
+
# performance decreases, etc.
|
66
|
+
option :propagate_all_exceptions do |o|
|
67
|
+
o.type :bool
|
68
|
+
o.default false
|
69
|
+
end
|
70
|
+
|
71
|
+
# An array of variable and key names to redact in addition to
|
72
|
+
# the built-in list of identifiers.
|
73
|
+
#
|
74
|
+
# The names will be normalized by removing the following
|
75
|
+
# symbols: _, -, @, $, and then matched to the complete
|
76
|
+
# variable or key name while ignoring the case.
|
77
|
+
# For example, specifying pass_word will match password and
|
78
|
+
# PASSWORD, and specifying PASSWORD will match pass_word.
|
79
|
+
# Note that, while the at sign (@) is used in Ruby to refer
|
80
|
+
# to instance variables, it does not have any significance
|
81
|
+
# for this setting (and is removed before matching identifiers).
|
82
|
+
option :redacted_identifiers do |o|
|
83
|
+
o.env "DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS"
|
84
|
+
o.env_parser do |value|
|
85
|
+
value&.split(",")&.map(&:strip)
|
86
|
+
end
|
87
|
+
|
88
|
+
o.type :array
|
89
|
+
o.default []
|
90
|
+
end
|
91
|
+
|
92
|
+
# An array of class names, values of which will be redacted from
|
93
|
+
# dynamic instrumentation snapshots. Example: FooClass.
|
94
|
+
# If a name is suffixed by '*', it becomes a wildcard and
|
95
|
+
# instances of any class whose name begins with the specified
|
96
|
+
# prefix will be redacted (example: Foo*).
|
97
|
+
#
|
98
|
+
# The names must all be fully-qualified, if any prefix of a
|
99
|
+
# class name is configured to be redacted, the value will be
|
100
|
+
# subject to redaction. For example, if Foo* is in the
|
101
|
+
# redacted class name list, instances of Foo, FooBar,
|
102
|
+
# Foo::Bar are all subject to redaction, but Bar::Foo will
|
103
|
+
# not be subject to redaction.
|
104
|
+
#
|
105
|
+
# Leading double-colon is permitted but has no effect,
|
106
|
+
# because the names are always considered to be fully-qualified.
|
107
|
+
# For example, adding ::Foo to the list will redact instances
|
108
|
+
# of Foo.
|
109
|
+
#
|
110
|
+
# Trailing colons should not be used because they will trigger
|
111
|
+
# exact match behavior but Ruby class names do not have
|
112
|
+
# trailing colons. For example, Foo:: will not cause anything
|
113
|
+
# to be redacted. Use Foo::* to redact all classes under
|
114
|
+
# the Foo module.
|
115
|
+
option :redacted_type_names do |o|
|
116
|
+
o.env "DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES"
|
117
|
+
o.env_parser do |value|
|
118
|
+
value&.split(",")&.map(&:strip)
|
119
|
+
end
|
120
|
+
|
121
|
+
o.type :array
|
122
|
+
o.default []
|
123
|
+
end
|
124
|
+
|
125
|
+
# Maximum number of object or collection traversals that
|
126
|
+
# will be permitted when serializing captured values.
|
127
|
+
option :max_capture_depth do |o|
|
128
|
+
o.type :int
|
129
|
+
o.default 3
|
130
|
+
end
|
131
|
+
|
132
|
+
# Maximum number of collection (Array and Hash) elements
|
133
|
+
# that will be captured. Arrays and hashes that have more
|
134
|
+
# elements will be truncated to this many elements.
|
135
|
+
option :max_capture_collection_size do |o|
|
136
|
+
o.type :int
|
137
|
+
o.default 100
|
138
|
+
end
|
139
|
+
|
140
|
+
# Strings longer than this length will be truncated to this
|
141
|
+
# length in dynamic instrumentation snapshots.
|
142
|
+
#
|
143
|
+
# Note that while all values are stringified during
|
144
|
+
# serialization, only values which are originally instances
|
145
|
+
# of the String class are subject to this length limit.
|
146
|
+
option :max_capture_string_length do |o|
|
147
|
+
o.type :int
|
148
|
+
o.default 255
|
149
|
+
end
|
150
|
+
|
151
|
+
# Maximim number of attributes that will be captured for
|
152
|
+
# a single non-primitive value.
|
153
|
+
option :max_capture_attribute_count do |o|
|
154
|
+
o.type :int
|
155
|
+
o.default 20
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module DI
|
5
|
+
# Base class for Dynamic Instrumentation exceptions.
|
6
|
+
#
|
7
|
+
# None of these exceptions should be propagated out of DI to user
|
8
|
+
# applications, therefore these exceptions are not considered to be
|
9
|
+
# part of the public API of the library.
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
class Error < StandardError
|
13
|
+
# Probe does not contain a line number (i.e., is not a line probe).
|
14
|
+
class MissingLineNumber < Error
|
15
|
+
end
|
16
|
+
|
17
|
+
# Failed to communicate to the local Datadog agent (e.g. to send
|
18
|
+
# probe status or a snapshot).
|
19
|
+
class AgentCommunicationError < Error
|
20
|
+
end
|
21
|
+
|
22
|
+
# Attempting to instrument a method or file which does not exist.
|
23
|
+
#
|
24
|
+
# This could be due to the code that is referenced in the probe
|
25
|
+
# having not been loaded yet, or due to the probe referencing code
|
26
|
+
# that does not in fact exist anywhere (e.g. due to a misspelling).
|
27
|
+
class DITargetNotDefined < Error
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../core/configuration"
|
4
|
+
require_relative "configuration"
|
5
|
+
|
6
|
+
module Datadog
|
7
|
+
module DI
|
8
|
+
# Extends Datadog tracing with DI features
|
9
|
+
module Extensions
|
10
|
+
# Inject DI into global objects.
|
11
|
+
def self.activate!
|
12
|
+
Core::Configuration::Settings.extend(Configuration::Settings)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,301 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Lint/AssignmentInCondition
|
4
|
+
|
5
|
+
require 'benchmark'
|
6
|
+
|
7
|
+
module Datadog
|
8
|
+
module DI
|
9
|
+
# Arranges to invoke a callback when a particular Ruby method or
|
10
|
+
# line of code is executed.
|
11
|
+
#
|
12
|
+
# Method instrumentation is accomplished via module prepending.
|
13
|
+
# Unlike the alias_method_chain pattern, module prepending permits
|
14
|
+
# removing instrumentation with no virtually performance side-effects
|
15
|
+
# (the target class retains an empty included module, but no additional
|
16
|
+
# code is executed as part of target method).
|
17
|
+
#
|
18
|
+
# Method hooking works with explicitly defined methods and "virtual"
|
19
|
+
# methods defined via method_missing.
|
20
|
+
#
|
21
|
+
# Line instrumentation is normally accomplished with a targeted line
|
22
|
+
# trace point. This requires MRI and at least Ruby 2.6.
|
23
|
+
# For testing purposes, it is also possible to use untargeted trace
|
24
|
+
# points, but they have a huge performance penalty and should generally
|
25
|
+
# not be used in production.
|
26
|
+
#
|
27
|
+
# Targeted line trace points require tracking of loaded code; see
|
28
|
+
# the CodeTracker class for more details.
|
29
|
+
#
|
30
|
+
# Instrumentation state (i.e., the module or trace point used for
|
31
|
+
# instrumentation) is stored in the Probe instance. Thus, Instrumenter
|
32
|
+
# mutates attributes of Probes it is asked to install or remove.
|
33
|
+
# A previous version of the code attempted to maintain the instrumentation
|
34
|
+
# state within Instrumenter but this was very messy and hard to
|
35
|
+
# guarantee correctness of. With the state stored in Probes, it is
|
36
|
+
# straightforward to determine if a Probe has been successfully instrumented,
|
37
|
+
# and thus requires cleanup, and to properly clean it up.
|
38
|
+
#
|
39
|
+
# Note that the upstream code is responsible for generally storing Probes.
|
40
|
+
# This is normally accomplished by ProbeManager. ProbeManager stores all
|
41
|
+
# known probes, instrumented or not, and is responsible for calling
|
42
|
+
# +unhook+ of Instrumenter to clean up instrumentation when a user
|
43
|
+
# deletes a probe in UI or when DI is shut down.
|
44
|
+
#
|
45
|
+
# Given the need to store state, and also that there are several Probe
|
46
|
+
# attributes that affect how instrumentation is set up and that must be
|
47
|
+
# consulted very early in the callback invocation (e.g., to perform
|
48
|
+
# rate limiting correctly), Instrumenter takes Probe instances as
|
49
|
+
# arguments rather than e.g. file + line number or class + method name.
|
50
|
+
# As a result, Instrumenter is rather coupled to DI the product and is
|
51
|
+
# not trivially usable as a general-purpose Ruby instrumentation tool
|
52
|
+
# (however, Probe instances can be replaced by OpenStruct instances
|
53
|
+
# providing the same interface with not much effort).
|
54
|
+
#
|
55
|
+
# @api private
|
56
|
+
class Instrumenter
|
57
|
+
def initialize(settings, serializer, logger, code_tracker: nil)
|
58
|
+
@settings = settings
|
59
|
+
@serializer = serializer
|
60
|
+
@logger = logger
|
61
|
+
@code_tracker = code_tracker
|
62
|
+
|
63
|
+
@lock = Mutex.new
|
64
|
+
end
|
65
|
+
|
66
|
+
attr_reader :settings
|
67
|
+
attr_reader :serializer
|
68
|
+
attr_reader :logger
|
69
|
+
attr_reader :code_tracker
|
70
|
+
|
71
|
+
# This is a substitute for Thread::Backtrace::Location
|
72
|
+
# which does not have a public constructor.
|
73
|
+
# Used for the fabricated stack frame for the method itself
|
74
|
+
# for method probes (which use Module#prepend and thus aren't called
|
75
|
+
# from the method but from outside of the method).
|
76
|
+
Location = Struct.new(:path, :lineno, :label)
|
77
|
+
|
78
|
+
def hook_method(probe, &block)
|
79
|
+
unless block
|
80
|
+
raise ArgumentError, 'block is required'
|
81
|
+
end
|
82
|
+
|
83
|
+
lock.synchronize do
|
84
|
+
if probe.instrumentation_module
|
85
|
+
# Already instrumented, warn?
|
86
|
+
return
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
cls = symbolize_class_name(probe.type_name)
|
91
|
+
serializer = self.serializer
|
92
|
+
method_name = probe.method_name
|
93
|
+
target_method = cls.instance_method(method_name)
|
94
|
+
loc = target_method.source_location
|
95
|
+
rate_limiter = probe.rate_limiter
|
96
|
+
|
97
|
+
mod = Module.new do
|
98
|
+
define_method(method_name) do |*args, **kwargs| # steep:ignore
|
99
|
+
if rate_limiter.nil? || rate_limiter.allow?
|
100
|
+
# Arguments may be mutated by the method, therefore
|
101
|
+
# they need to be serialized prior to method invocation.
|
102
|
+
entry_args = if probe.capture_snapshot?
|
103
|
+
serializer.serialize_args(args, kwargs)
|
104
|
+
end
|
105
|
+
rv = nil
|
106
|
+
duration = Benchmark.realtime do # steep:ignore
|
107
|
+
rv = super(*args, **kwargs)
|
108
|
+
end
|
109
|
+
# The method itself is not part of the stack trace because
|
110
|
+
# we are getting the stack trace from outside of the method.
|
111
|
+
# Add the method in manually as the top frame.
|
112
|
+
method_frame = Location.new(loc.first, loc.last, method_name)
|
113
|
+
caller_locs = [method_frame] + caller_locations # steep:ignore
|
114
|
+
# TODO capture arguments at exit
|
115
|
+
# & is to stop steep complaints, block is always present here.
|
116
|
+
block&.call(probe: probe, rv: rv, duration: duration, caller_locations: caller_locs,
|
117
|
+
serialized_entry_args: entry_args)
|
118
|
+
rv
|
119
|
+
else
|
120
|
+
super(*args, **kwargs)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
lock.synchronize do
|
126
|
+
if probe.instrumentation_module
|
127
|
+
# Already instrumented from another thread
|
128
|
+
return
|
129
|
+
end
|
130
|
+
|
131
|
+
probe.instrumentation_module = mod
|
132
|
+
cls.send(:prepend, mod)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def unhook_method(probe)
|
137
|
+
# Ruby does not permit removing modules from classes.
|
138
|
+
# We can, however, remove method definitions from modules.
|
139
|
+
# After this the modules remain in memory and stay included
|
140
|
+
# in the classes but are empty (have no methods).
|
141
|
+
lock.synchronize do
|
142
|
+
if mod = probe.instrumentation_module
|
143
|
+
mod.send(:remove_method, probe.method_name)
|
144
|
+
probe.instrumentation_module = nil
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Instruments a particluar line in a source file.
|
150
|
+
# Note that this method only works for physical files,
|
151
|
+
# not for eval'd code, unless the eval'd code is associated with
|
152
|
+
# a file name and client invokes this method with the correct
|
153
|
+
# file name for the eval'd code.
|
154
|
+
def hook_line(probe, &block)
|
155
|
+
unless block
|
156
|
+
raise ArgumentError, 'No block given to hook_line'
|
157
|
+
end
|
158
|
+
|
159
|
+
lock.synchronize do
|
160
|
+
if probe.instrumentation_trace_point
|
161
|
+
# Already instrumented, warn?
|
162
|
+
return
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
line_no = probe.line_no!
|
167
|
+
rate_limiter = probe.rate_limiter
|
168
|
+
|
169
|
+
# Memoize the value to ensure this method always uses the same
|
170
|
+
# value for the setting.
|
171
|
+
# Normally none of the settings should change, but in the test suite
|
172
|
+
# we use mock objects and the methods may be mocked with
|
173
|
+
# individual invocations, yielding different return values on
|
174
|
+
# different calls to the same method.
|
175
|
+
permit_untargeted_trace_points = settings.dynamic_instrumentation.untargeted_trace_points
|
176
|
+
|
177
|
+
iseq = nil
|
178
|
+
if code_tracker
|
179
|
+
iseq = code_tracker.iseqs_for_path_suffix(probe.file).first # steep:ignore
|
180
|
+
unless iseq
|
181
|
+
if permit_untargeted_trace_points
|
182
|
+
# Continue withoout targeting the trace point.
|
183
|
+
# This is going to cause a serious performance penalty for
|
184
|
+
# the entire file containing the line to be instrumented.
|
185
|
+
else
|
186
|
+
# Do not use untargeted trace points unless they have been
|
187
|
+
# explicitly requested by the user, since they cause a
|
188
|
+
# serious performance penalty.
|
189
|
+
#
|
190
|
+
# If the requested file is not in code tracker's registry,
|
191
|
+
# or the code tracker does not exist at all,
|
192
|
+
# do not attempt to instrumnet now.
|
193
|
+
# The caller should add the line to the list of pending lines
|
194
|
+
# to instrument and install the hook when the file in
|
195
|
+
# question is loaded (and hopefully, by then code tracking
|
196
|
+
# is active, otherwise the line will never be instrumented.)
|
197
|
+
raise Error::DITargetNotDefined, "File not in code tracker registry: #{probe.file}"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
elsif !permit_untargeted_trace_points
|
201
|
+
# Same as previous comment, if untargeted trace points are not
|
202
|
+
# explicitly defined, and we do not have code tracking, do not
|
203
|
+
# instrument the method.
|
204
|
+
raise Error::DITargetNotDefined, "File not in code tracker registry: #{probe.file}"
|
205
|
+
end
|
206
|
+
|
207
|
+
# If trace point is not targeted, we only need one trace point per file.
|
208
|
+
# Creating a trace point for each probe does work but the performance
|
209
|
+
# penalty will be taken for each trace point defined in the file.
|
210
|
+
# Since untargeted trace points are only (currently) used internally
|
211
|
+
# for benchmarking, and shouldn't be used in customer applications,
|
212
|
+
# we always create a trace point here to reduce complexity.
|
213
|
+
#
|
214
|
+
# For targeted trace points, if multiple probes target the same
|
215
|
+
# file and line, we also only need one trace point, but since the
|
216
|
+
# overhead of targeted trace points is minimal, don't worry about
|
217
|
+
# this optimization just yet and create a trace point for each probe.
|
218
|
+
|
219
|
+
tp = TracePoint.new(:line) do |tp|
|
220
|
+
# If trace point is not targeted, we must verify that the invocation
|
221
|
+
# is the file & line that we want, because untargeted trace points
|
222
|
+
# are invoked for *each* line of Ruby executed.
|
223
|
+
if iseq || tp.lineno == probe.line_no && probe.file_matches?(tp.path)
|
224
|
+
if rate_limiter.nil? || rate_limiter.allow?
|
225
|
+
# & is to stop steep complaints, block is always present here.
|
226
|
+
block&.call(probe: probe, trace_point: tp, caller_locations: caller_locations)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
rescue => exc
|
230
|
+
raise if settings.dynamic_instrumentation.propagate_all_exceptions
|
231
|
+
logger.warn("Unhandled exception in line trace point: #{exc.class}: #{exc}")
|
232
|
+
# TODO test this path
|
233
|
+
end
|
234
|
+
|
235
|
+
# TODO internal check - remove or use a proper exception
|
236
|
+
if !iseq && !permit_untargeted_trace_points
|
237
|
+
raise "Trying to use an untargeted trace point when user did not permit it"
|
238
|
+
end
|
239
|
+
|
240
|
+
lock.synchronize do
|
241
|
+
if probe.instrumentation_trace_point
|
242
|
+
# Already instrumented in another thread, warn?
|
243
|
+
return
|
244
|
+
end
|
245
|
+
|
246
|
+
probe.instrumentation_trace_point = tp
|
247
|
+
|
248
|
+
if iseq
|
249
|
+
tp.enable(target: iseq, target_line: line_no)
|
250
|
+
else
|
251
|
+
tp.enable
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def unhook_line(probe)
|
257
|
+
lock.synchronize do
|
258
|
+
if tp = probe.instrumentation_trace_point
|
259
|
+
tp.disable
|
260
|
+
probe.instrumentation_trace_point = nil
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def hook(probe, &block)
|
266
|
+
if probe.method?
|
267
|
+
hook_method(probe, &block)
|
268
|
+
elsif probe.line?
|
269
|
+
hook_line(probe, &block)
|
270
|
+
else
|
271
|
+
# TODO add test coverage for this path
|
272
|
+
logger.warn("Unknown probe type to hook: #{probe}")
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def unhook(probe)
|
277
|
+
if probe.method?
|
278
|
+
unhook_method(probe)
|
279
|
+
elsif probe.line?
|
280
|
+
unhook_line(probe)
|
281
|
+
else
|
282
|
+
# TODO add test coverage for this path
|
283
|
+
logger.warn("Unknown probe type to unhook: #{probe}")
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
private
|
288
|
+
|
289
|
+
attr_reader :lock
|
290
|
+
|
291
|
+
# TODO test that this resolves qualified names e.g. A::B
|
292
|
+
def symbolize_class_name(cls_name)
|
293
|
+
Object.const_get(cls_name)
|
294
|
+
rescue NameError => exc
|
295
|
+
raise Error::DITargetNotDefined, "Class not defined: #{cls_name}: #{exc.class}: #{exc}"
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
# rubocop:enable Lint/AssignmentInCondition
|