class Appsignal::Config

def self.add_loader_defaults(name, env: nil, root_path: nil, **options)

@!visibility private
def self.add_loader_defaults(name, env: nil, root_path: nil, **options)
  if Appsignal.config
    Appsignal.internal_logger.warn(
      "The config defaults from the '#{name}' loader are ignored since " \
        "the AppSignal config has already been initialized."
    )
  end
  loader_defaults << {
    :name => name,
    :env => env,
    :root_path => root_path,
    :options => options.compact
  }
end

def self.determine_env(initial_env = nil)

@!visibility private
Determine which env AppSignal should initialize with.
def self.determine_env(initial_env = nil)
  [
    initial_env,
    ENV.fetch("_APPSIGNAL_CONFIG_FILE_ENV", nil), # PRIVATE ENV var used by the diagnose CLI
    ENV.fetch("APPSIGNAL_APP_ENV", nil),
    ENV.fetch("RAILS_ENV", nil),
    ENV.fetch("RACK_ENV", nil)
  ].compact.each do |env_value|
    value = env_value.to_s.strip
    next if value.empty?
    return value if value
  end
  loader_defaults.reverse.each do |loader_defaults|
    env = loader_defaults[:env]
    return env if env
  end
  nil
end

def self.determine_root_path

@!visibility private
Determine which root path AppSignal should initialize with.
def self.determine_root_path
  app_path_env_var = ENV.fetch("APPSIGNAL_APP_PATH", nil)
  return app_path_env_var if app_path_env_var
  loader_defaults.reverse.each do |loader_defaults|
    root_path = loader_defaults[:root_path]
    return root_path if root_path
  end
  Dir.pwd
end

def self.loader_defaults

@!visibility private
def self.loader_defaults
  @loader_defaults ||= []
end

def self.system_tmp_dir

Returns:
  • (String) - System's tmp directory.
def self.system_tmp_dir
  if Gem.win_platform?
    Dir.tmpdir
  else
    File.realpath("/tmp")
  end
end

def [](key)

Returns:
  • (Object) - The configuration value.

Parameters:
  • key (Symbol, String) -- The configuration option key to fetch.
def [](key)
  config_hash[key]
end

def []=(key, value)

Returns:
  • (void) -

Parameters:
  • value (Object) -- The configuration value to set.
  • key (Symbol, String) -- The configuration option key to set.
def []=(key, value)
  config_hash[key] = value
end

def active?

Returns:
  • (Boolean) - True if valid and active for the current environment.
def active?
  valid? && active_for_env?
end

def active_for_env?

Returns:
  • (Boolean) - True if active for the current environment.
def active_for_env?
  config_hash[:active]
end

def apply_overrides

@!visibility private
Apply any overrides for invalid settings.
def apply_overrides
  @override_config = determine_overrides
  merge(override_config)
end

def detect_from_system

def detect_from_system
  {}.tap do |hash|
    hash[:log] = "stdout" if Appsignal::System.heroku?
    # Make AppSignal active by default if APPSIGNAL_PUSH_API_KEY
    # environment variable is present and not empty.
    env_push_api_key = ENV["APPSIGNAL_PUSH_API_KEY"] || ""
    hash[:active] = true unless env_push_api_key.strip.empty?
    hash[:enable_at_exit_hook] = "always" if Appsignal::Extension.running_in_container?
    # Set revision from REVISION file if present in project root
    # This helps with Capistrano and Hatchbox.io deployments
    revision_from_file = detect_revision_from_file
    hash[:revision] = revision_from_file if revision_from_file
  end
end

def detect_revision_from_file

def detect_revision_from_file
  return unless root_path
  revision_file_path = File.join(root_path, "REVISION")
  unless File.exist?(revision_file_path)
    logger.debug "No REVISION file found at: #{revision_file_path}"
    return
  end
  unless File.readable?(revision_file_path)
    logger.debug "REVISION file is not readable at: #{revision_file_path}"
    return
  end
  begin
    revision_content = File.read(revision_file_path).strip
    if revision_content.empty?
      logger.debug "REVISION file found but is empty at: #{revision_file_path}"
      nil
    else
      logger.debug "REVISION file found and read successfully at: #{revision_file_path}"
      revision_content
    end
  rescue SystemCallError => e
    logger.debug "Error occurred while reading REVISION file at " \
      "#{revision_file_path}: #{e.class}: #{e.message}\n#{e.backtrace}"
    nil
  end
