class Appsignal::Config
def self.add_loader_defaults(name, env: nil, root_path: nil, **options)
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)
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
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
def self.loader_defaults @loader_defaults ||= [] end
def self.system_tmp_dir
-
(String)- System's tmp directory.
def self.system_tmp_dir if Gem.win_platform? Dir.tmpdir else File.realpath("/tmp") end end
def [](key)
-
(Object)- The configuration value.
Parameters:
-
key(Symbol, String) -- The configuration option key to fetch.
def [](key) config_hash[key] end
def []=(key, value)
-
(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?
-
(Boolean)- True if valid and active for the current environment.
def active? valid? && active_for_env? end
def active_for_env?
-
(Boolean)- True if active for the current environment.
def active_for_env? config_hash[:active] end
def apply_overrides
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
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
- Since: - 4.0.0
Returns:
-
(void)-
def freeze super config_hash.freeze config_hash.transform_values(&:freeze) end
def initialize(
- 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
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
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
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)
def merge_dsl_options(options) @dsl_config.merge!(options) merge(options) end
def valid?
-
(Boolean)- True if the configuration is valid, false otherwise.
def valid? @valid end
def validate
-
(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
@!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?
- Api: - private
def yml_config_file? return false unless yml_config_file File.exist?(yml_config_file) end