datadog 2.9.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +2 -2
  4. data/ext/datadog_profiling_native_extension/collectors_stack.h +2 -2
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +2 -5
  6. data/ext/datadog_profiling_native_extension/heap_recorder.c +50 -92
  7. data/ext/datadog_profiling_native_extension/heap_recorder.h +1 -1
  8. data/ext/datadog_profiling_native_extension/stack_recorder.c +9 -22
  9. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -1
  10. data/lib/datadog/appsec/actions_handler.rb +27 -0
  11. data/lib/datadog/appsec/component.rb +14 -8
  12. data/lib/datadog/appsec/configuration/settings.rb +9 -0
  13. data/lib/datadog/appsec/context.rb +28 -8
  14. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +6 -2
  15. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +1 -7
  16. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +4 -5
  17. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +1 -1
  18. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +15 -12
  19. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +1 -1
  20. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +1 -1
  21. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +1 -1
  22. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +3 -3
  23. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +11 -22
  24. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +5 -4
  25. data/lib/datadog/appsec/contrib/rails/patcher.rb +3 -13
  26. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +1 -1
  27. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +10 -8
  28. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +3 -26
  29. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +1 -1
  30. data/lib/datadog/appsec/ext.rb +6 -1
  31. data/lib/datadog/appsec/metrics/collector.rb +38 -0
  32. data/lib/datadog/appsec/metrics/exporter.rb +35 -0
  33. data/lib/datadog/appsec/metrics/telemetry.rb +23 -0
  34. data/lib/datadog/appsec/metrics.rb +13 -0
  35. data/lib/datadog/appsec/monitor/gateway/watcher.rb +5 -4
  36. data/lib/datadog/appsec/monitor/reactive/set_user.rb +1 -1
  37. data/lib/datadog/appsec/processor.rb +4 -3
  38. data/lib/datadog/appsec/response.rb +18 -80
  39. data/lib/datadog/appsec/security_engine/result.rb +67 -0
  40. data/lib/datadog/appsec/security_engine/runner.rb +88 -0
  41. data/lib/datadog/appsec/security_engine.rb +9 -0
  42. data/lib/datadog/appsec.rb +14 -5
  43. data/lib/datadog/di/component.rb +2 -0
  44. data/lib/datadog/di/probe_notification_builder.rb +6 -0
  45. data/lib/datadog/di/redactor.rb +0 -1
  46. data/lib/datadog/di/remote.rb +26 -5
  47. data/lib/datadog/tracing/contrib/aws/integration.rb +1 -1
  48. data/lib/datadog/tracing/contrib/extensions.rb +15 -3
  49. data/lib/datadog/tracing/contrib/http/integration.rb +3 -0
  50. data/lib/datadog/version.rb +1 -1
  51. metadata +32 -18
  52. data/lib/datadog/appsec/contrib/sinatra/ext.rb +0 -14
  53. data/lib/datadog/appsec/processor/context.rb +0 -107
@@ -24,7 +24,7 @@ module Datadog
24
24
  engine = AppSec::Reactive::Engine.new
25
25
 
26
26
  Monitor::Reactive::SetUser.subscribe(engine, context) do |result|
27
- if result.status == :match
27
+ if result.match?
28
28
  # TODO: should this hash be an Event instance instead?
