# encoding: utf-8 module OneApm module Agent class Agent module Start # Sanity-check the agent configuration and start the agent, # setting up the worker thread and the exit handler to shut # down the agent def check_config_and_start_agent return unless monitoring? && has_correct_license_key? return if using_forking_dispatcher? setup_and_start_agent end # This is the shared method between the main agent startup and the # after_fork call restarting the thread in deferred dispatchers. # # Treatment of @started and env report is important to get right. def setup_and_start_agent(options={}) @started = true @harvester.mark_started unless in_resque_child_process? generate_environment_report install_exit_handler @harvest_samplers.load_samplers unless OneApm::Manager.config[:disable_samplers] end connect_in_foreground if OneApm::Manager.config[:sync_startup] start_worker_thread(options) end # True if we have initialized and completed 'start' def started? @started end # Check whether we have already started, which is an error condition def already_started? if started? OneApm::Manager.logger.error("Agent Started Already!") true end end # Installs our exit handler, which exploits the weird # behavior of at_exit blocks to make sure it runs last, by # doing an at_exit within an at_exit block. def install_exit_handler if OneApm::Manager.config[:send_data_on_exit] && !weird_ruby? at_exit do # Workaround for MRI 1.9 bug that loses exit codes in at_exit blocks. # This is necessary to get correct exit codes for the agent's # test suites. # http://bugs.ruby-lang.org/issues/5218 if defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" && RUBY_VERSION.match(/^1\.9/) exit_status = $!.status if $!.is_a?(SystemExit) shutdown exit exit_status if exit_status else shutdown end end end end # The agent is disabled when it is not force enabled by the # 'agent_enabled' option (e.g. in a manual start), or # enabled normally through the configuration file def disabled? !Manager.config[:agent_enabled] end # Logs the configured application names def app_name_configured? names = OneApm::Manager.config.app_names return names.respond_to?(:any?) && names.any? end # Connecting in the foreground blocks further startup of the # agent until we have a connection - useful in cases where # you're trying to log a very-short-running process and want # to get statistics from before a server connection # (typically 20 seconds) exists def connect_in_foreground OneApm::Manager.disable_all_tracing { connect(:keep_retrying => false) } end # If we're using sinatra, old versions run in an at_exit # block so we should probably know that def using_sinatra? defined?(Sinatra::Application) end # we should not set an at_exit block if people are using # these as they don't do standard at_exit behavior per MRI/YARV def weird_ruby? OneApm::LanguageSupport.using_engine?('rbx') || OneApm::LanguageSupport.using_engine?('jruby') || using_sinatra? end # Warn the user if they have configured their agent not to # send data, that way we can see this clearly in the log file def monitoring? if OneApm::Manager.config[:monitor_mode] true else OneApm::Manager.logger.warn('Agent configured not to send data in this environment.') false end end # Tell the user when the license key is missing so they can # fix it by adding it to the file def has_license_key? if OneApm::Manager.config[:license_key] && OneApm::Manager.config[:license_key].length > 0 true else OneApm::Manager.logger.warn("No license key found. " + "This often means your oneapm.yml file was not found, or it lacks a section for the running environment, '#{OneApm::Probe.instance.env}'. You may also want to try linting your oneapm.yml to ensure it is valid YML.") false end end # A correct license key exists and is of the proper length def has_correct_license_key? has_license_key? end # If we're using a dispatcher that forks before serving # requests, we need to wait until the children are forked # before connecting, otherwise the parent process sends useless data def using_forking_dispatcher? if [:puma, :passenger, :rainbows, :unicorn].include? OneApm::Manager.config[:dispatcher] OneApm::Manager.logger.info "Deferring startup of agent reporting thread because #{Manager.config[:dispatcher]} may fork." true else false end end def defer_for_background_jobs? if defer_for_delayed_job? OneApm::Manager.logger.debug "Deferring startup for DelayedJob" return true end if defer_for_resque? OneApm::Manager.logger.debug "Deferring startup for Resque in case it daemonizes" return true end false end require 'one_apm/inst/background_job/delayed_job_injection' def defer_for_delayed_job? OneApm::Manager.config[:dispatcher] == :delayed_job && !OneApm::DelayedJobInjection.worker_name end # Return true if we're using resque and it hasn't had a chance to (potentially) # daemonize itself. This avoids hanging when there's a Thread started # before Resque calls Process.daemon (Jira RUBY-857) def defer_for_resque? OneApm::Manager.config[:dispatcher] == :resque && OneApm::LanguageSupport.can_fork? && !OneApm::Support::ForkedProcessChannel.listener.started? end def in_resque_child_process? @service.is_a?(OneApm::Collector::ForkedProcessService) end # Log startup information that we almost always want to know def log_startup log_environment log_dispatcher log_app_name end # Classy logging of the agent version and the current pid, # so we can disambiguate processes in the log file and make # sure they're running a reasonable version def log_version_and_pid OneApm::Manager.logger.debug "OneApm Ruby Agent #{OneApm::VERSION::STRING} Initialized: pid = #{$$}" end # Log the environment the app thinks it's running in. # Useful in debugging, as this is the key for config YAML lookups. def log_environment OneApm::Manager.logger.info "Environment: #{OneApm::Probe.instance.env}" end # Logs the dispatcher to the log file to assist with # debugging. When no debugger is present, logs this fact to # assist with proper dispatcher detection def log_dispatcher dispatcher_name = OneApm::Manager.config[:dispatcher].to_s if dispatcher_name.empty? OneApm::Manager.logger.info 'No known dispatcher detected.' else OneApm::Manager.logger.info "Dispatcher: #{dispatcher_name}" end end def log_app_name OneApm::Manager.logger.info "Application: #{Manager.config.app_names.join(", ")}" end def log_ignore_url_regexes regexes = OneApm::Manager.config[:'rules.ignore_url_regexes'] unless regexes.empty? OneApm::Manager.logger.info "Ignoring URLs that match the following regexes: #{regexes.map(&:inspect).join(", ")}." end end end end end end