module Appsignal

def _load_config!(env_param = nil, &block) # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity

Returns:
  • (void) -

Parameters:
  • block (Proc) -- Optional block to configure the config object.
  • env_param (String, nil) -- Used by diagnose CLI to pass through
def _load_config!(env_param = nil, &block) # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
  # Ensure it's not an empty string if it's a value
  proper_env_param = env_param&.to_s&.strip
  # Unset it if it's an empty string
  proper_env_param = nil if proper_env_param&.empty?
  context = Appsignal::Config::Context.new(
    :env => Config.determine_env(env_param),
    :root_path => Config.determine_root_path
  )
  # If there's a config/appsignal.rb file
  if context.dsl_config_file?
    if config
      # When calling `Appsignal.configure` from an app, not the
      # `config/appsignal.rb` file, with also a Ruby config file present.
      message = "The `Appsignal.configure` helper is called from within an " \
        "app while a `#{context.dsl_config_file}` file is present. " \
        "The `config/appsignal.rb` file is ignored when the " \
        "config is loaded with `Appsignal.configure` from within an app. " \
        "We recommend moving all config to the `config/appsignal.rb` file " \
        "or the `Appsignal.configure` helper in the app."
      Appsignal::Utils::StdoutAndLoggerMessage.warning(message)
    else
      # Load it when no config is present
      #
      # We don't pass the `env_var` or `context.env` here so that the
      # `Appsignal.configure` or `Appsignal.start` can figure out the
      # environment themselves if it was not explicitly set.
      # This way we prevent forcing an environment that's auto loaded on
      # the to-be-loaded config file.
      #
      # The `(proper)_env_param` is only set when it's explicitly set,
      # which means it needs to override any auto detected environment.
      load_dsl_config_file(context.dsl_config_file, proper_env_param)
    end
  else
    # Load config if no config file was found and no config is present yet
    # This will load the config/appsignal.yml file automatically
    @config ||= Config.new(context.root_path, context.env)
  end
  # Allow a block to be given to customize the config and override any
  # loaded config before it's validated.
  block.call(config) if block_given?
  # Apply any config overrides after the user config has been merged
  config&.apply_overrides
  # Skip validation if not configured as active for this environment
  return unless config.active_for_env?
  # Validate the config, if present
  config&.validate
end

def _start_logger

Returns:
  • (void) -
def _start_logger
  if config && config[:log] == "file" && config.log_file_path
    start_internal_file_logger(config.log_file_path)
  else
    start_internal_stdout_logger
  end
  internal_logger.level =
    if config
      config.log_level
    else
      Appsignal::Config::DEFAULT_LOG_LEVEL
    end
  return unless @in_memory_logger
  messages = @in_memory_logger.messages_for_level(internal_logger.level)
  internal_logger << messages.join
  @in_memory_logger = nil
end

def active?

Other tags:
    Since: - 0.2.7

Returns:
  • (Boolean) -

Other tags:
    Example: Don't do this -
    Example: Do this -
def active?
  config&.active? && extension_loaded?
end

def check_if_started!

Returns:
  • (void) -

Raises:
  • (Appsignal::NotStartedError) -
def check_if_started!
  return if started?
  begin
    raise config_error if config_error?
  rescue
    # Raise the NotStartedError and make the config error the error cause
    raise NotStartedError, config_error
  end
  # Raise the NotStartedError as normal
  raise NotStartedError
end

def collect_environment_metadata

def collect_environment_metadata
  Appsignal::Environment.report("ruby_version") do
    "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
  end
  Appsignal::Environment.report("ruby_engine") { RUBY_ENGINE }
  if defined?(RUBY_ENGINE_VERSION)
    Appsignal::Environment.report("ruby_engine_version") do
      RUBY_ENGINE_VERSION
    end
  end
  Appsignal::Environment.report_supported_gems
end

def config_file_context?

context.
Returns true if we're currently in the `config/appsignal.rb` file
def config_file_context?
  ENV.fetch("_APPSIGNAL_CONFIG_FILE_CONTEXT", nil) == "true"
end

def configure(env_param = nil, root_path: nil)

Other tags:
    See: https://docs.appsignal.com/ruby/configuration/options.html - Configuration options
    See: https://docs.appsignal.com/ruby/configuration.html - Configuration guide
    See: Config -
    See: config -

Returns:
  • (void) -

