contrast-agent 4.3.2 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent.rb +5 -1
  3. data/lib/contrast/agent/assess.rb +0 -9
  4. data/lib/contrast/agent/assess/contrast_event.rb +0 -2
  5. data/lib/contrast/agent/assess/contrast_object.rb +5 -2
  6. data/lib/contrast/agent/assess/finalizers/hash.rb +7 -0
  7. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +17 -3
  8. data/lib/contrast/agent/assess/policy/propagation_method.rb +28 -13
  9. data/lib/contrast/agent/assess/policy/propagator/append.rb +28 -13
  10. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +21 -16
  11. data/lib/contrast/agent/assess/policy/propagator/splat.rb +23 -13
  12. data/lib/contrast/agent/assess/policy/propagator/split.rb +14 -7
  13. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +30 -14
  14. data/lib/contrast/agent/assess/policy/trigger_method.rb +13 -8
  15. data/lib/contrast/agent/assess/policy/trigger_node.rb +28 -7
  16. data/lib/contrast/agent/assess/policy/trigger_validation/redos_validator.rb +59 -0
  17. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +1 -2
  18. data/lib/contrast/agent/assess/policy/trigger_validation/trigger_validation.rb +6 -4
  19. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +2 -4
  20. data/lib/contrast/agent/assess/properties.rb +0 -2
  21. data/lib/contrast/agent/assess/property/tagged.rb +37 -19
  22. data/lib/contrast/agent/assess/tracker.rb +1 -1
  23. data/lib/contrast/agent/middleware.rb +85 -55
  24. data/lib/contrast/agent/patching/policy/patch_status.rb +1 -1
  25. data/lib/contrast/agent/patching/policy/patcher.rb +51 -44
  26. data/lib/contrast/agent/patching/policy/trigger_node.rb +5 -2
  27. data/lib/contrast/agent/protect/rule/sqli.rb +17 -11
  28. data/lib/contrast/agent/request_context.rb +12 -0
  29. data/lib/contrast/agent/thread.rb +1 -1
  30. data/lib/contrast/agent/thread_watcher.rb +20 -5
  31. data/lib/contrast/agent/version.rb +1 -1
  32. data/lib/contrast/api/communication/messaging_queue.rb +18 -21
  33. data/lib/contrast/api/communication/response_processor.rb +8 -1
  34. data/lib/contrast/api/communication/socket_client.rb +22 -14
  35. data/lib/contrast/api/decorators.rb +2 -0
  36. data/lib/contrast/api/decorators/agent_startup.rb +58 -0
  37. data/lib/contrast/api/decorators/application_startup.rb +51 -0
  38. data/lib/contrast/api/decorators/route_coverage.rb +15 -5
  39. data/lib/contrast/api/decorators/trace_event.rb +42 -14
  40. data/lib/contrast/components/agent.rb +2 -0
  41. data/lib/contrast/components/app_context.rb +4 -22
  42. data/lib/contrast/components/sampling.rb +48 -6
  43. data/lib/contrast/components/settings.rb +5 -4
  44. data/lib/contrast/framework/manager.rb +13 -12
  45. data/lib/contrast/framework/rails/support.rb +42 -43
  46. data/lib/contrast/framework/sinatra/support.rb +100 -41
  47. data/lib/contrast/logger/log.rb +31 -15
  48. data/lib/contrast/utils/class_util.rb +3 -1
  49. data/lib/contrast/utils/heap_dump_util.rb +103 -87
  50. data/lib/contrast/utils/invalid_configuration_util.rb +21 -12
  51. data/resources/assess/policy.json +3 -9
  52. data/resources/deadzone/policy.json +6 -0
  53. data/ruby-agent.gemspec +54 -16
  54. metadata +105 -136
  55. data/lib/contrast/agent/assess/rule.rb +0 -18
  56. data/lib/contrast/agent/assess/rule/base.rb +0 -52
  57. data/lib/contrast/agent/assess/rule/redos.rb +0 -67
  58. data/lib/contrast/framework/sinatra/patch/base.rb +0 -83
  59. data/lib/contrast/framework/sinatra/patch/support.rb +0 -27
  60. data/lib/contrast/utils/prevent_serialization.rb +0 -52
@@ -13,7 +13,7 @@ module Contrast
13
13
  # one does not exist.
14
14
  #
15
15
  # @param mod [Module] the Module for which the status is asked
16
- # @return [Contrast::Agent::Patching::Policy::PolicyStatus]
16
+ # @return [Contrast::Agent::Patching::Policy::PatchStatus]
17
17
  def get_status mod
