datadog 2.8.0 → 2.10.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.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +62 -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 +66 -56
  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_stack.h +2 -2
  10. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +221 -127
  11. data/ext/datadog_profiling_native_extension/heap_recorder.c +50 -92
  12. data/ext/datadog_profiling_native_extension/heap_recorder.h +2 -2
  13. data/ext/datadog_profiling_native_extension/http_transport.c +4 -4
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +3 -0
  15. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -1
  16. data/ext/datadog_profiling_native_extension/profiling.c +10 -8
  17. data/ext/datadog_profiling_native_extension/ruby_helpers.c +8 -8
  18. data/ext/datadog_profiling_native_extension/stack_recorder.c +63 -76
  19. data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -2
  20. data/ext/datadog_profiling_native_extension/time_helpers.h +1 -1
  21. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +47 -0
  22. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +31 -0
  23. data/ext/libdatadog_api/crashtracker.c +3 -0
  24. data/lib/datadog/appsec/actions_handler.rb +27 -0
  25. data/lib/datadog/appsec/assets/waf_rules/recommended.json +355 -157
  26. data/lib/datadog/appsec/assets/waf_rules/strict.json +62 -32
  27. data/lib/datadog/appsec/component.rb +14 -8
  28. data/lib/datadog/appsec/configuration/settings.rb +9 -0
  29. data/lib/datadog/appsec/context.rb +74 -0
  30. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +12 -8
  31. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +6 -6
  32. data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +4 -4
  33. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +1 -7
  34. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +20 -30
  35. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +6 -6
  36. data/lib/datadog/appsec/contrib/rack/gateway/response.rb +3 -3
  37. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +67 -96
  38. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +11 -11
  39. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +6 -6
  40. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +7 -7
  41. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +10 -11
  42. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +43 -60
  43. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +23 -33
  44. data/lib/datadog/appsec/contrib/rails/patcher.rb +4 -14
  45. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +7 -7
  46. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +45 -65
  47. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +5 -28
  48. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +6 -6
  49. data/lib/datadog/appsec/event.rb +6 -6
  50. data/lib/datadog/appsec/ext.rb +8 -1
  51. data/lib/datadog/appsec/metrics/collector.rb +38 -0
  52. data/lib/datadog/appsec/metrics/exporter.rb +35 -0
  53. data/lib/datadog/appsec/metrics/telemetry.rb +23 -0
  54. data/lib/datadog/appsec/metrics.rb +13 -0
  55. data/lib/datadog/appsec/monitor/gateway/watcher.rb +23 -32
  56. data/lib/datadog/appsec/monitor/reactive/set_user.rb +6 -6
  57. data/lib/datadog/appsec/processor/rule_loader.rb +0 -3
  58. data/lib/datadog/appsec/processor.rb +4 -3
  59. data/lib/datadog/appsec/response.rb +18 -80
  60. data/lib/datadog/appsec/security_engine/result.rb +67 -0
  61. data/lib/datadog/appsec/security_engine/runner.rb +88 -0
  62. data/lib/datadog/appsec/security_engine.rb +9 -0
  63. data/lib/datadog/appsec.rb +17 -8
  64. data/lib/datadog/auto_instrument.rb +3 -0
  65. data/lib/datadog/core/configuration/agent_settings_resolver.rb +39 -11
  66. data/lib/datadog/core/configuration/components.rb +4 -2
  67. data/lib/datadog/core/configuration.rb +1 -1
  68. data/lib/datadog/{tracing → core}/contrib/rails/utils.rb +1 -3
  69. data/lib/datadog/core/crashtracking/component.rb +1 -3
  70. data/lib/datadog/core/telemetry/event.rb +87 -3
  71. data/lib/datadog/core/telemetry/logging.rb +2 -2
  72. data/lib/datadog/core/telemetry/metric.rb +22 -0
  73. data/lib/datadog/core/telemetry/worker.rb +33 -0
  74. data/lib/datadog/di/base.rb +115 -0
  75. data/lib/datadog/di/code_tracker.rb +7 -4
  76. data/lib/datadog/di/component.rb +19 -11
  77. data/lib/datadog/di/configuration/settings.rb +11 -1
  78. data/lib/datadog/di/contrib/railtie.rb +15 -0
  79. data/lib/datadog/di/contrib.rb +26 -0
  80. data/lib/datadog/di/error.rb +5 -0
  81. data/lib/datadog/di/instrumenter.rb +39 -18
  82. data/lib/datadog/di/{init.rb → preload.rb} +2 -4
  83. data/lib/datadog/di/probe_manager.rb +4 -4
  84. data/lib/datadog/di/probe_notification_builder.rb +22 -2
  85. data/lib/datadog/di/probe_notifier_worker.rb +5 -6
  86. data/lib/datadog/di/redactor.rb +0 -1
  87. data/lib/datadog/di/remote.rb +30 -9
  88. data/lib/datadog/di/transport.rb +2 -4
  89. data/lib/datadog/di.rb +5 -108
  90. data/lib/datadog/kit/appsec/events.rb +3 -3
  91. data/lib/datadog/kit/identity.rb +4 -4
  92. data/lib/datadog/profiling/component.rb +55 -53
  93. data/lib/datadog/profiling/http_transport.rb +1 -26
  94. data/lib/datadog/tracing/contrib/action_cable/integration.rb +5 -2
  95. data/lib/datadog/tracing/contrib/action_mailer/integration.rb +6 -2
  96. data/lib/datadog/tracing/contrib/action_pack/integration.rb +5 -2
  97. data/lib/datadog/tracing/contrib/action_view/integration.rb +5 -2
  98. data/lib/datadog/tracing/contrib/active_job/integration.rb +5 -2
  99. data/lib/datadog/tracing/contrib/active_record/integration.rb +6 -2
  100. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +3 -1
  101. data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +3 -1
  102. data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +10 -0
  103. data/lib/datadog/tracing/contrib/active_support/integration.rb +5 -2
  104. data/lib/datadog/tracing/contrib/auto_instrument.rb +2 -2
  105. data/lib/datadog/tracing/contrib/aws/integration.rb +3 -0
  106. data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +3 -0
  107. data/lib/datadog/tracing/contrib/extensions.rb +15 -3
  108. data/lib/datadog/tracing/contrib/http/integration.rb +3 -0
  109. data/lib/datadog/tracing/contrib/httprb/integration.rb +3 -0
  110. data/lib/datadog/tracing/contrib/kafka/integration.rb +3 -0
  111. data/lib/datadog/tracing/contrib/mongodb/integration.rb +3 -0
  112. data/lib/datadog/tracing/contrib/opensearch/integration.rb +3 -0
  113. data/lib/datadog/tracing/contrib/presto/integration.rb +3 -0
  114. data/lib/datadog/tracing/contrib/rack/integration.rb +2 -2
  115. data/lib/datadog/tracing/contrib/rails/framework.rb +2 -2
  116. data/lib/datadog/tracing/contrib/rails/patcher.rb +1 -1
  117. data/lib/datadog/tracing/contrib/rest_client/integration.rb +3 -0
  118. data/lib/datadog/tracing/span.rb +12 -4
  119. data/lib/datadog/tracing/span_event.rb +123 -3
  120. data/lib/datadog/tracing/span_operation.rb +6 -0
  121. data/lib/datadog/tracing/transport/serializable_trace.rb +24 -6
  122. data/lib/datadog/version.rb +1 -1
  123. metadata +40 -17
  124. data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
  125. data/lib/datadog/appsec/processor/context.rb +0 -107
  126. data/lib/datadog/appsec/reactive/operation.rb +0 -68
  127. data/lib/datadog/appsec/scope.rb +0 -58
  128. 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
