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.



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

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



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

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.



41
42
43
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 41

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



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

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.



83
84
85
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 83

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)



89
90
91
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 89

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



64
65
66
67
68
69
70
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 64

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



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

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

#log_collector_messages(messages) ⇒ Object



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

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



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

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.



52
53
54
55
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 52

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

#note_connect_failureObject



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

def note_connect_failure
  self.connect_attempts += 1
end

#retry_from_error?(e, opts) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#should_connect?(force = false) ⇒ Boolean

Don’t connect if we’re already connected, 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)


33
34
35
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 33

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

#signal_connectedObject



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

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

#wait_on_connect(timeout) ⇒ Object



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

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)


133
134
135
# File 'lib/new_relic/agent/agent_helpers/connect.rb', line 133

def waited_on_connect?
  @waited_on_connect
end