29
29
  event = {
30
30
  waf_result: result,
@@ -37,12 +37,13 @@ module Datadog
37
37
  # We want to keep the trace in case of security event
38
38
  context.trace.keep! if context.trace
39
39
  Datadog::AppSec::Event.tag_and_keep!(context, result)
40
- context.waf_runner.events << event
40
+ context.events << event
41
+
42
+ Datadog::AppSec::ActionsHandler.handle(result.actions)
41
43
  end
42
44
  end
43
45
 
44
- block = Monitor::Reactive::SetUser.publish(engine, user)
45
- throw(Datadog::AppSec::Ext::INTERRUPT, event[:actions]) if block
46
+ Monitor::Reactive::SetUser.publish(engine, user)
46
47
 
47
48
  stack.call(user)
48
49
  end
@@ -32,7 +32,7 @@ module Datadog
32
32
  waf_timeout = Datadog.configuration.appsec.waf_timeout
33
33
  result = context.run_waf(persistent_data, {}, waf_timeout)
34
34
 
35
- next if result.status != :match
35
+ next unless result.match?
36
36
 
37
37
  yield result
38
38
  throw(:block, true) unless result.actions.empty?
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'processor/context'
3
+ require_relative 'security_engine/runner'
4
4
 
5
5
  module Datadog
6
6
  module AppSec
7
7
  # Processor integrates libddwaf into datadog/appsec
8
+ # NOTE: This class will be moved under AppSec::SecurityEngine namespace
8
9
  class Processor
9
10
  attr_reader :diagnostics, :addresses
10
11
 
@@ -29,8 +30,8 @@ module Datadog
29
30
  @handle.finalize
30
31
  end
31
32
 
32
- def new_context
33
- Context.new(@handle, telemetry: @telemetry)
33
+ def new_runner
34
+ SecurityEngine::Runner.new(@handle, telemetry: @telemetry)
34
35
  end
35
36
 
36
37
  private
@@ -19,100 +19,38 @@ module Datadog
19
19
  [status, headers, body]
20
20
  end
21
21
 
22
- def to_sinatra_response
23
- ::Sinatra::Response.new(body, status, headers)
24
- end
25
-
26
- def to_action_dispatch_response
27
- ::ActionDispatch::Response.new(status, headers, body)
28
- end
29
-
30
22
  class << self
31
- def negotiate(env, actions)
32
- # @type var configured_response: Response?
33
- configured_response = nil
34
- actions.each do |type, parameters|
35
- # Need to use next to make steep happy :(
36
- # I rather use break to stop the execution
37
- next if configured_response
38
-
39
- configured_response = case type
40
- when 'block_request'
41
- block_response(env, parameters)
42
- when 'redirect_request'
43
- redirect_response(env, parameters)
44
- end
45
- end
46
-
47
- configured_response || default_response(env)
48
- end
49
-
50
- def graphql_response(gateway_multiplex)
51
- multiplex_return = []
52
- gateway_multiplex.queries.each do |query|
53
- # This method is only called in places where GraphQL-Ruby is already required
54
- query_result = ::GraphQL::Query::Result.new(
55
- query: query,
56
- values: JSON.parse(content('application/json'))
57
- )
58
- multiplex_return << query_result
59
- end
23
+ def from_interrupt_params(interrupt_params, http_accept_header)
24
+ return redirect_response(interrupt_params) if interrupt_params['location']
60
25
 
61
- multiplex_return
26
+ block_response(interrupt_params, http_accept_header)
62
27
  end
63
28
 
64
29
  private
65
30
 
66
- def default_response(env)
67
- content_type = content_type(env)
68
-
69
- body = []
70
- body << content(content_type)
31
+ def block_response(interrupt_params, http_accept_header)
32
+ content_type = case interrupt_params['type']
33
+ when nil, 'auto' then content_type(http_accept_header)
34
+ else FORMAT_TO_CONTENT_TYPE.fetch(interrupt_params['type'], DEFAULT_CONTENT_TYPE)
35
+ end
71
36
 
72
37
  Response.new(
73
- status: 403,
38
+ status: interrupt_params['status_code']&.to_i || 403,
74
39
  headers: { 'Content-Type' => content_type },
75
- body: body,
40
+ body: [content(content_type)],
76
41
  )
77
42
  end
78
43
 
79
- def block_response(env, options)
80
- content_type = if options['type'] == 'auto'
81
- content_type(env)
82
- else
83
- FORMAT_TO_CONTENT_TYPE[options['type']]
84
- end
85
-
86
- body = []
87
- body << content(content_type)
44
+ def redirect_response(interrupt_params)
45
+ status_code = interrupt_params['status_code'].to_i
88
46
 
89
47
  Response.new(
90
- status: options['status_code']&.to_i || 403,
91
- headers: { 'Content-Type' => content_type },
92
- body: body,
48
+ status: (status_code >= 300 && status_code < 400 ? status_code : 303),
49
+ headers: { 'Location' => interrupt_params.fetch('location') },
50
+ body: [],
93
51
  )
94
52
  end
95
53
 
96
- def redirect_response(env, options)
97
- if options['location'] && !options['location'].empty?
98
- content_type = content_type(env)
99
-
100
- headers = {
101
- 'Content-Type' => content_type,
102
- 'Location' => options['location']
103
- }
104
-
105
- status_code = options['status_code'].to_i
106
- Response.new(
107
- status: (status_code >= 300 && status_code < 400 ? status_code : 303),
108
- headers: headers,
109
- body: [],
110
- )
111
- else
112
- default_response(env)
113
- end
114
- end
115
-
116
54
  CONTENT_TYPE_TO_FORMAT = {
117
55
  'application/json' => :json,
118
56
  'text/html' => :html,
@@ -126,10 +64,10 @@ module Datadog
126
64
 
127
65
  DEFAULT_CONTENT_TYPE = 'application/json'
128
66
 
129
- def content_type(env)
130
- return DEFAULT_CONTENT_TYPE unless env.key?('HTTP_ACCEPT')
67
+ def content_type(http_accept_header)
68
+ return DEFAULT_CONTENT_TYPE if http_accept_header.nil?
131
69
 
132
- accept_types = env['HTTP_ACCEPT'].split(',').map(&:strip)
70
+ accept_types = http_accept_header.split(',').map(&:strip)
133
71
 
134
72
  accepted = accept_types.map { |m| Utils::HTTP::MediaRange.new(m) }.sort!.reverse!
135
73
 
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ module SecurityEngine
6
+ # A namespace for value-objects representing the result of WAF check.
7
+ module Result
8
+ # A generic result without indication of its type.
9
+ class Base
10
+ attr_reader :events, :actions, :derivatives, :duration_ns, :duration_ext_ns
11
+
12
+ def initialize(events:, actions:, derivatives:, timeout:, duration_ns:, duration_ext_ns:)
13
+ @events = events
14
+ @actions = actions
15
+ @derivatives = derivatives
16
+
17
+ @timeout = timeout
18
+ @duration_ns = duration_ns
19
+ @duration_ext_ns = duration_ext_ns
20
+ end
21
+
22
+ def timeout?
23
+ !!@timeout
24
+ end
25
+
26
+ def match?
27
+ raise NotImplementedError
28
+ end
29
+ end
30
+
31
+ # A result that indicates a security rule match
32
+ class Match < Base
33
+ def match?
34
+ true
35
+ end
36
+ end
37
+
38
+ # A result that indicates a successful security rules check without a match
39
+ class Ok < Base
40
+ def match?
41
+ false
42
+ end
43
+ end
44
+
45
+ # A result that indicates an internal security library error
46
+ class Error
47
+ attr_reader :events, :actions, :derivatives, :duration_ns, :duration_ext_ns
48
+
49
+ def initialize(duration_ext_ns:)
50
+ @events = []
51
+ @actions = @derivatives = {}
52
+ @duration_ns = 0
53
+ @duration_ext_ns = duration_ext_ns
54
+ end
55
+
56
+ def timeout?
57
+ false
58
+ end
59
+
60
+ def match?
61
+ false
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'result'
4
+
5
+ module Datadog
6
+ module AppSec
7
+ module SecurityEngine
8
+ # A class that check input via security engine (WAF) and respond with result.
9
+ class Runner
10
+ SUCCESSFUL_EXECUTION_CODES = [:ok, :match].freeze
11
+
12
+ def initialize(handle, telemetry:)
13
+ @mutex = Mutex.new
14
+ @context = WAF::Context.new(handle)
15
+ @telemetry = telemetry
16
+
17
+ @debug_tag = "libddwaf:#{WAF::VERSION::STRING} method:ddwaf_run"
18
+ end
19
+
20
+ def run(persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
21
+ @mutex.lock
22
+
23
+ start_ns = Core::Utils::Time.get_time(:nanosecond)
24
+ persistent_data.reject! do |_, v|
25
+ next false if v.is_a?(TrueClass) || v.is_a?(FalseClass)
26
+
27
+ v.nil? ? true : v.empty?
28
+ end
29
+
30
+ ephemeral_data.reject! do |_, v|
31
+ next false if v.is_a?(TrueClass) || v.is_a?(FalseClass)
32
+
33
+ v.nil? ? true : v.empty?
34
+ end
35
+
36
+ _code, result = try_run(persistent_data, ephemeral_data, timeout)
37
+ stop_ns = Core::Utils::Time.get_time(:nanosecond)
38
+
39
+ report_execution(result)
40
+
41
+ unless SUCCESSFUL_EXECUTION_CODES.include?(result.status)
42
+ return Result::Error.new(duration_ext_ns: stop_ns - start_ns)
43
+ end
44
+
45
+ klass = result.status == :match ? Result::Match : Result::Ok
46
+ klass.new(
47
+ events: result.events,
48
+ actions: result.actions,
49
+ derivatives: result.derivatives,
50
+ timeout: result.timeout,
51
+ duration_ns: result.total_runtime,
52
+ duration_ext_ns: (stop_ns - start_ns)
53
+ )
54
+ ensure
55
+ @mutex.unlock
56
+ end
57
+
58
+ def finalize
59
+ @context.finalize
60
+ end
61
+
62
+ private
63
+
64
+ def try_run(persistent_data, ephemeral_data, timeout)
65
+ @context.run(persistent_data, ephemeral_data, timeout)
66
+ rescue WAF::LibDDWAF::Error => e
67
+ Datadog.logger.debug { "#{@debug_tag} execution error: #{e} backtrace: #{e.backtrace&.first(3)}" }
68
+ @telemetry.report(e, description: 'libddwaf-rb internal low-level error')
69
+
70
+ [:err_internal, WAF::Result.new(:err_internal, [], 0, false, [], [])]
71
+ end
72
+
73
+ def report_execution(result)
74
+ Datadog.logger.debug { "#{@debug_tag} execution timed out: #{result.inspect}" } if result.timeout
75
+
76
+ if SUCCESSFUL_EXECUTION_CODES.include?(result.status)
77
+ Datadog.logger.debug { "#{@debug_tag} execution result: #{result.inspect}" }
78
+ else
79
+ message = "#{@debug_tag} execution error: #{result.status.inspect}"
80
+
81
+ Datadog.logger.debug { message }
82
+ @telemetry.error(message)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ # A namespace for secutiry library we use to detect and prevent threats.
6
+ module SecurityEngine
7
+ end
8
+ end
9
+ end
@@ -14,19 +14,24 @@ module Datadog
14
14
  Datadog.configuration.appsec.enabled
15
15
  end
16
16
 
17
+ def rasp_enabled?
18
+ Datadog.configuration.appsec.rasp_enabled
19
+ end
20
+
17
21
  def active_context
18
22
  Datadog::AppSec::Context.active
19
23
  end
20
24
 
21
- def processor
22
- appsec_component = components.appsec
25
+ def telemetry
26
+ components.appsec&.telemetry
27
+ end
23
28
 
24
- appsec_component.processor if appsec_component
29
+ def processor
30
+ components.appsec&.processor
25
31
  end
26
32
 
27
33
  def reconfigure(ruleset:, telemetry:)
28
34
  appsec_component = components.appsec
29
-
30
35
  return unless appsec_component
31
36
 
32
37
  appsec_component.reconfigure(ruleset: ruleset, telemetry: telemetry)
@@ -34,12 +39,16 @@ module Datadog
34
39
 
35
40
  def reconfigure_lock(&block)
36
41
  appsec_component = components.appsec
37
-
38
42
  return unless appsec_component
39
43
 
40
44
  appsec_component.reconfigure_lock(&block)
41
45
  end
42
46
 
47
+ def api_security_enabled?
48
+ Datadog.configuration.appsec.api_security.enabled &&
49
+ Datadog.configuration.appsec.api_security.sample_rate.sample?
50
+ end
51
+
43
52
  private
44
53
 
45
54
  def components
@@ -76,6 +76,7 @@ module Datadog
76
76
  @agent_settings = agent_settings
77
77
  @logger = logger
78
78
  @telemetry = telemetry
79
+ @code_tracker = code_tracker
79
80
  @redactor = Redactor.new(settings)
80
81
  @serializer = Serializer.new(settings, redactor, telemetry: telemetry)
81
82
  @instrumenter = Instrumenter.new(settings, serializer, logger, code_tracker: code_tracker, telemetry: telemetry)
@@ -90,6 +91,7 @@ module Datadog
90
91
  attr_reader :agent_settings
91
92
  attr_reader :logger
92
93
  attr_reader :telemetry
94
+ attr_reader :code_tracker
93
95
  attr_reader :instrumenter
94
96
  attr_reader :transport
95
97
  attr_reader :probe_notifier_worker
@@ -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,
@@ -119,7 +119,6 @@ module Datadog
119
119
  "dburl",
120
120
  "encryptionkey",
121
121
  "encryptionkeyid",
122
- "env",
123
122
  "geolocation",
124
123
  "gpgkey",
125
124
  "ipaddress",
@@ -49,28 +49,48 @@ module Datadog
49
49
  begin
50
50
  probe_spec = parse_content(content)
51
51
  probe = ProbeBuilder.build_from_remote_config(probe_spec)
52
- payload = component.probe_notification_builder.build_received(probe)
53
- component.probe_notifier_worker.add_status(payload)
52
+ probe_notification_builder = component.probe_notification_builder
53
+ payload = probe_notification_builder.build_received(probe)
54
+ probe_notifier_worker = component.probe_notifier_worker
55
+ probe_notifier_worker.add_status(payload)
54
56
  component.logger.debug { "di: received probe from RC: #{probe.type} #{probe.location}" }
55
57
 
56
58
  begin
57
59
  # TODO test exception capture
58
60
  probe_manager.add_probe(probe)
59
61
  content.applied
62
+ rescue DI::Error::DITargetNotInRegistry => exc
63
+ component.telemetry&.report(exc, description: "Line probe is targeting a loaded file that is not in code tracker")
64
+
65
+ payload = probe_notification_builder.build_errored(probe, exc)
66
+ probe_notifier_worker.add_status(payload)
67
+
68
+ # If a probe fails to install, we will mark the content
69
+ # as errored. On subsequent remote configuration application
70
+ # attemps, probe manager will raise the "previously errored"
71
+ # exception and we'll rescue it here, again marking the
72
+ # content as errored but with a somewhat different exception
73
+ # message.
74
+ # TODO assert content state (errored for this example)
75
+ content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}")
60
76
  rescue => exc
61
77
  raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
62
78
 
63
79
  component.logger.debug { "di: unhandled exception adding probe in DI remote receiver: #{exc.class}: #{exc}" }
64
80
  component.telemetry&.report(exc, description: "Unhandled exception adding probe in DI remote receiver")
65
81
 
82
+ # TODO test this path
83
+ payload = probe_notification_builder.build_errored(probe, exc)
84
+ probe_notifier_worker.add_status(payload)
85
+
66
86
  # If a probe fails to install, we will mark the content
67
87
  # as errored. On subsequent remote configuration application
68
88
  # attemps, probe manager will raise the "previously errored"
69
89
  # exception and we'll rescue it here, again marking the
70
90
  # content as errored but with a somewhat different exception
71
91
  # message.
72
- # TODO stack trace must be redacted or not sent at all
73
- content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}: #{Array(exc.backtrace).join("\n")}")
92
+ # TODO assert content state (errored for this example)
93
+ content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}")
74
94
  end
