module OneApm module Agent class Agent # 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 module Connect # number of attempts we've made to contact the server attr_accessor :connect_attempts # Disconnect just sets connected to false, which prevents # the agent from trying to connect again def disconnect @connect_state = :disconnected true end def connected? @connect_state == :connected end def disconnected? @connect_state == :disconnected end # 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. def should_connect?(force = false) force || (!connected? && !disconnected?) end # Retry period is a minute for each failed attempt that # we've made. This should probably do some sort of sane TCP # backoff to prevent hammering the server, but a minute for # each attempt seems to work reasonably well. def connect_retry_period [600, connect_attempts * 60].min end def note_connect_failure self.connect_attempts += 1 end # 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. def log_error(error) OneApm::Manager.logger.error "Error establishing connection with OneApm Service at #{@service.server}:", error end # 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 def handle_license_error(error) OneApm::Manager.logger.error( \ error.message, \ "Visit oneapm.com to obtain a valid license key, or to upgrade your account.") disconnect end def handle_unrecoverable_agent_error(error) OneApm::Manager.logger.error(error.message) disconnect shutdown end def generate_environment_report @environment_report = environment_for_connect end # 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. def environment_for_connect OneApm::Manager.config[:send_environment_info] ? Array(EnvironmentReport.new) : [] end # We've seen objects in the environment report (Rails.env in # particular) that can't seralize to JSON. Cope with that here and # clear out so downstream code doesn't have to check again. def sanitize_environment_report if !@service.valid_to_marshal?(@environment_report) @environment_report = [] end end # Initializes the hash of settings that we send to the # server. Returns a literal hash containing the options def connect_settings sanitize_environment_report { :pid => $$, :host => local_host, :app_name => OneApm::Manager.config.app_names, :language => 'ruby', :labels => OneApm::Manager.config.parsed_labels, :agent_version => OneApm::VERSION::STRING, :environment => @environment_report, :settings => OneApm::Manager.config.to_collector_hash, :high_security => OneApm::Manager.config[:high_security], :identifier => "ruby:#{local_host}" } end # apdex_f is always 4 times the apdex_t def apdex_f (4 * OneApm::Manager.config[:apdex_t]).to_f end # Sets the collector host and connects to the server, then # invokes the final configuration with the returned data def query_server_for_configuration finish_setup(connect_to_server) end # Returns connect data passed back from the server def connect_to_server @service.connect(connect_settings) end # Takes a hash of configuration data returned from the # server and uses it to set local variables and to # initialize various parts of the agent that are configured # separately. # # Can accommodate most arbitrary data - anything extra is # ignored unless we say to do something with it here. def finish_setup(config_data) return if config_data == nil @service.agent_id = config_data['agent_run_id'] if @service OneApm::Manager.logger.debug "Server provided config: #{config_data.inspect}" server_config = OneApm::Configuration::ServerSource.new(config_data, OneApm::Manager.config) OneApm::Manager.config.replace_or_add_config(server_config) log_connection!(config_data) if @service @transaction_rules = OneApm::Support::RulesEngine.create_transaction_rules(config_data) @stats_engine.metric_rules = OneApm::Support::RulesEngine.create_metric_rules(config_data) # If you're adding something else here to respond to the server-side config, # use OneApm::Manager.agent.events.subscribe(:finished_configuring) callback instead! end def local_host @local_host ||= OneApm::Agent::Hostname.get end # Logs when we connect to the server, for debugging purposes # - makes sure we know if an agent has not connected def log_connection!(config_data) OneApm::Manager.logger.debug "Connected to OneApm Service at #{@service.collector.name}" OneApm::Manager.logger.debug "Agent Run = #{@service.agent_id}." OneApm::Manager.logger.debug "Connection data = #{config_data.inspect}" if config_data['messages'] && config_data['messages'].any? log_collector_messages(config_data['messages']) end end def log_collector_messages(messages) messages.each do |message| OneApm::Manager.logger.send(message['level'].downcase, message['message']) end end end end end end