Module: NewRelic::Agent::AgentHelpers::Connect

Included in:
NewRelic::Agent::Agent
Defined in:
lib/new_relic/agent/agent_helpers/connect.rb

Overview

This module is an artifact of a refactoring of the connect method - all of its methods are used in that context, so it can be refactored at will. It should be fully tested

Defined Under Namespace

Classes: WaitOnConnectTimeout

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#connect_attemptsObject

number of attempts we’ve made to contact the server



13
14
15
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 13

def connect_attempts
  @connect_attempts
end

Instance Method Details

#connect(options = {}) ⇒ Object

Establish a connection to New Relic servers.

By default, if a connection has already been established, this method will be a no-op.

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :keep_retrying (Boolean) — default: true

    If true, this method will block until a connection is successfully established, continuing to retry upon failure. If false, this method will return after either successfully connecting, or after failing once.

  • :force_reconnect (Boolean) — default: false

    If true, this method will force establishment of a new connection with New Relic, even if there is already an existing connection. This is useful primarily when re-establishing a new connection after forking off from a parent process.



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 185

def connect(options = {})
  opts = connect_options(options)
  return unless should_connect?(opts[:force_reconnect])

  ::NewRelic::Agent.logger.debug("Connecting Process to New Relic: #$0")
  connect_to_server
  @connected_pid = $$
  @connect_state = :connected
  signal_connected
rescue NewRelic::Agent::ForceDisconnectException => e
  handle_force_disconnect(e)
rescue NewRelic::Agent::LicenseException => e
  handle_license_error(e)
rescue NewRelic::Agent::UnrecoverableAgentException => e
  handle_unrecoverable_agent_error(e)
rescue StandardError, Timeout::Error, NewRelic::Agent::ServerConnectionException => e
  retry if retry_from_error?(e, opts)
rescue Exception => e
  ::NewRelic::Agent.logger.error('Exception of unexpected type during Agent#connect():', e)

  raise
end

#connect_options(options) ⇒ Object



160
161
162
163
164
165
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 160

def connect_options(options)
  {
    keep_retrying: true,
    force_reconnect: Agent.config[:force_reconnect]
  }.merge(options)
end

#connect_retry_periodObject

Per the spec at /agents/agent-specs/Collector-Response-Handling.md, retry connections after a specific backoff sequence to prevent hammering the server.



45
46
47
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 45

def connect_retry_period
  NewRelic::CONNECT_RETRY_PERIODS[connect_attempts] || NewRelic::MAX_RETRY_PERIOD
end

#connect_to_serverObject

Builds the payload to send to the connect service, connects, then configures the agent using the response from the connect service



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 98

def connect_to_server
  request_builder = ::NewRelic::Agent::Connect::RequestBuilder.new(
    @service,
    Agent.config,
    event_harvest_config,
    environment_for_connect
  )
  connect_response = @service.connect(request_builder.connect_payload)

  response_handler = ::NewRelic::Agent::Connect::ResponseHandler.new(self, Agent.config)
  response_handler.configure_agent(connect_response)

  log_connection(connect_response) if connect_response
  connect_response
end

#connected?Boolean

Returns:

  • (Boolean)


22
23
24
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 22

def connected?
  @connect_state == :connected
end

#disconnectObject

Disconnect just sets the connect state to disconnected, preventing further retries.



17
18
19
20
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 17

def disconnect
  @connect_state = :disconnected
  true
end

#disconnected?Boolean

Returns:

  • (Boolean)


26
27
28
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 26

def disconnected?
  @connect_state == :disconnected
end

#environment_for_connectObject

Checks whether we should send environment info, and if so, returns the snapshot from the local environment. Generating the EnvironmentReport has the potential to trigger require calls in Rails environments, so this method should only be called synchronously from on the main thread.



85
86
87
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 85

def environment_for_connect
  @environment_report ||= Agent.config[:send_environment_info] ? Array(EnvironmentReport.new) : []
end

#event_harvest_configObject

Constructs and memoizes an event_harvest_config hash to be used in the payload sent during connect (and reconnect)



91
92
93
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 91