75
95
 
76
96
  # Important: even if processing fails for this probe config,
@@ -84,7 +104,8 @@ module Datadog
84
104
  component.logger.debug { "di: unhandled exception handling probe in DI remote receiver: #{exc.class}: #{exc}" }
85
105
  component.telemetry&.report(exc, description: "Unhandled exception handling probe in DI remote receiver")
86
106
 
87
- content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}: #{Array(exc.backtrace).join("\n")}")
107
+ # TODO assert content state (errored for this example)
108
+ content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}")
88
109
  end
89
110
  end
90
111
  end
@@ -17,7 +17,7 @@ module Datadog
17
17
  # @public_api Changing the integration name or integration options can cause breaking changes
18
18
  register_as :aws, auto_patch: true
19
19
  def self.gem_name
20
- 'aws-sdk-core'
20
+ 'aws-sdk'
21
21
  end
22
22
 
23
23
  def self.version
@@ -110,6 +110,13 @@ module Datadog
110
110
  module Settings
111
111
  InvalidIntegrationError = Class.new(StandardError)
112
112
 
113
+ # Used to avoid concurrency issues between registering integrations (e.g. mutation) and reporting the
114
+ # current integrations for logging/debugging/telemetry purposes (e.g. iteration) in the
115
+ # `@instrumented_integrations` hash.
116
+ #
117
+ # See https://github.com/DataDog/dd-trace-rb/issues/2851 for details on the original issue.
118
+ INSTRUMENTED_INTEGRATIONS_LOCK = Mutex.new
119
+
113
120
  def self.included(base)
