lib/new_relic/config.ex

defmodule NewRelic.Config do
  @moduledoc """
  New Relic Agent Configuration

  All configuration items can be set via Environment variables _or_ via `Application` config
  """

  @doc """
  **Required**

  Configure your application name. May contain up to 3 names seperated by `;`

  Application name can be configured in two ways:
  * Environment variable: `NEW_RELIC_APP_NAME=MyApp`
  * Application config: `config :new_relic_agent, app_name: "MyApp"`
  """
  def app_name,
    do: get(:app_name)

  @doc """
  **Required**

  Configure your New Relic License Key.

  License Key can be configured in two ways, though using Environment Variables is strongly
  recommended to keep secrets out of source code:
  * Environment variables: `NEW_RELIC_LICENSE_KEY=abc123`
  * Application config: `config :new_relic_agent, license_key: "abc123"`
  """
  def license_key,
    do: get(:license_key)

  @doc false
  def host,
    do: get(:host)

  @doc """
  Configure the Agent logging mechanism.

  This controls how the Agent logs it's own behavior, and doesn't impact your
  applications own logging at all.

  Defaults to the File `"tmp/new_relic.log"`.

  Options:
  - `"stdout"` Write directly to Standard Out
  - `"Logger"` Send Agent logs to Elixir's Logger
  - `"file_name.log"` Write to a chosen file

  Agent logging can be configured in two ways:
  * Environment variable: `NEW_RELIC_LOG=stdout`
  * Application config: `config :new_relic_agent, log: "stdout"`
  """
  def logger,
    do: get(:log)

  @doc """
  An optional list of key/value pairs that will be automatic custom attributes
  on all event types reported (Transactions, etc). Values are determined at Agent
  start.

  Options:
  - `{:system, "ENV_NAME"}` Read a System ENV variable
  - `{module, function, args}` Call a function.
  - `"foo"` A direct value

  This feature is only configurable with `Application` config.

  Example:

  ```
  config :new_relic_agent,
    automatic_attributes: [
      environment: {:system, "APP_ENV"},
      node_name: {Node, :self, []},
      team_name: "Afterlife"
    ]
  ```
  """
  def automatic_attributes,
    do: get(:automatic_attributes)

  @doc """
  An optional list of labels that will be applied to the application.

  Configured with a single string containing a list of key-value pairs:

  `key1:value1;key2:value2`

  The delimiting characters `;` and `:` are not allowed in the `key` or `value`

  Labels can be configured in two ways:
  * Environment variables: `NEW_RELIC_LABELS=region:west;env:prod`
  * Application config: `config :new_relic_agent, labels: "region:west;env:prod"`
  """
  def labels,
    do: get(:labels)

  @doc """
  Some Agent features can be toggled via configuration.

  ### Security

  * `:error_collector_enabled` (default `true`)
    * Toggles collection of any Error traces or metrics
  * `:db_query_collection_enabled` (default `true`)
    * Toggles collection of Database query strings
  * `function_argument_collection_enabled` (default `true`)
    * Toggles collection of traced function arguments

  ### Instrumentation

  Opting out of Instrumentation means that `:telemetry` handlers
  will not be attached, reducing the performance impact to zero.

  * `:plug_instrumentation_enabled` (default `true`)
    * Controls all Plug instrumentation
  * `:ecto_instrumentation_enabled` (default `true`)
    * Controls all Ecto instrumentation
  * `:redix_instrumentation_enabled` (default `true`)
    * Controls all Redix instrumentation
  * `:request_queuing_metrics_enabled`
    * Controls collection of request queuing metrics


  ### Configuration

  Each of these features can be configured in two ways, for example:
  * Environment variables: `NEW_RELIC_ERROR_COLLECTOR_ENABLED=false`
  * Application config: `config :new_relic_agent, error_collector_enabled: false`
  """
  def feature?(toggleable_agent_feature)

  def feature?(:error_collector) do
    get(:features, :error_collector)
  end

  def feature?(:db_query_collection) do
    get(:features, :db_query_collection)
  end

  def feature?(:plug_instrumentation) do
    get(:features, :plug_instrumentation)
  end

  def feature?(:phoenix_instrumentation) do
    get(:features, :phoenix_instrumentation)
  end

  def feature?(:ecto_instrumentation) do
    get(:features, :ecto_instrumentation)
  end

  def feature?(:redix_instrumentation) do
    get(:features, :redix_instrumentation)
  end

  def feature?(:function_argument_collection) do
    get(:features, :function_argument_collection)
  end

  def feature?(:stacktrace_argument_collection) do
    get(:features, :stacktrace_argument_collection)
  end

  def feature?(:request_queuing_metrics) do
    get(:features, :request_queuing_metrics)
  end

  @doc """
  Some Agent features can be controlled via configuration.

  ### Logs In Context

  This feature can be run in multiple "modes":
  * `forwarder` The recommended mode which formats outgoing logs as JSON objects
  ready to be picked up by a [Log Forwarder](https://docs.newrelic.com/docs/logs/enable-log-management-new-relic/enable-log-monitoring-new-relic/enable-log-management-new-relic)
  * `direct` Logs are buffered in the agent and shipped directly to New Relic. Your logs
  will continue being output to their normal destination.
  * `disabled` (default)

  Logs In Context can be configured in two ways:
  * Environment variable `NEW_RELIC_LOGS_IN_CONTEXT=forwarder`
  * Application config `config :new_relic_agent, logs_in_context: :forwarder`

  ### Infinite Tracing

  [Infinite Tracing](https://docs.newrelic.com/docs/understand-dependencies/distributed-tracing/infinite-tracing/introduction-infinite-tracing)
  gives you more control of sampling by collecting 100% of Spans and sending them
  to a Trace Observer for processing.

  You can configure your Trace Observer in two ways:
  * Environment variable `NEW_RELIC_INFINITE_TRACING_TRACE_OBSERVER_HOST=trace-observer.host`
  * Application config `config :new_relic_agent, infinite_tracing_trace_observer_host: "trace-observer.host"`
  """
  def feature(configurable_agent_feature)

  def feature(:logs_in_context) do
    case System.get_env("NEW_RELIC_LOGS_IN_CONTEXT") do
      nil -> Application.get_env(:new_relic_agent, :logs_in_context, :disabled)
      "forwarder" -> :forwarder
      "direct" -> :direct
      other -> other
    end
  end

  def feature(:infinite_tracing) do
    get(:trace_mode)
  end

  @doc false
  def enabled?,
    do: (harvest_enabled?() && app_name() && license_key() && true) || false

  @doc false
  def region_prefix,
    do: get(:region_prefix)

  @doc false
  def event_harvest_config() do
    %{
      harvest_limits: %{
        analytic_event_data:
          Application.get_env(:new_relic_agent, :analytic_event_per_minute, 1000),
        custom_event_data: Application.get_env(:new_relic_agent, :custom_event_per_minute, 1000),
        error_event_data: Application.get_env(:new_relic_agent, :error_event_per_minute, 100),
        span_event_data: Application.get_env(:new_relic_agent, :span_event_per_minute, 1000)
      }
    }
  end

  defp harvest_enabled?, do: get(:harvest_enabled)

  @doc false
  def get(key), do: :persistent_term.get(:nr_config)[key]
  @doc false
  def get(:features, key), do: :persistent_term.get(:nr_features)[key]

  @doc false
  def put(items), do: :persistent_term.put(:nr_config, items)
  @doc false
  def put(:features, items), do: :persistent_term.put(:nr_features, items)

  @external_resource "VERSION"
  @agent_version "VERSION" |> File.read!() |> String.trim()
  @doc false
  def agent_version, do: @agent_version
end