Other tags:
    Yieldparam: config_dsl - The configuration DSL object

Other tags:
    Yield: - Gives the configuration DSL instance to the block.

Parameters:
  • root_path (String) -- The path to look the `config/appsignal.yml` config file in.
  • env_param (String, Symbol) -- The environment to load.

Other tags:
    Example: Load config without a block -
    Example: Calling configure multiple times for different environments resets the configuration -
    Example: Automatically detects the app environment -
    Example: Configure AppSignal for the application and select the environment -
    Example: Configure AppSignal for the application -
def configure(env_param = nil, root_path: nil)
  if Appsignal.started?
    Appsignal.internal_logger
      .warn("AppSignal is already started. Ignoring `Appsignal.configure` call.")
    return
  end
  root_path_param = root_path
  if params_match_loaded_config?(env_param, root_path_param)
    config
  else
    @config = Config.new(
      root_path_param || Config.determine_root_path,
      Config.determine_env(env_param),
      # If in the context of an `config/appsignal.rb` config file, do not
      # load the `config/appsignal.yml` file.
      # The `.rb` file is a replacement for the `.yml` file so it shouldn't
      # load both.
      :load_yaml_file => !config_file_context?
    )
  end
  # When calling `Appsignal.configure` from a Rails initializer and a YAML
  # file is present. We will not load the YAML file in the future.
  if !config_file_context? && config.yml_config_file?
    message = "The `Appsignal.configure` helper is called while a " \
      "`config/appsignal.yml` file is present. In future versions the " \
      "`config/appsignal.yml` file will be ignored when loading the " \
      "config. We recommend moving all config to the " \
      "`config/appsignal.rb` file, or the `Appsignal.configure` helper " \
      "in Rails initializer file, and remove the " \
      "`config/appsignal.yml` file."
    Appsignal::Utils::StdoutAndLoggerMessage.warning(message)
  end
  config_dsl = Appsignal::Config::ConfigDSL.new(config)
  return unless block_given?
  yield config_dsl
  config.merge_dsl_options(config_dsl.dsl_options)
  nil
end

def dsl_config_file_loaded?

@!visibility private
def dsl_config_file_loaded?
  defined?(@dsl_config_file_loaded) ? true : false
end

def extension_loaded?

Other tags:
    Since: - 1.0.0

Other tags:
    See: Extension -

Returns:
  • (Boolean) -
def extension_loaded?
  !!extension_loaded
end

def forked

Returns:
  • (void) -
def forked
  return unless active?
  Appsignal._start_logger
  internal_logger.debug("Forked process, resubscribing and restarting extension")
  Appsignal::Extension.start
  nil
end

def get_server_state(key)

@!visibility private
def get_server_state(key)
  Appsignal::Extension.get_server_state(key)
end

def in_memory_logger

@!visibility private
def in_memory_logger
  @in_memory_logger ||=
    Appsignal::Utils::IntegrationMemoryLogger.new.tap do |l|
      l.formatter = log_formatter("appsignal")
    end
end

def internal_logger

@!visibility private
def internal_logger
  @internal_logger ||= in_memory_logger
end

def load(integration_name)

Other tags:
    Since: - 3.12.0

Returns:
  • (void) -

Parameters:
  • integration_name (String, Symbol) -- Name of the integration to load.

Other tags:
    Example: Load Sinatra integrations and define custom config -
    Example: Load Sinatra integrations -
def load(integration_name)
  Loaders.load(integration_name)
  nil
end

def load_dsl_config_file(path, env_param = nil)

anything.
loaded more than once, which should never happen, it will not do
If the config file has already been loaded once and it's trying to be

Load the `config/appsignal.rb` config file, if present.
def load_dsl_config_file(path, env_param = nil)
  return if defined?(@dsl_config_file_loaded)
  begin
    ENV["_APPSIGNAL_CONFIG_FILE_CONTEXT"] = "true"
    ENV["_APPSIGNAL_CONFIG_FILE_ENV"] = env_param if env_param
    @dsl_config_file_loaded = true
    require path
  rescue => error
    @config_error = error
    message = "Not starting AppSignal because an error occurred while " \
      "loading the AppSignal config file.\n" \
      "File: #{path.inspect}\n" \
      "#{error.class.name}: #{error}"
    Kernel.warn "appsignal ERROR: #{message}"
    internal_logger.error "#{message}\n#{error.backtrace.join("\n")}"
  ensure
    unless Appsignal.config
      # Ensure _a config object_ is present, even if something went wrong
      # loading it or the file is empty. In this config file context, see
      # the context env vars, it will intentionally not load the YAML file.
      Appsignal.configure
      # Disable if no config was loaded from the file but it is present
      config[:active] = false
    end
    # Disable on config file error
    config[:active] = false if defined?(@config_error)
    ENV.delete("_APPSIGNAL_CONFIG_FILE_CONTEXT")
    ENV.delete("_APPSIGNAL_CONFIG_FILE_ENV")
  end
