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.


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

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
  NewRelic::Agent.agent.health_check.update_status(NewRelic::Agent::HealthCheck::HEALTHY)
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
  NewRelic::Agent.agent.health_check.update_status(NewRelic::Agent::HealthCheck::FAILED_TO_CONNECT)
  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


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

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


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

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.


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

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)


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

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
73
# 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.')
  NewRelic::Agent.agent.health_check.update_status(NewRelic::Agent::HealthCheck::INVALID_LICENSE_KEY)
  disconnect
end

#handle_unrecoverable_agent_error(error) ⇒ Object


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

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

#log_collector_messages(messages) ⇒ Object


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

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


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

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)

211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 211

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


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

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

#wait_on_connect(timeout) ⇒ Object


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

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)

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

def waited_on_connect?
  @waited_on_connect
end