datadog 2.8.0 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -1
  3. data/ext/datadog_profiling_native_extension/clock_id.h +2 -2
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +64 -54
  5. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
  6. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +1 -1
  7. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +16 -16
  8. data/ext/datadog_profiling_native_extension/collectors_stack.c +7 -7
  9. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +219 -122
  10. data/ext/datadog_profiling_native_extension/heap_recorder.h +1 -1
  11. data/ext/datadog_profiling_native_extension/http_transport.c +4 -4
  12. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +3 -0
  13. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -1
  14. data/ext/datadog_profiling_native_extension/profiling.c +10 -8
  15. data/ext/datadog_profiling_native_extension/ruby_helpers.c +8 -8
  16. data/ext/datadog_profiling_native_extension/stack_recorder.c +54 -54
  17. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -1
  18. data/ext/datadog_profiling_native_extension/time_helpers.h +1 -1
  19. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +47 -0
  20. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +31 -0
  21. data/ext/libdatadog_api/crashtracker.c +3 -0
  22. data/lib/datadog/appsec/assets/waf_rules/recommended.json +355 -157
  23. data/lib/datadog/appsec/assets/waf_rules/strict.json +62 -32
  24. data/lib/datadog/appsec/context.rb +54 -0
  25. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +7 -7
  26. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +6 -6
  27. data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +4 -4
  28. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +19 -28
  29. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +5 -5
  30. data/lib/datadog/appsec/contrib/rack/gateway/response.rb +3 -3
  31. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +64 -96
  32. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +10 -10
  33. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +5 -5
  34. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +6 -6
  35. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +10 -11
  36. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +43 -49
  37. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +21 -32
  38. data/lib/datadog/appsec/contrib/rails/patcher.rb +1 -1
  39. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +6 -6
  40. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +41 -63
  41. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +2 -2
  42. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +5 -5
  43. data/lib/datadog/appsec/event.rb +6 -6
  44. data/lib/datadog/appsec/ext.rb +3 -1
  45. data/lib/datadog/appsec/monitor/gateway/watcher.rb +22 -32
  46. data/lib/datadog/appsec/monitor/reactive/set_user.rb +5 -5
  47. data/lib/datadog/appsec/processor/rule_loader.rb +0 -3
  48. data/lib/datadog/appsec.rb +3 -3
  49. data/lib/datadog/auto_instrument.rb +3 -0
  50. data/lib/datadog/core/configuration/agent_settings_resolver.rb +39 -11
  51. data/lib/datadog/core/configuration/components.rb +4 -2
  52. data/lib/datadog/core/configuration.rb +1 -1
  53. data/lib/datadog/{tracing → core}/contrib/rails/utils.rb +1 -3
  54. data/lib/datadog/core/crashtracking/component.rb +1 -3
  55. data/lib/datadog/core/telemetry/event.rb +87 -3
  56. data/lib/datadog/core/telemetry/logging.rb +2 -2
  57. data/lib/datadog/core/telemetry/metric.rb +22 -0
  58. data/lib/datadog/core/telemetry/worker.rb +33 -0
  59. data/lib/datadog/di/base.rb +115 -0
  60. data/lib/datadog/di/code_tracker.rb +7 -4
  61. data/lib/datadog/di/component.rb +17 -11
  62. data/lib/datadog/di/configuration/settings.rb +11 -1
  63. data/lib/datadog/di/contrib/railtie.rb +15 -0
  64. data/lib/datadog/di/contrib.rb +26 -0
  65. data/lib/datadog/di/error.rb +5 -0
  66. data/lib/datadog/di/instrumenter.rb +39 -18
  67. data/lib/datadog/di/{init.rb → preload.rb} +2 -4
  68. data/lib/datadog/di/probe_manager.rb +4 -4
  69. data/lib/datadog/di/probe_notification_builder.rb +16 -2
  70. data/lib/datadog/di/probe_notifier_worker.rb +5 -6
  71. data/lib/datadog/di/remote.rb +4 -4
  72. data/lib/datadog/di/transport.rb +2 -4
  73. data/lib/datadog/di.rb +5 -108
  74. data/lib/datadog/kit/appsec/events.rb +3 -3
  75. data/lib/datadog/kit/identity.rb +4 -4
  76. data/lib/datadog/profiling/component.rb +55 -53
  77. data/lib/datadog/profiling/http_transport.rb +1 -26
  78. data/lib/datadog/tracing/contrib/action_cable/integration.rb +5 -2
  79. data/lib/datadog/tracing/contrib/action_mailer/integration.rb +6 -2
  80. data/lib/datadog/tracing/contrib/action_pack/integration.rb +5 -2
  81. data/lib/datadog/tracing/contrib/action_view/integration.rb +5 -2
  82. data/lib/datadog/tracing/contrib/active_job/integration.rb +5 -2
  83. data/lib/datadog/tracing/contrib/active_record/integration.rb +6 -2
  84. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +3 -1
  85. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +3 -1
  86. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +10 -0
  87. data/lib/datadog/tracing/contrib/active_support/integration.rb +5 -2
  88. data/lib/datadog/tracing/contrib/auto_instrument.rb +2 -2
  89. data/lib/datadog/tracing/contrib/aws/integration.rb +3 -0
  90. data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +3 -0
  91. data/lib/datadog/tracing/contrib/httprb/integration.rb +3 -0
  92. data/lib/datadog/tracing/contrib/kafka/integration.rb +3 -0
  93. data/lib/datadog/tracing/contrib/mongodb/integration.rb +3 -0
  94. data/lib/datadog/tracing/contrib/opensearch/integration.rb +3 -0
  95. data/lib/datadog/tracing/contrib/presto/integration.rb +3 -0
  96. data/lib/datadog/tracing/contrib/rack/integration.rb +2 -2
  97. data/lib/datadog/tracing/contrib/rails/framework.rb +2 -2
  98. data/lib/datadog/tracing/contrib/rails/patcher.rb +1 -1
  99. data/lib/datadog/tracing/contrib/rest_client/integration.rb +3 -0
  100. data/lib/datadog/tracing/span.rb +12 -4
  101. data/lib/datadog/tracing/span_event.rb +123 -3
  102. data/lib/datadog/tracing/span_operation.rb +6 -0
  103. data/lib/datadog/tracing/transport/serializable_trace.rb +24 -6
  104. data/lib/datadog/version.rb +1 -1
  105. metadata +19 -10
  106. data/lib/datadog/appsec/reactive/operation.rb +0 -68
  107. data/lib/datadog/appsec/scope.rb +0 -58
  108. 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