18
18
  if mod.cs__const_defined?(status_key, false)
19
19
  mod.cs__const_get(status_key, false)
@@ -52,7 +52,7 @@ module Contrast
52
52
  # startup to catchup on everything we didn't see get loaded
53
53
  def patch
54
54
  catchup_after_load_patches
55
- patch_methods
55
+ catchup_loaded_methods
56
56
  Contrast::Agent::Assess::Policy::RewriterPatch.rewrite_interpolations
57
57
  end
58
58
 
@@ -62,10 +62,11 @@ module Contrast
62
62
  # where only a subset of the Assess changes are needed.
63
63
  PATCH_MONITOR = Monitor.new
64
64
 
65
- def patch_methods
65
+ # Iterate over and patch those Modules and Methods which were loaded before the Agent was enabled.
66
+ def catchup_loaded_methods
66
67
  PATCH_MONITOR.synchronize do
67
68
  t = Contrast::Agent::Thread.new do
68
- synchronized_patch_methods
69
+ synchronized_catchup_loaded_methods
69
70
  end
70
71
  # aborting on exception makes exceptions propagate.
71
72
  t.abort_on_exception = true
@@ -100,7 +101,7 @@ module Contrast
100
101
  # @param mod [Module] the module in which the patch should be
101
102
  # placed.
102
103
  # @param methods [Array(Symbol)] all the instance or singleton
103
- # methods in this clazz.
104
+ # methods in this mod.
104
105
  # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy]
105
106
  # the policy that applies to the given method_name.
106
107
  # @return [Boolean] if patched, either by this invocation or a
@@ -149,7 +150,7 @@ module Contrast
149
150
  # other functions, like rewriting or scanning. This method should
150
151
  # only be invoked by the patch_methods method above in order to
151
152
  # ensure it is wrapped in a synchronize call
152
- def synchronized_patch_methods
153
+ def synchronized_catchup_loaded_methods
153
154
  logger.trace_with_time('Running patching') do
154
155
  patched = []
155
156
  all_module_names.each do |patchable_name|
@@ -166,53 +167,37 @@ module Contrast
166
167
  end
167
168
  end
168
169
 
169
- # Given the patchers that apply to this class that may apply, patch
170
- # Contrast method calls into the methods for which we have rules.
170
+ # Given the patchers that apply to this class that may apply, patch Contrast method calls into the methods
171
+ # for which we have rules.
171
172
  #
172
- # @param module_data [Contrast::Agent::ModuleData] the module, and
173
- # its name, that's being patched into
174
- # @param redo_patch [Boolean] a trigger to force patching
175
- # regardless of the state of the
176
- # Contrast::Agent::Patching::Policy::PatchStatus status on the
177
- # Module
173
+ # @param module_data [Contrast::Agent::ModuleData] the module, and its name, that's being patched into
174
+ # @param redo_patch [Boolean] a trigger to force patching regardless of the state of the
175
+ # Contrast::Agent::Patching::Policy::PatchStatus status on the Module
178
176
  def patch_into_module module_data, redo_patch = false
179
177
  status = status_type.get_status(module_data.mod)
180
178
  return if (status&.patched? || status&.patching?) && !redo_patch
181
179
 
182
- # Begin patching our sources into the given clazz (or module)
183
- # Any patcher that has the name of the clazz will be evaluated for
184
- # patching.
185
- # Find all the patchers that apply to this class, sorted by type.
180
+ # Begin patching our sources into the given module. Any patcher that has the name of the module will be
181
+ # evaluated for patching. Find all the patchers that apply to this class, sorted by type.
186
182
  module_policy = Contrast::Agent::Patching::Policy::ModulePolicy.create_module_policy(module_data.name)
187
-
188
- clazz = module_data.mod
183
+ # If there's nothing to match, then set that status and exit
184
+ if module_policy.empty?
185
+ status.no_patch!
186
+ return
187
+ end
189
188
 
190
189
  status.patching!
191
- patched = false
192
-
193
- counts = 0
194
- # Monkey patch any methods in this class that have matching nodes in the policy
195
- unless module_policy.empty?
196
- instance_methods = all_instance_methods(clazz, true)
197
- singleton_methods = clazz.singleton_methods(false)
198
- counts += patch_into_methods(clazz, instance_methods, module_policy, true)
199
- counts += patch_into_methods(clazz, singleton_methods, module_policy, false)
200
- counts = module_policy.num_expected_patches if adjust_for_prepend(clazz)
201
- patched = true
202
- end
190
+ num_applied_patches = patch_into_instance_methods(module_data, module_policy)
191
+ num_applied_patches += patch_into_singleton_methods(module_data, module_policy)
192
+ if adjust_for_prepend(module_data) ||
193
+ module_policy.num_expected_patches == num_applied_patches
203
194
 
