datadog 2.7.1 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -1
  3. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +47 -17
  4. data/ext/datadog_profiling_native_extension/extconf.rb +0 -8
  5. data/ext/datadog_profiling_native_extension/heap_recorder.c +11 -89
  6. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +1 -1
  7. data/ext/datadog_profiling_native_extension/stack_recorder.c +0 -34
  8. data/ext/libdatadog_extconf_helpers.rb +1 -1
  9. data/lib/datadog/appsec/component.rb +1 -8
  10. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +73 -0
  11. data/lib/datadog/appsec/contrib/active_record/integration.rb +41 -0
  12. data/lib/datadog/appsec/contrib/active_record/patcher.rb +53 -0
  13. data/lib/datadog/appsec/event.rb +1 -1
  14. data/lib/datadog/appsec/processor/context.rb +2 -2
  15. data/lib/datadog/appsec/remote.rb +1 -3
  16. data/lib/datadog/appsec/response.rb +7 -11
  17. data/lib/datadog/appsec.rb +3 -2
  18. data/lib/datadog/core/configuration/components.rb +17 -1
  19. data/lib/datadog/core/configuration/settings.rb +10 -0
  20. data/lib/datadog/core/configuration.rb +9 -1
  21. data/lib/datadog/core/remote/client/capabilities.rb +6 -0
  22. data/lib/datadog/core/remote/client.rb +65 -59
  23. data/lib/datadog/core/telemetry/component.rb +9 -3
  24. data/lib/datadog/core/telemetry/ext.rb +1 -0
  25. data/lib/datadog/di/code_tracker.rb +5 -4
  26. data/lib/datadog/di/component.rb +5 -1
  27. data/lib/datadog/di/contrib/active_record.rb +1 -0
  28. data/lib/datadog/di/init.rb +20 -0
  29. data/lib/datadog/di/instrumenter.rb +81 -11
  30. data/lib/datadog/di/probe.rb +11 -1
  31. data/lib/datadog/di/probe_builder.rb +1 -0
  32. data/lib/datadog/di/probe_manager.rb +4 -1
  33. data/lib/datadog/di/probe_notification_builder.rb +13 -7
  34. data/lib/datadog/di/remote.rb +124 -0
  35. data/lib/datadog/di/serializer.rb +14 -7
  36. data/lib/datadog/di/transport.rb +1 -1
  37. data/lib/datadog/di/utils.rb +7 -0
  38. data/lib/datadog/di.rb +84 -20
  39. data/lib/datadog/profiling/component.rb +4 -16
  40. data/lib/datadog/tracing/configuration/settings.rb +4 -8
  41. data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +16 -4
  42. data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +4 -0
  43. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
  44. data/lib/datadog/version.rb +2 -2
  45. data/lib/datadog.rb +3 -0
  46. metadata +17 -13
  47. data/lib/datadog/appsec/processor/actions.rb +0 -49
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../patcher'
4
+ require_relative 'instrumentation'
5
+
6
+ module Datadog
7
+ module AppSec
8
+ module Contrib
9
+ module ActiveRecord
10
+ # AppSec patcher module for ActiveRecord
11
+ module Patcher
12
+ include Datadog::AppSec::Contrib::Patcher
13
+
14
+ module_function
15
+
16
+ def patched?
17
+ Patcher.instance_variable_get(:@patched)
18
+ end
19
+
20
+ def target_version
21
+ Integration.version
22
+ end
23
+
24
+ def patch
25
+ ActiveSupport.on_load :active_record do
26
+ instrumentation_module = if ::ActiveRecord.gem_version >= Gem::Version.new('7.1')
27
+ Instrumentation::InternalExecQueryAdapterPatch
28
+ else
29
+ Instrumentation::ExecQueryAdapterPatch
30
+ end
31
+
32
+ if defined?(::ActiveRecord::ConnectionAdapters::SQLite3Adapter)
33
+ ::ActiveRecord::ConnectionAdapters::SQLite3Adapter.prepend(instrumentation_module)
34
+ end
35
+
36
+ if defined?(::ActiveRecord::ConnectionAdapters::Mysql2Adapter)
37
+ ::ActiveRecord::ConnectionAdapters::Mysql2Adapter.prepend(instrumentation_module)
38
+ end
39
+
40
+ if defined?(::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
41
+ unless defined?(::ActiveRecord::ConnectionAdapters::JdbcAdapter)
42
+ instrumentation_module = Instrumentation::ExecuteAndClearAdapterPatch
43
+ end
44
+
45
+ ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(instrumentation_module)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -142,7 +142,7 @@ module Datadog
142
142
  scope.trace.keep! if scope.trace
143
143
 
144
144
  if scope.service_entry_span
145
- scope.service_entry_span.set_tag('appsec.blocked', 'true') if waf_result.actions.include?('block')
145
+ scope.service_entry_span.set_tag('appsec.blocked', 'true') if waf_result.actions.key?('block_request')
146
146
  scope.service_entry_span.set_tag('appsec.event', 'true')
147
147
  end
148
148
 
@@ -19,7 +19,7 @@ module Datadog
19
19
  @events = []
20
20
  @run_mutex = Mutex.new
21
21
 
22
- @libddwaf_debug_tag = "libddwaf:#{WAF::VERSION::STRING}"
22
+ @libddwaf_debug_tag = "libddwaf:#{WAF::VERSION::STRING} method:ddwaf_run"
23
23
  end
24
24
 
25
25
  def run(persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
@@ -79,7 +79,7 @@ module Datadog
79
79
  @context.run(persistent_data, ephemeral_data, timeout)
80
80
  rescue WAF::LibDDWAF::Error => e
81
81
  Datadog.logger.debug { "#{@libddwaf_debug_tag} execution error: #{e} backtrace: #{e.backtrace&.first(3)}" }
82
- @telemetry.report(e, description: 'libddwaf internal low-level error')
82
+ @telemetry.report(e, description: 'libddwaf-rb internal low-level error')
83
83
 
84
84
  [:err_internal, WAF::Result.new(:err_internal, [], 0.0, false, [], [])]
85
85
  end
@@ -67,7 +67,6 @@ module Datadog
67
67
  data = []
68
68
  overrides = []
69
69
  exclusions = []
70
- actions = []
71
70
 
72
71
  repository.contents.each do |content|
73
72
  parsed_content = parse_content(content)
@@ -81,7 +80,6 @@ module Datadog
81
80
  overrides << parsed_content['rules_override'] if parsed_content['rules_override']
82
81
  exclusions << parsed_content['exclusions'] if parsed_content['exclusions']
83
82
  custom_rules << parsed_content['custom_rules'] if parsed_content['custom_rules']
84
- actions.concat(parsed_content['actions']) if parsed_content['actions']
85
83
  end
86
84
  end
87
85
 
@@ -105,7 +103,7 @@ module Datadog
105
103
  telemetry: telemetry
106
104
  )
107
105
 
108
- Datadog::AppSec.reconfigure(ruleset: ruleset, actions: actions, telemetry: telemetry)
106
+ Datadog::AppSec.reconfigure(ruleset: ruleset, telemetry: telemetry)
109
107
  end
110
108
 
111
109
  [receiver]
@@ -31,19 +31,16 @@ module Datadog
31
31
  def negotiate(env, actions)
32
32
  # @type var configured_response: Response?
33
33
  configured_response = nil
34
- actions.each do |action|
34
+ actions.each do |type, parameters|
35
35
  # Need to use next to make steep happy :(
36
36
  # I rather use break to stop the execution
37
37
  next if configured_response
38
38
 
39
- action_configuration = AppSec::Processor::Actions.fetch_configuration(action)
40
- next unless action_configuration
41
-
42
- configured_response = case action_configuration['type']
39
+ configured_response = case type
43
40
  when 'block_request'
44
- block_response(env, action_configuration['parameters'])
41
+ block_response(env, parameters)
45
42
  when 'redirect_request'
46
- redirect_response(env, action_configuration['parameters'])
43
+ redirect_response(env, parameters)
47
44
  end
48
45
  end
49
46
 
@@ -90,7 +87,7 @@ module Datadog
90
87
  body << content(content_type)
91
88
 
92
89
  Response.new(
93
- status: options['status_code'] || 403,
90
+ status: options['status_code']&.to_i || 403,
94
91
  headers: { 'Content-Type' => content_type },
95
92
  body: body,
96
93
  )
@@ -100,15 +97,14 @@ module Datadog
100
97
  if options['location'] && !options['location'].empty?
101
98
  content_type = content_type(env)
102
99
 
103
- status = options['status_code'] >= 300 && options['status_code'] < 400 ? options['status_code'] : 303
104
-
105
100
  headers = {
106
101
  'Content-Type' => content_type,
107
102
  'Location' => options['location']
108
103
  }
109
104
 
105
+ status_code = options['status_code'].to_i
110
106
  Response.new(
111
- status: status,
107
+ status: (status_code >= 300 && status_code < 400 ? status_code : 303),
112
108
  headers: headers,
113
109
  body: [],
114
110
  )
@@ -24,12 +24,12 @@ module Datadog
24
24
  appsec_component.processor if appsec_component
25
25
  end
26
26
 
27
- def reconfigure(ruleset:, actions:, telemetry:)
27
+ def reconfigure(ruleset:, telemetry:)
28
28
  appsec_component = components.appsec
29
29
 
30
30
  return unless appsec_component
31
31
 
32
- appsec_component.reconfigure(ruleset: ruleset, actions: actions, telemetry: telemetry)
32
+ appsec_component.reconfigure(ruleset: ruleset, telemetry: telemetry)
33
33
  end
34
34
 
35
35
  def reconfigure_lock(&block)
@@ -56,6 +56,7 @@ end
56
56
  require_relative 'appsec/contrib/rack/integration'
57
57
  require_relative 'appsec/contrib/sinatra/integration'
58
58
  require_relative 'appsec/contrib/rails/integration'
59
+ require_relative 'appsec/contrib/active_record/integration'
59
60
  require_relative 'appsec/contrib/devise/integration'
60
61
  require_relative 'appsec/contrib/graphql/integration'
61
62
 
@@ -13,6 +13,7 @@ require_relative '../remote/component'
13
13
  require_relative '../../tracing/component'
14
14
  require_relative '../../profiling/component'
15
15
  require_relative '../../appsec/component'
16
+ require_relative '../../di/component'
16
17
  require_relative '../crashtracking/component'
17
18
 
18
19
  module Datadog
@@ -83,6 +84,7 @@ module Datadog
83
84
  :telemetry,
84
85
  :tracer,
85
86
  :crashtracker,
87
+ :dynamic_instrumentation,
86
88
  :appsec
87
89
 
88
90
  def initialize(settings)
@@ -110,12 +112,13 @@ module Datadog
110
112
  @runtime_metrics = self.class.build_runtime_metrics_worker(settings)
111
113
  @health_metrics = self.class.build_health_metrics(settings)
112
114
  @appsec = Datadog::AppSec::Component.build_appsec_component(settings, telemetry: telemetry)
115
+ @dynamic_instrumentation = Datadog::DI::Component.build(settings, agent_settings, telemetry: telemetry)
113
116
 
114
117
  self.class.configure_tracing(settings)
115
118
  end
116
119
 
117
120
  # Starts up components
118
- def startup!(settings)
121
+ def startup!(settings, old_state: nil)
119
122
  if settings.profiling.enabled
120
123
  if profiler
121
124
  profiler.start
@@ -126,6 +129,16 @@ module Datadog
126
129
  end
127
130
  end
128
131
 
132
+ if settings.remote.enabled && old_state&.[](:remote_started)
133
+ # The library was reconfigured and previously it already started
134
+ # the remote component (i.e., it received at least one request
135
+ # through the installed Rack middleware which started the remote).
136
+ # If the new configuration also has remote enabled, start the
137
+ # new remote right away.
138
+ # remote should always be not nil here but steep doesn't know this.
139
+ remote&.start
140
+ end
141
+
129
142
  Core::Diagnostics::EnvironmentLogger.collect_and_log!(@environment_logger_extra)
130
143
  end
131
144
 
@@ -136,6 +149,9 @@ module Datadog
136
149
  # Shutdown remote configuration
137
150
  remote.shutdown! if remote
138
151
 
152
+ # Shutdown DI after remote, since remote config triggers DI operations.
153
+ dynamic_instrumentation&.shutdown!
154
+
139
155
  # Decommission AppSec
140
156
  appsec.shutdown! if appsec
141
157
 
@@ -863,6 +863,16 @@ module Datadog
863
863
  o.type :float
864
864
  o.default 1.0
865
865
  end
866
+
867
+ # Enable log collection for telemetry. Log collection only works when telemetry is enabled and
868
+ # logs are enabled.
869
+ # @default `DD_TELEMETRY_LOG_COLLECTION_ENABLED` environment variable, otherwise `true`.
870
+ # @return [Boolean]
871
+ option :log_collection_enabled do |o|
872
+ o.type :bool
873
+ o.env Core::Telemetry::Ext::ENV_LOG_COLLECTION
874
+ o.default true
875
+ end
866
876
  end
867
877
 
868
878
  # Remote configuration
@@ -258,8 +258,16 @@ module Datadog
258
258
  def replace_components!(settings, old)
259
259
  components = Components.new(settings)
260
260
 
261
+ # Carry over state from existing components to the new ones.
262
+ # Currently, if we already started the remote component (which
263
+ # happens after a request goes through installed Rack middleware),
264
+ # we will start the new remote component as well.
265
+ old_state = {
266
+ remote_started: old.remote&.started?,
267
+ }
268
+
261
269
  old.shutdown!(components)
262
- components.startup!(settings)
270
+ components.startup!(settings, old_state: old_state)
263
271
  components
264
272
  end
265
273
 
@@ -32,6 +32,12 @@ module Datadog
32
32
  register_receivers(Datadog::AppSec::Remote.receivers(@telemetry))
33
33
  end
34
34
 
35
+ if settings.respond_to?(:dynamic_instrumentation) && settings.dynamic_instrumentation.enabled
36
+ register_capabilities(Datadog::DI::Remote.capabilities)
37
+ register_products(Datadog::DI::Remote.products)
38
+ register_receivers(Datadog::DI::Remote.receivers(@telemetry))
39
+ end
40
+
35
41
  register_capabilities(Datadog::Tracing::Remote.capabilities)
36
42
  register_products(Datadog::Tracing::Remote.products)
37
43
  register_receivers(Datadog::Tracing::Remote.receivers(@telemetry))
@@ -24,93 +24,99 @@ module Datadog
24
24
  @dispatcher = Dispatcher.new(@capabilities.receivers)
25
25
  end
26
26
 
27
- # rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity,Metrics/MethodLength,Metrics/CyclomaticComplexity
28
27
  def sync
29
28
  # TODO: Skip sync if no capabilities are registered
30
29
  response = transport.send_config(payload)
31
30
 
32
31
  if response.ok?
33
- # when response is completely empty, do nothing as in: leave as is
34
- if response.empty?
35
- Datadog.logger.debug { 'remote: empty response => NOOP' }
32
+ process_response(response)
33
+ elsif response.internal_error?
34
+ raise TransportError, response.to_s
35
+ end
36
+ end
36
37
 
37
- return
38
- end
38
+ private
39
39
 
40
- begin
41
- paths = response.client_configs.map do |path|
42
- Configuration::Path.parse(path)
43
- end
40
+ def process_response(response)
41
+ # when response is completely empty, do nothing as in: leave as is
42
+ if response.empty?
43
+ Datadog.logger.debug { 'remote: empty response => NOOP' }
44
44
 
45
- targets = Configuration::TargetMap.parse(response.targets)
45
+ return
46
+ end
46
47
 
47
- contents = Configuration::ContentList.parse(response.target_files)
48
- rescue Remote::Configuration::Path::ParseError => e
49
- raise SyncError, e.message
48
+ begin
49
+ paths = response.client_configs.map do |path|
50
+ Configuration::Path.parse(path)
50
51
  end
51
52
 
52
- # To make sure steep does not complain
53
- return unless paths && targets && contents
53
+ targets = Configuration::TargetMap.parse(response.targets)
54
+
55
+ contents = Configuration::ContentList.parse(response.target_files)
56
+ rescue Remote::Configuration::Path::ParseError => e
57
+ raise SyncError, e.message
58
+ end
54
59
 
55
- # TODO: sometimes it can strangely be so that paths.empty?
56
- # TODO: sometimes it can strangely be so that targets.empty?
60
+ # To make sure steep does not complain
61
+ return unless paths && targets && contents
57
62
 
58
- changes = repository.transaction do |current, transaction|
59
- # paths to be removed: previously applied paths minus ingress paths
60
- (current.paths - paths).each { |p| transaction.delete(p) }
63
+ # TODO: sometimes it can strangely be so that paths.empty?
64
+ # TODO: sometimes it can strangely be so that targets.empty?
61
65
 
62
- # go through each ingress path
63
- paths.each do |path|
64
- # match target with path
65
- target = targets[path]
66
+ apply_config(paths, targets, contents)
67
+ end
66
68
 
67
- # abort entirely if matching target not found
68
- raise SyncError, "no target for path '#{path}'" if target.nil?
69
+ def apply_config(paths, targets, contents)
70
+ changes = repository.transaction do |current, transaction|
71
+ # paths to be removed: previously applied paths minus ingress paths
72
+ (current.paths - paths).each { |p| transaction.delete(p) }
69
73
 
70
- # new paths are not in previously applied paths
71
- new = !current.paths.include?(path)
74
+ # go through each ingress path
75
+ paths.each do |path|
76
+ # match target with path
77
+ target = targets[path]
72
78
 
73
- # updated paths are in previously applied paths
74
- # but the content hash changed
75
- changed = current.paths.include?(path) && !current.contents.find_content(path, target)
79
+ # abort entirely if matching target not found
80
+ raise SyncError, "no target for path '#{path}'" if target.nil?
76
81
 
77
- # skip if unchanged
78
- same = !new && !changed
82
+ # new paths are not in previously applied paths
83
+ new = !current.paths.include?(path)
79
84
 
80
- next if same
85
+ # updated paths are in previously applied paths
86
+ # but the content hash changed
87
+ changed = current.paths.include?(path) && !current.contents.find_content(path, target)
81
88
 
82
- # match content with path and target
83
- content = contents.find_content(path, target)
89
+ # skip if unchanged
90
+ same = !new && !changed
84
91
 
85
- # abort entirely if matching content not found
86
- raise SyncError, "no valid content for target at path '#{path}'" if content.nil?
92
+ next if same
87
93
 
88
- # to be added or updated << config
89
- # TODO: metadata (hash, version, etc...)
90
- transaction.insert(path, target, content) if new
91
- transaction.update(path, target, content) if changed
92
- end
94
+ # match content with path and target
95
+ content = contents.find_content(path, target)
93
96
 
94
- # save backend opaque backend state
95
- transaction.set(opaque_backend_state: targets.opaque_backend_state)
96
- transaction.set(targets_version: targets.version)
97
+ # abort entirely if matching content not found
98
+ raise SyncError, "no valid content for target at path '#{path}'" if content.nil?
97
99
 
98
- # upon transaction end, new list of applied config + metadata (add, change, remove) will be saved
99
- # TODO: also remove stale config (matching removed) from cache (client configs is exhaustive list of paths)
100
+ # to be added or updated << config
101
+ # TODO: metadata (hash, version, etc...)
102
+ transaction.insert(path, target, content) if new
103
+ transaction.update(path, target, content) if changed
100
104
  end
101
105
 
102
- if changes.empty?
103
- Datadog.logger.debug { 'remote: no changes' }
104
- else
105
- dispatcher.dispatch(changes, repository)
106
- end
107
- elsif response.internal_error?
108
- raise TransportError, response.to_s
106
+ # save backend opaque backend state
107
+ transaction.set(opaque_backend_state: targets.opaque_backend_state)
108
+ transaction.set(targets_version: targets.version)
109
+
110
+ # upon transaction end, new list of applied config + metadata (add, change, remove) will be saved
111
+ # TODO: also remove stale config (matching removed) from cache (client configs is exhaustive list of paths)
109
112
  end
110
- end
111
- # rubocop:enable Metrics/AbcSize,Metrics/PerceivedComplexity,Metrics/MethodLength,Metrics/CyclomaticComplexity
112
113
 
113
- private
114
+ if changes.empty?
115
+ Datadog.logger.debug { 'remote: no changes' }
116
+ else
117
+ dispatcher.dispatch(changes, repository)
118
+ end
119
+ end
114
120
 
115
121
  def payload # rubocop:disable Metrics/MethodLength
116
122
  state = repository.state
@@ -14,6 +14,7 @@ module Datadog
14
14
  module Core
15
15
  module Telemetry
16
16
  # Telemetry entrypoint, coordinates sending telemetry events at various points in app lifecycle.
17
+ # Note: Telemetry does not spawn its worker thread in fork processes, thus no telemetry is sent in forked processes.
17
18
  class Component
18
19
  attr_reader :enabled
19
20
 
@@ -52,6 +53,7 @@ module Datadog
52
53
  metrics_aggregation_interval_seconds: settings.telemetry.metrics_aggregation_interval_seconds,
53
54
  dependency_collection: settings.telemetry.dependency_collection,
54
55
  shutdown_timeout_seconds: settings.telemetry.shutdown_timeout_seconds,
56
+ log_collection_enabled: settings.telemetry.log_collection_enabled
55
57
  )
