datadog 2.7.0 → 2.8.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -1
  3. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +47 -17
  4. data/ext/datadog_profiling_native_extension/extconf.rb +3 -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 +3 -9
  7. data/ext/datadog_profiling_native_extension/profiling.c +6 -0
  8. data/ext/datadog_profiling_native_extension/ruby_helpers.c +14 -4
  9. data/ext/datadog_profiling_native_extension/ruby_helpers.h +4 -0
  10. data/ext/datadog_profiling_native_extension/stack_recorder.c +0 -34
  11. data/ext/libdatadog_extconf_helpers.rb +1 -1
  12. data/lib/datadog/appsec/component.rb +1 -8
  13. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +73 -0
  14. data/lib/datadog/appsec/contrib/active_record/integration.rb +41 -0
  15. data/lib/datadog/appsec/contrib/active_record/patcher.rb +53 -0
  16. data/lib/datadog/appsec/event.rb +1 -1
  17. data/lib/datadog/appsec/processor/context.rb +2 -2
  18. data/lib/datadog/appsec/remote.rb +1 -3
  19. data/lib/datadog/appsec/response.rb +7 -11
  20. data/lib/datadog/appsec.rb +3 -2
  21. data/lib/datadog/core/configuration/components.rb +17 -1
  22. data/lib/datadog/core/configuration/settings.rb +10 -0
  23. data/lib/datadog/core/configuration.rb +9 -1
  24. data/lib/datadog/core/remote/client/capabilities.rb +6 -0
  25. data/lib/datadog/core/remote/client.rb +65 -59
  26. data/lib/datadog/core/telemetry/component.rb +9 -3
  27. data/lib/datadog/core/telemetry/ext.rb +1 -0
  28. data/lib/datadog/di/code_tracker.rb +5 -4
  29. data/lib/datadog/di/component.rb +5 -1
  30. data/lib/datadog/di/contrib/active_record.rb +1 -0
  31. data/lib/datadog/di/init.rb +20 -0
  32. data/lib/datadog/di/instrumenter.rb +81 -11
  33. data/lib/datadog/di/probe.rb +11 -1
  34. data/lib/datadog/di/probe_builder.rb +1 -0
  35. data/lib/datadog/di/probe_manager.rb +4 -1
  36. data/lib/datadog/di/probe_notification_builder.rb +13 -7
  37. data/lib/datadog/di/remote.rb +124 -0
  38. data/lib/datadog/di/serializer.rb +14 -7
  39. data/lib/datadog/di/transport.rb +2 -1
  40. data/lib/datadog/di/utils.rb +7 -0
  41. data/lib/datadog/di.rb +84 -20
  42. data/lib/datadog/profiling/component.rb +4 -16
  43. data/lib/datadog/tracing/configuration/settings.rb +4 -8
  44. data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +16 -4
  45. data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +4 -0
  46. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
  47. data/lib/datadog/tracing/tracer.rb +1 -1
  48. data/lib/datadog/version.rb +1 -1
  49. data/lib/datadog.rb +3 -0
  50. metadata +17 -13
  51. data/lib/datadog/appsec/processor/actions.rb +0 -49
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ module Contrib
6
+ module ActiveRecord
7
+ # AppSec module that will be prepended to ActiveRecord adapter
8
+ module Instrumentation
9
+ module_function
10
+
11
+ def detect_sql_injection(sql, adapter_name)
12
+ scope = AppSec.active_scope
13
+ return unless scope
14
+
15
+ # libddwaf expects db system to be lowercase,
16
+ # in case of sqlite adapter, libddwaf expects 'sqlite' as db system
17
+ db_system = adapter_name.downcase
18
+ db_system = 'sqlite' if db_system == 'sqlite3'
19
+
20
+ ephemeral_data = {
21
+ 'server.db.statement' => sql,
22
+ 'server.db.system' => db_system
23
+ }
24
+
25
+ waf_timeout = Datadog.configuration.appsec.waf_timeout
26
+ result = scope.processor_context.run({}, ephemeral_data, waf_timeout)
27
+
28
+ if result.status == :match
29
+ Datadog::AppSec::Event.tag_and_keep!(scope, result)
30
+
31
+ event = {
32
+ waf_result: result,
33
+ trace: scope.trace,
34
+ span: scope.service_entry_span,
35
+ sql: sql,
36
+ actions: result.actions
37
+ }
38
+ scope.processor_context.events << event
39
+ end
40
+ end
41
+
42
+ # patch for all adapters in ActiveRecord >= 7.1
43
+ module InternalExecQueryAdapterPatch
44
+ def internal_exec_query(sql, *args, **rest)
45
+ Instrumentation.detect_sql_injection(sql, adapter_name)
46
+
47
+ super
48
+ end
49
+ end
50
+
51
+ # patch for postgres adapter in ActiveRecord < 7.1
52
+ module ExecuteAndClearAdapterPatch
53
+ def execute_and_clear(sql, *args, **rest)
54
+ Instrumentation.detect_sql_injection(sql, adapter_name)
55
+
56
+ super
57
+ end
58
+ end
59
+
60
+ # patch for mysql2 and sqlite3 adapters in ActiveRecord < 7.1
61
+ # this patch is also used when using JDBC adapter
62
+ module ExecQueryAdapterPatch
63
+ def exec_query(sql, *args, **rest)
64
+ Instrumentation.detect_sql_injection(sql, adapter_name)
65
+
66
+ super
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../integration'
4
+ require_relative 'patcher'
5
+
6
+ module Datadog
7
+ module AppSec
8
+ module Contrib
9
+ module ActiveRecord
10
+ # This class provides helper methods that are used when patching ActiveRecord
11
+ class Integration
12
+ include Datadog::AppSec::Contrib::Integration
13
+
14
+ MINIMUM_VERSION = Gem::Version.new('4')
15
+
16
+ register_as :active_record, auto_patch: false
17
+
18
+ def self.version
19
+ Gem.loaded_specs['activerecord'] && Gem.loaded_specs['activerecord'].version
20
+ end
21
+
22
+ def self.loaded?
23
+ !defined?(::ActiveRecord).nil?
24
+ end
25
+
26
+ def self.compatible?
27
+ super && version >= MINIMUM_VERSION
28
+ end
29
+
30
+ def self.auto_instrument?
31
+ true
32
+ end
33
+
34
+ def patcher
35
+ Patcher
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -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")