114
121
  base.class_eval do
115
122
  settings :contrib do
@@ -161,7 +168,10 @@ module Datadog
161
168
  configuration_name = options[:describes] || :default
162
169
  filtered_options = options.reject { |k, _v| k == :describes }
163
170
  integration.configure(configuration_name, filtered_options, &block)
164
- instrumented_integrations[integration_name] = integration
171
+ INSTRUMENTED_INTEGRATIONS_LOCK.synchronize do
172
+ @instrumented_integrations ||= {}
173
+ @instrumented_integrations[integration_name] = integration
174
+ end
165
175
 
166
176
  # Add to activation list
167
177
  integrations_pending_activation << integration
@@ -192,14 +202,16 @@ module Datadog
192
202
  @integrations_pending_activation ||= Set.new
193
203
  end
194
204
 
205
+ # This method is only for logging/debugging/telemetry purposes (e.g. iteration) in the
206
+ # `@instrumented_integrations` hash.
195
207
  # @!visibility private
196
208
  def instrumented_integrations
197
- @instrumented_integrations ||= {}
209
+ INSTRUMENTED_INTEGRATIONS_LOCK.synchronize { (@instrumented_integrations&.dup || {}).freeze }
198
210
  end
199
211
 
200
212
  # @!visibility private
201
213
  def reset!
202
- instrumented_integrations.clear
214
+ INSTRUMENTED_INTEGRATIONS_LOCK.synchronize { @instrumented_integrations&.clear }
203
215
  super
204
216
  end
205
217
 
@@ -22,6 +22,9 @@ module Datadog
22
22
 
23
23
  # @public_api Changing the integration name or integration options can cause breaking changes
24
24
  register_as :http, auto_patch: true
25
+ def self.gem_name
26
+ 'net-http'
27
+ end
25
28
 
26
29
  def self.version
27
30
  Gem::Version.new(RUBY_VERSION)
@@ -3,7 +3,7 @@
3
3
  module Datadog
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 9
6
+ MINOR = 10
7
7
  PATCH = 0
8
8
  PRE = nil
9
9
  BUILD = nil