lib/uplink.ex

defmodule Uplink do
  @moduledoc """
  A simple abstraction for standardized observability with telemetry and more.

  Uplink provides a simple abstraction for configuring observability for libraries
  and applications. The heart of Uplink is the `Uplink.Monitor` which provides a
  standard template for common observability needs such as creating Telemetry.Metrics
  definitions, telemetry pollers, or setting up other custom observability requirements.

  The most common challenge when getting started with telemetry is understanding where
  to start, what all the libraries do, and how they all fit together. This creates a
  high learning curve while leaving a huge gap at organizations where most developers
  just want a simple "drop-in" solution for observability that meets the org's requirements.

  Uplink can be used on its own in simple personal projects or as the basis for a standard
  "drop-in" library with a simple abstraction containing standard monitors for your
  organization, such as your Telemetry.Metrics reporter of choice, BEAM VM measurements,
  or Phoenix measurements, all conforming to your org's metric naming conventions or
  other standard practices.

  ## Usage

  ```
  # application supervisor
  children = [
    {
      Uplink, [
        monitors: [
          {MyMonitors.Ecto, [:my_repo]},
          Uplink.Monitors.VM
        ],
        pollers: [
          {10, [{TestModule, :test_emitter, []}]}
        ],
        metric_definitions: [
          Telemetry.Metrics.counter("poller.test.event.lasers")
        ],
        reporters: [
          Telemetry.Metrics.ConsoleReporter
        ]
      ]
    }
  ]
  ```
  """

  @typedoc """
  An MFA tuple for the poller to execute.
  """
  @type measurement :: {module :: module(), function :: atom(), args :: keyword()}

  @typedoc """
  A list of `t:TelemetryMetrics.t/0` definitions.
  """
  @type metric_definitions :: [Telemetry.Metrics.t()]

  @typedoc """
  A module or two-element tuple consisting of a module and arguments to be
  supplied to the monitor. Any arguments passed are passed to all callbacks
  in the monitor.
  """
  @type monitor :: module() | {module :: module(), args :: keyword()}
  @type monitors :: [monitor()]

  @typedoc """
  Time in ms between poller executions.

  Example: `:timer.seconds(5)`
  """
  # move to :telemetry_poller.period() once released https://github.com/beam-telemetry/telemetry_poller/pull/50
  @type period :: pos_integer()

  @typedoc """
  A shorthand specification for listing a number of pollers to be executed at
  a shared interval.

  Example: `{:timer.seconds(5), [{MyApp.Telemetry, :emit_stats, []}]}`
  """
  @type poller_spec :: {period(), [measurement()]}
  @type poller_specs :: [poller_spec()]

  @typedoc """
  A module or two-element tuple consisting of a reporter and arguments to be
  passed to the reporter,

  Example: `{TelemetryMetricsPrometheus, [port: 9568]}`
  """
  @type reporter_spec :: module() | {module :: module(), args :: keyword()}
  @type reporter_specs :: [reporter_spec()]

  @type option ::
          {:metric_definitions, metric_definitions()}
          | {:monitors, monitors()}
          | {:poller_specs, poller_specs()}
          | {:reporters, reporter_specs()}

  @typedoc """
  Valid options. No options are required but a monitor and/or monitor are the
  minimum required to do anything.

  * `:metric_definitions` - a list of additional `t:Telemetry.Metrics.t/0` definitions not
  exposed by monitors.
  * `:monitors` - a list of `t:monitor/0` to use.
  * `:poller_specs` - a list of additoinal `t:poller_spec/0` not exposed by monitors.
  * `:reporters` - a list of `Telemetry.Metrics` reporters, usually 0 or 1.
  """
  @type options :: [option()]

  @doc """
  Returns the child spec for running Uplink under a supervisor.

  Example:

      children = [
        {Uplink, options}
      ]

  See `t:options/0` for a list of available options.
  """
  @spec child_spec(options()) :: Supervisor.child_spec()
  def child_spec(opts) do
    id =
      case Keyword.get(opts, :name, :uplink) do
        name when is_atom(name) -> name
        {:global, name} -> name
        {:via, _, name} -> name
      end

    spec = %{
      id: id,
      start: {Uplink.Supervisor, :start_link, [opts]},
      type: :supervisor
    }

    Supervisor.child_spec(spec, [])
  end
end