lib/telegraf.ex

defmodule Telegraf do
  use Supervisor

  @external_resource "README.md"
  @moduledoc "README.md"
             |> File.read!()
             |> String.split("<!-- MDOC !-->")
             |> Enum.fetch!(1)

  @typedoc "Name of the telegraf instance."
  @type name :: atom()

  @opts_definition [
    name: [
      type: :atom,
      doc: "Name of the telegraf instance.",
      required: true
    ],
    transport: [
      type: :atom,
      doc: "A module implementing `Telegraf.Transport` behaviour.",
      default: Telegraf.Transport.UnixSocket
    ],
    transport_options: [
      type: :keyword_list,
      doc:
        "Options passed to the transport adapter. " <>
          "Checkout each transport adapter docs for a detailed description of the options.",
      default: []
    ],
    serializer: [
      type: :atom,
      doc: "A module implementing `Telegraf.Serializer` behaviour.",
      default: Telegraf.Serializer.LineProtocol
    ]
  ]

  @doc """
  Starts a #{inspect(__MODULE__)} supervisor.

  ## Supported options

  #{NimbleOptions.docs(@opts_definition)}
  """
  def start_link(opts) do
    opts = validate_options!(opts)

    name = Keyword.fetch!(opts, :name)
    Supervisor.start_link(__MODULE__, opts, name: name)
  end

  @impl Supervisor
  def init(opts) do
    name = Keyword.fetch!(opts, :name)
    transport = Keyword.fetch!(opts, :transport)
    serializer = Keyword.fetch!(opts, :serializer)
    transport_options = Keyword.fetch!(opts, :transport_options)

    :persistent_term.put({__MODULE__, name}, {transport, serializer})

    name
    |> transport.children(transport_options)
    |> Supervisor.init(strategy: :one_for_one)
  end

  @doc """
  Sends a metric to the telegraf daemon

  ```elixir
  metric = %Telegraf.Metric{
    name: "weather",
    tag_set: %{location: "us-midwest"},
    field_set: %{temperature: 82},
    timestamp: System.os_time()
  }

  Telegraf.send(MyTelegraf, metric)
  ```

  """
  @spec send(name(), Metric.t() | [Metric.t()], Keyword.t()) :: :ok | {:error, term()}
  def send(name, metric_or_metrics, opts \\ []) do
    {transport, serializer} = :persistent_term.get({__MODULE__, name})

    message = metric_or_metrics |> List.wrap() |> serializer.serialize()

    transport.send(name, message, opts)
  end

  defp validate_options!(opts) do
    case NimbleOptions.validate(opts, @opts_definition) do
      {:ok, opts} ->
        opts

      {:error, %NimbleOptions.ValidationError{message: message}} ->
        raise ArgumentError,
              "invalid configuration given to #{inspect(__MODULE__)}.start_link/1, " <> message
    end
  end
end