- # TODO we do not have DI.component defined yet, remove steep:ignore
91
- # before release.
92
- if component = DI.current_component # steep:ignore
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.warn("Unhandled exception in script_compiled trace point: #{exc.class}: #{exc}")
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
@@ -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
- Datadog.logger.debug("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")
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, Datadog.logger, code_tracker: DI.code_tracker, telemetry: telemetry).tap do |component|
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, Datadog.logger, code_tracker: DI.code_tracker, telemetry: telemetry)
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
- Datadog.logger.debug("Not enabling dynamic instrumentation because we are in development environment")
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' || RUBY_VERSION < '2.6'
61
- Datadog.logger.debug("Not enabling dynamic instrumentation because of unsupported Ruby version")
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 :int
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
@@ -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
- # rubocop:disable Lint/AssignmentInCondition
3
+ require_relative '../core/utils/time'
4
4
 
5
- require 'benchmark'
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
- rv = nil
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
- duration = Benchmark.realtime do # steep:ignore
122
- rv = if args.any?
123
- if kwargs.any?
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 instrumnet now.
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.warn("Unhandled exception in line trace point: #{exc.class}: #{exc}")
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.warn("Unhandled exception in line trace point: #{exc.class}: #{exc}")
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.warn("Unknown probe type to hook: #{probe}")
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.warn("Unknown probe type to unhook: #{probe}")
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/init' early in the application boot process to
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 '../tracing'
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.warn("Unhandled exception in definition trace point: #{exc.class}: #{exc}")
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.warn("Error processing probe configuration: #{exc.class}: #{exc}")
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.warn("Error removing probe #{probe.id}: #{exc.class}: #{exc}")
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.warn("Error installing probe after class is defined: #{exc.class}: #{exc}")
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": Datadog::Tracing.active_trace&.id&.to_s,
145
- "dd.span_id": Datadog::Tracing.active_span&.id&.to_s,
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.warn("Error in probe notifier worker: #{exc.class}: #{exc} (at #{exc.backtrace.first})")
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
- # TODO determine a suitable limit via testing/benchmarking
187
- if queue.length > 100
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.warn("failed to send #{event_name}: #{exc.class}: #{exc} (at #{exc.backtrace.first})")
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.warn("Unexpected #{event_name} queue underflow - consumed elsewhere?")
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