56
58
  end
57
59
 
@@ -67,10 +69,11 @@ module Datadog
67
69
  http_transport:,
68
70
  shutdown_timeout_seconds:,
69
71
  enabled: true,
70
- metrics_enabled: true
72
+ metrics_enabled: true,
73
+ log_collection_enabled: true
71
74
  )
72
75
  @enabled = enabled
73
- @stopped = false
76
+ @log_collection_enabled = log_collection_enabled
74
77
 
75
78
  @metrics_manager = MetricsManager.new(
76
79
  enabled: enabled && metrics_enabled,
@@ -86,6 +89,9 @@ module Datadog
86
89
  dependency_collection: dependency_collection,
87
90
  shutdown_timeout: shutdown_timeout_seconds
88
91
  )
92
+
93
+ @stopped = false
94
+
89
95
  @worker.start
90
96
  end
91
97
 
@@ -114,7 +120,7 @@ module Datadog
114
120
  end
115
121
 
116
122
  def log!(event)
117
- return unless @enabled || forked?
123
+ return if !@enabled || forked? || !@log_collection_enabled
118
124
 
119
125
  @worker.enqueue(event)
120
126
  end
@@ -13,6 +13,7 @@ module Datadog
13
13
  ENV_INSTALL_TYPE = 'DD_INSTRUMENTATION_INSTALL_TYPE'