end

def determine_overrides

config.
Set config options based on the final user config. Fix any conflicting
def determine_overrides
  config = {}
  # If an error was detected during config file reading/parsing and the new
  # behavior is enabled to not start AppSignal on incomplete config, do not
  # start AppSignal.
  config[:active] = false if @yml_config_file_error
  if config_hash[:activejob_report_errors] == "discard" &&
      !Appsignal::Hooks::ActiveJobHook.version_7_1_or_higher?
    config[:activejob_report_errors] = "all"
  end
  if config_hash[:sidekiq_report_errors] == "discard" &&
      !Appsignal::Hooks::SidekiqHook.version_5_1_or_higher?
    config[:sidekiq_report_errors] = "all"
  end
  config
end

def freeze

Other tags:
    Since: - 4.0.0

Returns:
  • (void) -
def freeze
  super
  config_hash.freeze
  config_hash.transform_values(&:freeze)
end

def initialize(

Other tags:
    See: https://docs.appsignal.com/ruby/instrumentation/integrating-appsignal.html -
    See: https://docs.appsignal.com/ruby/configuration/load-order.html -
    See: https://docs.appsignal.com/ruby/configuration/ -

Returns:
  • (Config) - The initialized configuration object.

Parameters:
  • load_yaml_file (Boolean) -- Whether to load configuration from
  • env (String) -- The environment to load when AppSignal is started. It
  • root_path (String) -- Path to the root of the application.
def initialize(
  root_path,
  env,
  load_yaml_file: true
)
  @load_yaml_file = load_yaml_file
  @root_path = root_path.to_s
  @yml_config_file_error = false
  @yml_config_file = yml_config_file
  @valid = false
  @env = env.to_s
  @config_hash = {}
  @system_config = {}
  @loaders_config = {}
  @initial_config = {}
  @file_config = {}
  @env_config = {}
  @override_config = {}
  @dsl_config = {} # Can be set using `Appsignal.configure`
  load_config
end

def load_config

@!visibility private
def load_config
  # Set defaults
  # Deep duplicate each frozen default value
  merge(DEFAULT_CONFIG.transform_values(&:dup))
  # Set config based on the system
  @system_config = detect_from_system
  merge(system_config)
  # Set defaults from loaders in reverse order so the first registered
  # loader's defaults overwrite all others
  self.class.loader_defaults.reverse.each do |loader_defaults|
    options = config_hash
    new_loader_defaults = {}
    defaults = loader_defaults[:options]
    defaults.each do |option, value|
      new_loader_defaults[option] =
        if ARRAY_OPTIONS.key?(option)
          # Merge arrays: new value first
          value + options[option]
        else
          value
        end
    end
    @loaders_config.merge!(new_loader_defaults.merge(
      :root_path => loader_defaults[:root_path],
      :env => loader_defaults[:env]
    ))
    merge(new_loader_defaults)
  end
  # Track origin of env
  @initial_config[:env] = @env
  # Load the config file if it exists
  if @load_yaml_file
    @file_config = load_from_disk || {}
    merge(file_config)
  elsif yml_config_file?
    # When in a `config/appsignal.rb` file and it detects a
    # `config/appsignal.yml` file.
    # Only logged and printed on `Appsignal.start`.
    message = "Both a Ruby and YAML configuration file are found. " \
      "The `config/appsignal.yml` file is ignored when the " \
      "config is loaded from `config/appsignal.rb`. Move all config to " \
      "the `config/appsignal.rb` file and remove the " \
      "`config/appsignal.yml` file."
    Appsignal::Utils::StdoutAndLoggerMessage.warning(message)
  end
  # Load config from environment variables
  @env_config = load_from_environment
  merge(env_config)
  # Track origin of env
  env_loaded_from_env = ENV.fetch("APPSIGNAL_APP_ENV", nil)
  @env_config[:env] = env_loaded_from_env if env_loaded_from_env
end

def load_from_disk

def load_from_disk
  return unless yml_config_file?
  read_options = YAML::VERSION >= "4.0.0" ? { :aliases => true } : {}
  configurations = YAML.load(ERB.new(File.read(yml_config_file)).result, **read_options)
  config_for_this_env = configurations[env]
  if config_for_this_env
    config_for_this_env.transform_keys(&:to_sym)
  else
    logger.error "Not loading from config file: config for '#{env}' not found"
    nil
  end
rescue => e
  @yml_config_file_error = true
  message = "An error occurred while loading the AppSignal config file. " \
    "Not starting AppSignal.\n" \
    "File: #{yml_config_file.inspect}\n" \
    "#{e.class.name}: #{e}"
  Kernel.warn "appsignal: #{message}"
  logger.error "#{message}\n#{e.backtrace.join("\n")}"
  nil
end

def load_from_environment

def load_from_environment
  config = {}
  # Configuration with string type
  STRING_OPTIONS.each do |option, env_key|
    env_var = ENV.fetch(env_key, nil)
    next unless env_var
    config[option] = env_var
  end
  # Configuration with boolean type
  BOOLEAN_OPTIONS.each do |option, env_key|
    env_var = ENV.fetch(env_key, nil)
    next unless env_var
    config[option] = env_var.casecmp("true").zero?
  end
  # Configuration with array of strings type
  ARRAY_OPTIONS.each do |option, env_key|
    env_var = ENV.fetch(env_key, nil)
    next unless env_var
    config[option] = env_var.split(",")
  end
  # Configuration with float type
  FLOAT_OPTIONS.each do |option, env_key|
    env_var = ENV.fetch(env_key, nil)
    next unless env_var
    config[option] = env_var.to_f
  end
  config
end

def log_file_path

@!visibility private
def log_file_path
  return @log_file_path if defined? @log_file_path
  path = config_hash[:log_path] || (root_path && File.join(root_path, "log"))
  if path && File.writable?(path)
    @log_file_path = File.join(File.realpath(path), "appsignal.log")
    return @log_file_path
  end
  system_tmp_dir = self.class.system_tmp_dir
  if File.writable? system_tmp_dir
    $stdout.puts "appsignal: Unable to log to '#{path}'. Logging to " \
      "'#{system_tmp_dir}' instead. " \
      "Please check the permissions for the application's (log) " \
      "directory."
    @log_file_path = File.join(system_tmp_dir, "appsignal.log")
  else
    $stdout.puts "appsignal: Unable to log to '#{path}' or the " \
      "'#{system_tmp_dir}' fallback. Please check the permissions " \
      "for the application's (log) directory."
    @log_file_path = nil
  end
  @log_file_path
end

def log_level

@!visibility private
def log_level
  option = config_hash[:log_level]
  level =
    if option
      log_level_option = LOG_LEVEL_MAP[option]
      log_level_option
    end
  level.nil? ? Appsignal::Config::DEFAULT_LOG_LEVEL : level
end

def logger

def logger
  Appsignal.internal_logger
end

def merge(new_config)

def merge(new_config)
  new_config.each do |key, value|
    logger.debug("Config key '#{key}' is being overwritten") unless config_hash[key].nil?
    config_hash[key] = value
  end
end

def merge_dsl_options(options)

@!visibility private
def merge_dsl_options(options)
  @dsl_config.merge!(options)
  merge(options)
end

def valid?

Returns:
  • (Boolean) - True if the configuration is valid, false otherwise.
def valid?
  @valid
end

def validate

Returns:
  • (void) -
def validate
  # Strip path from endpoint so we're backwards compatible with
  # earlier versions of the gem.
  # TODO: Move to its own method, maybe in `#[]=`?
  endpoint_uri = URI(config_hash[:endpoint])
  config_hash[:endpoint] =
    if endpoint_uri.port == 443
      "#{endpoint_uri.scheme}://#{endpoint_uri.host}"
    else
      "#{endpoint_uri.scheme}://#{endpoint_uri.host}:#{endpoint_uri.port}"
    end
  push_api_key = config_hash[:push_api_key] || ""
  if push_api_key.strip.empty?
    @valid = false
    logger.error "Push API key not set after loading config"
  else
    @valid = true
  end
end

def write_to_environment # rubocop:disable Metrics/AbcSize

rubocop:disable Metrics/AbcSize
@!visibility private
def write_to_environment # rubocop:disable Metrics/AbcSize
  ENV["_APPSIGNAL_ACTIVE"]                       = active?.to_s
  ENV["_APPSIGNAL_AGENT_PATH"]                   = File.expand_path("../../ext", __dir__).to_s
  ENV["_APPSIGNAL_APP_NAME"]                     = config_hash[:name]
  ENV["_APPSIGNAL_APP_PATH"]                     = root_path.to_s
  ENV["_APPSIGNAL_BIND_ADDRESS"]                 = config_hash[:bind_address].to_s
  ENV["_APPSIGNAL_CA_FILE_PATH"]                 = config_hash[:ca_file_path].to_s
  ENV["_APPSIGNAL_CPU_COUNT"]                    = config_hash[:cpu_count].to_s
  ENV["_APPSIGNAL_DNS_SERVERS"]                  = config_hash[:dns_servers].join(",")
  ENV["_APPSIGNAL_ENABLE_HOST_METRICS"]          = config_hash[:enable_host_metrics].to_s
  ENV["_APPSIGNAL_ENABLE_STATSD"]                = config_hash[:enable_statsd].to_s
  ENV["_APPSIGNAL_ENABLE_NGINX_METRICS"]         = config_hash[:enable_nginx_metrics].to_s
  ENV["_APPSIGNAL_APP_ENV"]                      = env
  ENV["_APPSIGNAL_FILES_WORLD_ACCESSIBLE"]       = config_hash[:files_world_accessible].to_s
  ENV["_APPSIGNAL_FILTER_PARAMETERS"]            = config_hash[:filter_parameters].join(",")
  ENV["_APPSIGNAL_FILTER_SESSION_DATA"]          = config_hash[:filter_session_data].join(",")
  ENV["_APPSIGNAL_HOSTNAME"]                     = config_hash[:hostname].to_s
  ENV["_APPSIGNAL_HOST_ROLE"]                    = config_hash[:host_role].to_s
  ENV["_APPSIGNAL_HTTP_PROXY"]                   = config_hash[:http_proxy]
  ENV["_APPSIGNAL_IGNORE_ACTIONS"]               = config_hash[:ignore_actions].join(",")
  ENV["_APPSIGNAL_IGNORE_ERRORS"]                = config_hash[:ignore_errors].join(",")
  ENV["_APPSIGNAL_IGNORE_LOGS"]                  = config_hash[:ignore_logs].join(",")
  ENV["_APPSIGNAL_IGNORE_NAMESPACES"]            = config_hash[:ignore_namespaces].join(",")
  ENV["_APPSIGNAL_LANGUAGE_INTEGRATION_VERSION"] = "ruby-#{Appsignal::VERSION}"
  ENV["_APPSIGNAL_LOG"]                          = config_hash[:log]
  ENV["_APPSIGNAL_LOG_LEVEL"]                    = config_hash[:log_level]
  ENV["_APPSIGNAL_LOG_FILE_PATH"]                = log_file_path.to_s if log_file_path
  ENV["_APPSIGNAL_LOGGING_ENDPOINT"]             = config_hash[:logging_endpoint]
  ENV["_APPSIGNAL_PROCESS_NAME"]                 = $PROGRAM_NAME
  ENV["_APPSIGNAL_PUSH_API_ENDPOINT"]            = config_hash[:endpoint]
  ENV["_APPSIGNAL_PUSH_API_KEY"]                 = config_hash[:push_api_key]
  ENV["_APPSIGNAL_RUNNING_IN_CONTAINER"]         = config_hash[:running_in_container].to_s
  ENV["_APPSIGNAL_SEND_ENVIRONMENT_METADATA"]    = config_hash[:send_environment_metadata].to_s
  ENV["_APPSIGNAL_STATSD_PORT"]                  = config_hash[:statsd_port].to_s
  ENV["_APPSIGNAL_NGINX_PORT"]                   = config_hash[:nginx_port].to_s
  if config_hash[:working_directory_path]
    ENV["_APPSIGNAL_WORKING_DIRECTORY_PATH"] = config_hash[:working_directory_path]
  end
  ENV["_APP_REVISION"] = config_hash[:revision].to_s
end

def yml_config_file

def yml_config_file
  @yml_config_file ||=
    root_path.nil? ? nil : File.join(root_path, "config", "appsignal.yml")
end

def yml_config_file?

Other tags:
    Api: - private
def yml_config_file?
  return false unless yml_config_file
  File.exist?(yml_config_file)
end