def event_harvest_config
  @event_harvest_config ||= Configuration::EventHarvestConfig.from_config(Agent.config)
end

#handle_license_error(error) ⇒ Object

When the server sends us an error with the license key, we want to tell the user that something went wrong, and let them know where to go to get a valid license key

After this runs, it disconnects the agent so that it will no longer try to connect to the server, saving the application and the server load



68
69
70
71
72
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 68

def handle_license_error(error)
  ::NewRelic::Agent.logger.error(error.message,
    'Visit newrelic.com to obtain a valid license key, or to upgrade your account.')
  disconnect
end

#handle_unrecoverable_agent_error(error) ⇒ Object



74
75
76
77
78
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 74

def handle_unrecoverable_agent_error(error)
  ::NewRelic::Agent.logger.error(error.message)
  disconnect
  shutdown
end

#log_collector_messages(messages) ⇒ Object



125
126
127
128
129
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 125

def log_collector_messages(messages)
  messages.each do |message|
    ::NewRelic::Agent.logger.send(message['level'].downcase, message['message'])
  end
end

#log_connection(config_data) ⇒ Object

Logs when we connect to the server, for debugging purposes

  • makes sure we know if an agent has not connected



116
117
118
119
120
121
122
123
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 116

def log_connection(config_data)
  ::NewRelic::Agent.logger.debug("Connected to NewRelic Service at #{@service.collector.name}")
  ::NewRelic::Agent.logger.debug("Agent Run       = #{@service.agent_id}.")
  ::NewRelic::Agent.logger.debug("Connection data = #{config_data.inspect}")
  if config_data['messages']&.any?
    log_collector_messages(config_data['messages'])
  end
end

#log_error(error) ⇒ Object

When we have a problem connecting to the server, we need to tell the user what happened, since this is not an error we can handle gracefully.



56
57
58
59
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 56

def log_error(error)
  ::NewRelic::Agent.logger.error("Error establishing connection with New Relic Service at #{control.server}:",
    error)
end

#note_connect_failureObject



49
50
51
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 49

def note_connect_failure
  self.connect_attempts += 1
end

#retry_from_error?(e, opts) ⇒ Boolean

Returns:

  • (Boolean)


208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 208

def retry_from_error?(e, opts)
  # Allow a killed (aborting) thread to continue exiting during shutdown.
  # See: https://github.com/newrelic/newrelic-ruby-agent/issues/340
  raise if Thread.current.status == 'aborting'

  log_error(e)
  return false unless opts[:keep_retrying]

  note_connect_failure
  ::NewRelic::Agent.logger.info("Will re-attempt in #{connect_retry_period} seconds")
  sleep(connect_retry_period)
  true
end

#serverless?Boolean

Returns:

  • (Boolean)


30
31
32
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 30

def serverless?
  Agent.config[:'serverless_mode.enabled']
end

#should_connect?(force = false) ⇒ Boolean

Don’t connect if we’re already connected, if we’re in serverless mode, or if we tried to connect and were rejected with prejudice because of a license issue, unless we’re forced to by force_reconnect.

Returns:

  • (Boolean)


37
38
39
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 37

def should_connect?(force = false)
  force || (!connected? && !disconnected?)
end

#signal_connectedObject



139
140
141
142
143
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 139

def signal_connected
  @wait_on_connect_mutex.synchronize do
    @wait_on_connect_condition.signal
  end
end

#wait_on_connect(timeout) ⇒ Object



145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 145

def wait_on_connect(timeout)
  return if connected?

  @waited_on_connect = true
  NewRelic::Agent.logger.debug('Waiting on connect to complete.')

  @wait_on_connect_mutex.synchronize do
    @wait_on_connect_condition.wait(@wait_on_connect_mutex, timeout)
  end

  unless connected?
    raise WaitOnConnectTimeout, "Agent was unable to connect in #{timeout} seconds."
  end
end

#waited_on_connect?Boolean

Used for testing to let us know we’ve actually started to wait

Returns:

  • (Boolean)


135
136
137
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 135

def waited_on_connect?
  @waited_on_connect
end