14
14
  ENV_INSTALL_TIME = 'DD_INSTRUMENTATION_INSTALL_TIME'
15
15
  ENV_AGENTLESS_URL_OVERRIDE = 'DD_TELEMETRY_AGENTLESS_URL'
16
+ ENV_LOG_COLLECTION = 'DD_TELEMETRY_LOG_COLLECTION_ENABLED'
16
17
  end
17
18
  end
18
19
  end
@@ -78,17 +78,18 @@ module Datadog
78
78
  registry_lock.synchronize do
79
79
  registry[path] = tp.instruction_sequence
80
80
  end
81
- end
82
-
83
- DI.component&.probe_manager&.install_pending_line_probes(path)
84
81
 
82
+ # Also, pending line probes should only be installed for
83
+ # non-eval'd code.
84
+ DI.current_component&.probe_manager&.install_pending_line_probes(path)
85
+ end
85
86
  # Since this method normally is called from customer applications,
86
87
  # rescue any exceptions that might not be handled to not break said
87
88
  # customer applications.
88
89
  rescue => exc
89
90
  # TODO we do not have DI.component defined yet, remove steep:ignore
90
91
  # before release.
91
- if component = DI.component # steep:ignore
92
+ if component = DI.current_component # steep:ignore
92
93
  raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