end

def log_formatter(prefix = nil)

@!visibility private
def log_formatter(prefix = nil)
  pre = "#{prefix}: " if prefix
  proc do |severity, datetime, _progname, msg|
    "[#{datetime.strftime("%Y-%m-%dT%H:%M:%S")} (process) " \
      "##{Process.pid}][#{severity}] #{pre}#{msg}\n"
  end
end

def params_match_loaded_config?(env_param, root_path_param)

def params_match_loaded_config?(env_param, root_path_param)
  # No config present: can't match any config
  return false unless config
  # Check if the params, if present, match the loaded config
  (env_param.nil? || config.env == env_param.to_s) &&
    (root_path_param.nil? || config.root_path == root_path_param)
end

def start # rubocop:disable Metrics/AbcSize

Other tags:
    Since: - 0.7.0

Returns:
  • (void) -

Other tags:
    Example: with custom loaded configuration -
def start # rubocop:disable Metrics/AbcSize
  if ENV.fetch("_APPSIGNAL_DIAGNOSE", false)
    internal_logger.info("Skipping start in diagnose context")
    return
  end
  if started?
    internal_logger.warn("Ignoring call to Appsignal.start after AppSignal has started")
    return
  end
  if config_file_context?
    internal_logger.warn(
      "Ignoring call to Appsignal.start in config file context."
    )
    return
  end
  unless extension_loaded?
    internal_logger.info("Not starting AppSignal, extension is not loaded")
    return
  end
  internal_logger.debug("Loading AppSignal gem")
  _load_config!
  _start_logger
  if config.active_for_env?
    if config.valid?
      @started = true
      internal_logger.info "Starting AppSignal #{Appsignal::VERSION} " \
        "(#{$PROGRAM_NAME}, Ruby #{RUBY_VERSION}, #{RUBY_PLATFORM})"
      config.write_to_environment
      Appsignal::Extension.start
      Appsignal::Hooks.load_hooks
      Appsignal::Loaders.start
      if config[:enable_allocation_tracking] && !Appsignal::System.jruby?
        Appsignal::Extension.install_allocation_event_hook
        Appsignal::Environment.report_enabled("allocation_tracking")
      end
      Appsignal::Probes.start if config[:enable_minutely_probes]
      collect_environment_metadata
      @config.freeze
    else
      internal_logger.info("Not starting, no valid config for this environment")
    end
  else
    internal_logger.info("Not starting, not active for #{config.env}")
  end
  nil
end

def start_internal_file_logger(path)

def start_internal_file_logger(path)
  @internal_logger = Appsignal::Utils::IntegrationLogger.new(path)
  internal_logger.formatter = log_formatter
rescue SystemCallError => error
  start_internal_stdout_logger
  internal_logger.warn "Unable to start internal logger with log path '#{path}'."
  internal_logger.warn error
end

def start_internal_stdout_logger

def start_internal_stdout_logger
  @internal_logger = Appsignal::Utils::IntegrationLogger.new($stdout)
  internal_logger.formatter = log_formatter("appsignal")
end

def started?

Other tags:
    Since: - 3.12.0

Other tags:
    See: Extension -

Returns:
  • (Boolean) -
def started?
  defined?(@started) ? @started : false
end

def stop(called_by = nil)

Other tags:
    Since: - 1.0.0

Returns:
  • (void) -

Parameters:
  • called_by (String) -- Name of the thing that requested the agent to
def stop(called_by = nil)
  Thread.new do
    if called_by
      internal_logger.info("Stopping AppSignal (#{called_by})")
    else
      internal_logger.info("Stopping AppSignal")
    end
    Appsignal::Extension.stop
    Appsignal::Probes.stop
    Appsignal::CheckIn.stop
  end.join
  nil
end

def testing?

@!visibility private
def testing?
  false
end