204
- if patched
205
- if module_policy.num_expected_patches == counts
206
- status.patched!
207
- else
208
- status.partial_patch!
209
- end
195
+ status.patched!
210
196
  else
211
- status.no_patch!
197
+ status.partial_patch!
212
198
  end
213
199
  rescue StandardError => e
214
- status ||= status_type.get_status(module_data.mod)
215
- status.failed_patch!
200
+ status&.failed_patch!
216
201
  logger.warn('Patching failed', e, module: module_data.name)
217
202
  ensure
218
203
  logger.trace('Patching complete',
@@ -249,6 +234,29 @@ module Contrast
249
234
  instance_methods
250
235
  end
251
236
 
237
+ # Patch into the Instance Methods, including private, of the given Module that match the ModulePolicy
238
+ # provided.
239
+ #
240
+ # @param module_data [Contrast::Agent::ModuleData] the module, and its name, that's being patched into
241
+ # @param module_policy [Contrast::Agent::Patching::Policy::ModulePolicy] All the patchers that apply to
242
+ # this module, sorted by type.
243
+ def patch_into_instance_methods module_data, module_policy
244
+ mod = module_data.mod
245
+ methods = all_instance_methods(mod, true)
246
+ patch_into_methods(mod, methods, module_policy, true)
247
+ end
248
+
249
+ # Patch into the Singleton Methods of the given Module that match the ModulePolicy provided.
250
+ #
251
+ # @param module_data [Contrast::Agent::ModuleData] the module, and its name, that's being patched into
252
+ # @param module_policy [Contrast::Agent::Patching::Policy::ModulePolicy] All the patchers that apply to
253
+ # this module, sorted by type.
254
+ def patch_into_singleton_methods module_data, module_policy
255
+ mod = module_data.mod
256
+ methods = mod.singleton_methods(false)
257
+ patch_into_methods(mod, methods, module_policy, false)
258
+ end
259
+
252
260
  # We've found the patchers that apply to this class (or module). Now we'll
253
261
  # filter on the given method.
254
262
  #
@@ -279,11 +287,10 @@ module Contrast
279
287
  # it has to be reapplied.
280
288
  # TODO: RUBY-620 should remove the need for this
281
289
  #
282
- # @param mod[Module] the Module for which a prepend action needs to
283
- # be accounted
290
+ # @param module_data [Contrast::Agent::ModuleData] the module, and its name, that's being patched into
284
291
  # @return [Boolean] if an adjustment was made or not
285
- def adjust_for_prepend mod
286
- return false unless mod.cs__name == 'CGI::Util'
292
+ def adjust_for_prepend module_data
293
+ return false unless module_data.mod.cs__name == 'CGI::Util'
287
294
 
288
295
  CGI.include(CGI::Util)
289
296
  CGI.extend(CGI::Util)
@@ -46,6 +46,11 @@ module Contrast
46
46
  raise(ArgumentError,
47
47
  "#{ id } did not have a proper applicator method: #{ applicator } does not respond to #{ applicator_method }. Unable to create.")
48
48
  end
49
+ validate_properties
50
+ validate_rule
51
+ end
52
+
53
+ def validate_properties
49
54
  if (required_properties & optional_properties).any?
50
55
  raise(ArgumentError, "#{ rule_id } had overlapping elements between required and optional properties. Unable to create.")
51
56
  end
@@ -53,8 +58,6 @@ module Contrast
53
58
  raise(ArgumentError, "#{ id } had an unexpected property. Unable to create.")
54
59
  end
55
60
  raise(ArgumentError, "#{ id } did not have a required property. Unable to create.") if (required_properties - properties.keys).any?
56
-
57
- validate_rule
58
61
  end
59
62
 
60
63
  def validate_rule
@@ -50,16 +50,9 @@ module Contrast
50
50
  last_boundary, boundary = scanner.crosses_boundary(query_string, idx, input_analysis_result.value)
51
51
  next unless last_boundary && boundary
52
52
 
53
- input_analysis_result.attack_count = input_analysis_result.attack_count + 1
54
-
55
- kwargs[:start_idx] = idx
56
- kwargs[:end_idx] = idx + length
57
- kwargs[:boundary_overrun_idx] = boundary
58
- kwargs[:input_boundary_idx] = last_boundary
59
-
60
53
  result ||= build_attack_result(context)
61
- update_successful_attack_response(context, input_analysis_result, result, query_string)
62
- append_sample(context, input_analysis_result, result, query_string, **kwargs)
54
+ record_match(idx, length, boundary, last_boundary, kwargs)
55
+ append_match(context, input_analysis_result, result, query_string, kwargs)
63
56
  end
64
57
 
65
58
  result
@@ -75,13 +68,26 @@ module Contrast
75
68
  sample.sqli.query = Contrast::Utils::StringUtils.protobuf_safe_string(candidate_string)
76
69
  sample.sqli.start_idx = sample.sqli.query.index(input).to_i
77
70
  sample.sqli.end_idx = sample.sqli.start_idx + input.length
78
- sample.sqli.boundary_overrun_idx = kwargs[:boundary].to_i
79
- sample.sqli.input_boundary_idx = kwargs[:last_boundary].to_i
71
+ sample.sqli.boundary_overrun_idx = kwargs[:boundary_overrun_idx].to_i
72
+ sample.sqli.input_boundary_idx = kwargs[:input_boundary_idx].to_i
80
73
  sample
81
74
  end
82
75
 
83
76
  private
84
77
 
78
+ def record_match idx, length, boundary, last_boundary, kwargs
79
+ kwargs[:start_idx] = idx
80
+ kwargs[:end_idx] = idx + length
81
+ kwargs[:boundary_overrun_idx] = boundary
82
+ kwargs[:input_boundary_idx] = last_boundary
83
+ end
84
+
85
+ def append_match context, input_analysis_result, result, query_string, **kwargs
86
+ input_analysis_result.attack_count = input_analysis_result.attack_count + 1
87
+ update_successful_attack_response(context, input_analysis_result, result, query_string)
88
+ append_sample(context, input_analysis_result, result, query_string, **kwargs)
89
+ end
90
+
85
91
  def select_scanner database
86
92
  @sql_scanners ||= {
87
93
  Contrast::Agent::Protect::Policy::AppliesSqliRule::DATABASE_MYSQL =>
@@ -12,6 +12,18 @@ module Contrast
12
12
  # This class acts to encapsulate information about the currently executed
13
13
  # request, making it available to the Agent for the duration of the request
14
14
  # in a standardized and normalized format which the Agent understands.
15
+ #
16
+ # @attr_reader timer [Contrast::Utils::Timer] when the context was created
17
+ # @attr_reader logging_hash [Hash] context used to log the request
18
+ # @attr_reader speedracer_input_analysis [Contrast::Api::Settings::InputAnalysis] the protect input analysis of
19
+ # sources on this request
20
+ # @attr_reader request [Contrast::Agent::Request] our wrapper around the Rack::Request for this context
21
+ # @attr_reader response [Contrast::Agent::Response] our wrapper aroudn the Rack::Response or Array for this context,
22
+ # only available after the application has finished its processing
23
+ # @attr_reader activity [Contrast::Api::Dtm::Activity] the application activity found in this request
24
+ # @attr_reader server_activity [Contrast::Api::Dtm::ServerActivity] the server activity found in this request
25
+ # @attr_reader route [Contrast::Api::Dtm::RouteCoverage] the route, used for findings, of this request
26
+ # @attr_reader observed_route [Contrast::Api::Dtm::ObservedRoute] the route, used for coverage, of this request
15
27
  class RequestContext
16
28
  include Contrast::Components::Interface
17
29
  access_component :agent, :analysis, :logging, :scope
@@ -5,7 +5,7 @@ require 'contrast/components/interface'
5
5
 
6
6
  module Contrast
7
7
  module Agent
8
- # Threads used by Contrast.
8
+ # Threads used by Contrast. Any long running thread should be created and managed by our ThreadWatcher class.
9
9
  class Thread < ::Thread
10
10
  include Contrast::Components::Interface
11
11
 
@@ -3,22 +3,31 @@
3
3
 
4
4
  require 'contrast/components/interface'
5
5
  require 'contrast/agent/service_heartbeat'
6
+ require 'contrast/api/communication/messaging_queue'
6
7
 
7
8
  module Contrast
8
9
  module Agent
9
10
  # This class used to ensure that our worker threads are running in multi-process environments
11
+ #
12
+ # @attr_reader heapdump_util [Contrast::Utils::HeapDumpUtil]
13
+ # @attr_reader heartbeat [Contrast::Agent::ServiceHeartbeat]
14
+ # @attr_reader messaging_queue [Contrast::Api::Communication::MessagingQueue]
10
15
  class ThreadWatcher
11
16
  include Contrast::Components::Interface
12
- access_component :logging
17
+ access_component :agent, :logging
13
18
 
14
- attr_reader :heartbeat
19
+ attr_reader :heapdump_util, :heartbeat, :messaging_queue
15
20
 
16
21
  def initialize
17
22
  @pids = {}
23
+ @heapdump_util = Contrast::Utils::HeapDumpUtil.new
18
24
  @heartbeat = Contrast::Agent::ServiceHeartbeat.new
25
+ @messaging_queue = Contrast::Api::Communication::MessagingQueue.new
19
26
  end
20
27
 
21
28
  def startup!
29
+ return unless AGENT.enabled?
30
+
22
31
  unless heartbeat.running?
23
32
  logger.debug('Attempting to start heartbeat thread')
24
33
  heartbeat.start_thread!
@@ -26,11 +35,11 @@ module Contrast
26
35
  heartbeat_result = heartbeat.running?
27
36
  logger.debug('Heartbeat thread status', alive: heartbeat_result)
28
37
 
29
- unless Contrast::Agent.messaging_queue.running?
38
+ unless messaging_queue.running?
30
39
  logger.debug('Attempting to start messaging queue thread')
31
- Contrast::Agent.messaging_queue.start_thread!
40
+ messaging_queue.start_thread!
32
41
  end
33
- messaging_result = Contrast::Agent.messaging_queue.running?
42
+ messaging_result = messaging_queue.running?
34
43
  logger.debug('Messaging thread status', alive: messaging_result)
35
44
 
36
45
  logger.debug('ThreadWatcher started threads')
@@ -44,6 +53,12 @@ module Contrast
44
53
  logger.debug('ThreadWatcher - threads not running')
45
54
  startup!
46
55
  end
56
+
57
+ def shutdown!
58
+ heartbeat.stop!
59
+ messaging_queue.stop!
60
+ heapdump_util.stop!
61
+ end
47
62
  end
48
63
  end
49
64
  end
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Contrast
5
5
  module Agent
6
- VERSION = '4.3.2'
6
+ VERSION = '4.4.0'
7
7
  end
8
8
  end
@@ -10,7 +10,7 @@ module Contrast
10
10
  # Top level gateway to messaging with speedracer
11
11
  class MessagingQueue < Contrast::Agent::WorkerThread
12
12
  include Contrast::Components::Interface
13
- access_component :analysis, :logging, :settings
13
+ access_component :agent, :analysis, :logging, :settings
14
14
 
15
15
  attr_reader :queue, :speedracer
16
16
 
@@ -22,11 +22,19 @@ module Contrast
22
22
 
23
23
  # Use this to bypass the messaging queue and leave response processing to the caller
24
24
  def send_event_immediately event
25
- send_event(event, true)
25
+ if AGENT.disabled?
26
+ logger.warn('Attempted to send event immediately with Agent disabled', caller: caller, event: event)
27
+ return
28
+ end
29
+ speedracer.return_response(event)
26
30
  end
27
31
 
28
32
  # Use this to add a message to the queue and process the response internally
29
33
  def send_event_eventually event
34
+ if AGENT.disabled?
35
+ logger.warn('Attempted to queue event with Agent disabled', caller: caller, event: event)
36
+ return
37
+ end
30
38
  logger.debug('Enqueued event for sending', event_type: event.cs__class)
31
39
  queue << event if event
32
40
  end
@@ -35,13 +43,14 @@ module Contrast
35
43
  speedracer.ensure_startup!
36
44
  return if running?
37
45
 
46
+ @queue ||= Queue.new
38
47
  @_thread = Contrast::Agent::Thread.new do
39
48
  loop do
40
49
  event = queue.pop
41
50
 
42
51
  begin
43
52
  logger.debug('Dequeued event for sending', event_type: event.cs__class)
44
- send_event(event)
53
+ speedracer.process_internally(event)
45
54
  rescue StandardError => e
46
55
  logger.error('Could not send message to service from messaging queue thread.', e)
47
56
  end
@@ -50,25 +59,13 @@ module Contrast
50
59
  logger.debug('Started background sending thread.')
51
60
  end
52
61
 
53
- private
62
+ def stop!
63
+ return unless running?
54
64
 
55
- # return_response is used to determine if we want to return the response to the caller or process it internally
56
- def send_event event, return_response = false
57
- preprocess_event(event)
58
- if return_response
59
- speedracer.return_response(event)
60
- else
61
- speedracer.process_internally(event)
62
- end
63
- end
64
-
65
- # For now this only handles appending assess tags
66
- # eventually we could break out preprocessors for every event type
67
- def preprocess_event event
68
- return unless event.is_a?(Contrast::Api::Dtm::Activity)
69
-
70
- # See if they're even enabled
71
- event.findings.delete_if { |finding| ASSESS.rule_disabled?(finding.rule_id) }
65
+ super
66
+ @queue&.clear
67
+ @queue&.close
68
+ @queue = nil
72
69
  end
73
70
  end
74
71
  end
@@ -11,6 +11,7 @@ module Contrast
11
11
  include Contrast::Components::Interface
12
12
  access_component :agent, :analysis, :logging, :settings
13
13
 
14
+ # @param response [Contrast::Api::Settings::AgentSettings]
14
15
  def process response
15
16
  logger.debug('Received a response', sent_ms: response&.sent_ms)
16
17
 
@@ -31,8 +32,10 @@ module Contrast
31
32
 
32
33
  private
33
34
 
34
- # Given some protobuf messages, update settings.
35
+ # Given some protobuf messages, update server features.
35
36
  # This is the bridge between Contrast Service <-> Settings.
37
+ #
38
+ # @param response [Contrast::Api::Settings::AgentSettings]
36
39
  def process_server_response response
37
40
  server_features = response&.server_features
38
41
  return unless server_features
@@ -44,6 +47,10 @@ module Contrast
44
47
  server_features
45
48
  end
46
49
 
50
+ # Given some protobuf messages, update application settings.
51
+ # This is the bridge between Contrast Service <-> Settings.
52
+ #
53
+ # @param response [Contrast::Api::Settings::AgentSettings]
47
54
  def process_application_response response
48
55
  app_settings = response&.application_settings
49
56
  return unless app_settings
@@ -26,6 +26,7 @@ module Contrast
26
26
  #
27
27
  # @param event [Contrast::Api::Dtm] One of the DTMs valid for the event field of
28
28
  # Contrast::Api::Dtm::Message
29
+ # @return [Contrast::Api::Settings::AgentSettings]
29
30
  def send_one event
30
31
  msg = Contrast::Api::Dtm::Message.build(event)
31
32
  send_message(msg)
@@ -58,24 +59,31 @@ module Contrast
58
59
  end
59
60
 
60
61
  # Or something is not set.
61
- msg = if CONFIG.root.agent.service.host
62
- 'Missing a required connection value to the Contrast Service. ' \
63
- '`agent.service.port` is not set. ' \
64
- 'Falling back to default TCP socket port.'
65
- elsif CONFIG.root.agent.service.port
66
- 'Missing a required connection value to the Contrast Service. ' \
67
- '`agent.service.host` is not set. ' \
68
- 'Falling back to default TCP socket host.'
69
- else
70
- 'Missing a required connection value to the Contrast Service. ' \
71
- 'Neither `agent.service.socket` nor the pair of `agent.service.host` and `agent.service.port` are set. '\
72
- 'Falling back to default TCP socket.'
73
- end
74
- logger.warn(msg,
62
+ logger.warn(log_connection_error_msg,
75
63
  host: CONTRAST_SERVICE.host,
76
64
  port: CONTRAST_SERVICE.port)
77
65
  end
78
66
 
67
+ # If our connection isn't built properly, we need to warn the user. This builds out the context specific
68
+ # message to provide that warning
69
+ #
70
+ # @return [String]
71
+ def log_connection_error_msg
72
+ if CONFIG.root.agent.service.host
73
+ 'Missing a required connection value to the Contrast Service. ' \
74
+ '`agent.service.port` is not set. ' \
75
+ 'Falling back to default TCP socket port.'
76
+ elsif CONFIG.root.agent.service.port
77
+ 'Missing a required connection value to the Contrast Service. ' \
78
+ '`agent.service.host` is not set. ' \
79
+ 'Falling back to default TCP socket host.'
80
+ else
81
+ 'Missing a required connection value to the Contrast Service. ' \
82
+ 'Neither `agent.service.socket` nor the pair of `agent.service.host` and `agent.service.port` are set. '\
83
+ 'Falling back to default TCP socket.'
84
+ end
85
+ end
86
+
79
87
  def send_message msg
80
88
  return unless msg
81
89