93
94
  component.logger.warn("Unhandled exception in script_compiled trace point: #{exc.class}: #{exc}")
94
95
  component.telemetry&.report(exc, description: "Unhandled exception in script_compiled trace point")
@@ -24,7 +24,9 @@ module Datadog
24
24
 
25
25
  return unless environment_supported?(settings)
26
26
 
27
- new(settings, agent_settings, Datadog.logger, code_tracker: DI.code_tracker, telemetry: telemetry)
27
+ new(settings, agent_settings, Datadog.logger, code_tracker: DI.code_tracker, telemetry: telemetry).tap do |component|
28
+ DI.add_current_component(component)
29
+ end
28
30
  end
29
31
 
30
32
  def build!(settings, agent_settings, telemetry: nil)
@@ -99,6 +101,8 @@ module Datadog
99
101
  # was replaced by a new instance, the new instance of it wouldn't have
100
102
  # any of the already loaded code tracked.
101
103
  def shutdown!(replacement = nil)
104
+ DI.remove_current_component(self)
105
+
102
106
  probe_manager.clear_hooks
103
107
  probe_manager.close
104
108
  probe_notifier_worker.stop
@@ -5,6 +5,7 @@ Datadog::DI::Serializer.register(condition: lambda { |value| ActiveRecord::Base
5
5
  # steep:ignore:start
6
6
  value_to_serialize = {
7
7
  attributes: value.attributes,
8
+ new_record: value.new_record?,
8
9
  }
9
10
  serializer.serialize_value(value_to_serialize, depth: depth ? depth - 1 : nil, type: value.class)
10
11
  # steep:ignore:end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Require 'datadog/di/init' early in the application boot process to
4
+ # enable dynamic instrumentation for third-party libraries used by the
5
+ # application.
6
+
7
+ require_relative '../tracing'
8
+ require_relative '../tracing/contrib'
9
+ require_relative '../di'
10
+
11
+ # Code tracking is required for line probes to work; see the comments
12
+ # on the activate_tracking methods in di.rb for further details.
13
+ #
14
+ # Unlike di.rb which conditionally activates tracking only if the
15
+ # DD_DYNAMIC_INSTRUMENTATION_ENABLED environment variable is set, this file
16
+ # always activates tracking. This is because this file is explicitly loaded
17
+ # by customer applications for the purpose of enabling code tracking
18
+ # early in application boot process (i.e., before datadog library itself
19
+ # is loaded).
20
+ Datadog::DI.activate_tracking