@@ -70,6 +76,7 @@ module Datadog
70
76
  @agent_settings = agent_settings
71
77
  @logger = logger
72
78
  @telemetry = telemetry
79
+ @code_tracker = code_tracker
73
80
  @redactor = Redactor.new(settings)
74
81
  @serializer = Serializer.new(settings, redactor, telemetry: telemetry)
75
82
  @instrumenter = Instrumenter.new(settings, serializer, logger, code_tracker: code_tracker, telemetry: telemetry)
@@ -84,6 +91,7 @@ module Datadog
84
91
  attr_reader :agent_settings
85
92
  attr_reader :logger
86
93
  attr_reader :telemetry
94
+ attr_reader :code_tracker
87
95
  attr_reader :instrumenter
88
96
  attr_reader :transport
89
97
  attr_reader :probe_notifier_worker
@@ -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
@@ -32,6 +32,12 @@ module Datadog
32
32
  status: 'EMITTING',)
33
33
  end
34
34
 
35
+ def build_errored(probe, exc)
36
+ build_status(probe,
37
+ message: "Instrumentation for probe #{probe.id} failed: #{exc}",
38
+ status: 'ERROR',)
39
+ end
40
+
35
41
  # Duration is in seconds.
36
42
  def build_executed(probe,
37
43
  trace_point: nil, rv: nil, duration: nil, caller_locations: nil,
@@ -141,8 +147,8 @@ module Datadog
141
147
  version: 2,
142
148
  },
143
149
  # 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,
150
+ "dd.trace_id": active_trace&.id&.to_s,
151
+ "dd.span_id": active_span&.id&.to_s,
146
152
  ddsource: 'dd_debugger',
147
153
  message: probe.template && evaluate_template(probe.template,
148
154
  duration: duration ? duration * 1000 : 0),
@@ -150,6 +156,8 @@ module Datadog
150
156
  }
151
157
  end
152
158
 
159
+ private
160
+
153
161
  def build_status(probe, message:, status:)
154
162
  {
155
163
  service: settings.service,
@@ -200,6 +208,18 @@ module Datadog
200
208
  map[name] = value
201
209
  end
202
210
  end
211
+
212
+ def active_trace
213
+ if defined?(Datadog::Tracing)
214
+ Datadog::Tracing.active_trace
215
+ end
216
+ end
217
+
218
+ def active_span
219
+ if defined?(Datadog::Tracing)
220
+ Datadog::Tracing.active_span
221
+ end
222
+ end
203
223
  end
204
224
  end
205
225
  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