datadog 2.9.0 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
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