defmodule ElixirTools.Metrix do @moduledoc false use GenServer alias __MODULE__ alias ElixirTools.Metrix.Adapters.Adapter require Logger @adapter Application.get_env(:pagantis_elixir_tools, ElixirTools.Metrix)[:adapter] @typep opts :: {:adapter_module, module} # Client @spec start_link(any) :: {:ok, pid} | {:error, term} def start_link(opts) do GenServer.start_link(Metrix, opts, name: Metrix) end @spec count(Adapter.metric(), Adapter.value(), Adapter.tag_map(), [opts]) :: :ok | :error def count(metric, value, tags \\ %{}, opts \\ []) do args = [metric, value, to_tags(tags, opts)] to_adapter(:count, args, opts) end @spec increment(Adapter.metric(), Adapter.value(), Adapter.tag_map(), [opts]) :: :ok | :error def increment(metric, value, tags \\ %{}, opts \\ []) do args = [metric, value, to_tags(tags, opts)] to_adapter(:increment, args, opts) end @spec decrement(Adapter.metric(), Adapter.value(), Adapter.tag_map(), [opts]) :: :ok | :error def decrement(metric, value, tags \\ %{}, opts \\ []) do args = [metric, value, to_tags(tags, opts)] to_adapter(:decrement, args, opts) end @spec gauge(Adapter.metric(), Adapter.value(), Adapter.tag_map(), [opts]) :: :ok | :error def gauge(metric, value, tags \\ %{}, opts \\ []) do args = [metric, value, to_tags(tags, opts)] to_adapter(:gauge, args, opts) end @spec histogram(Adapter.metric(), Adapter.value(), Adapter.tag_map(), [opts]) :: :ok | :error def histogram(metric, value, tags \\ %{}, opts \\ []) do args = [metric, value, to_tags(tags, opts)] to_adapter(:histogram, args, opts) end @spec timing(Adapter.metric(), Adapter.value(), Adapter.tag_map(), [opts]) :: :ok | :error def timing(metric, value, tags \\ %{}, opts \\ []) do args = [metric, value, to_tags(tags, opts)] to_adapter(:timing, args, opts) end @spec to_adapter(atom, [any], [opts]) :: :ok | :error defp to_adapter(method, args, opts) do GenServer.cast(Metrix, {:send_metric, method, args, opts}) end @spec to_tags(Adapter.tag_map(), [opts]) :: any def to_tags(tags, opts) do adapter_module = opts[:adapter_module] || @adapter default_tags = Application.get_env(:pagantis_elixir_tools, Metrix)[:default_tags] || %{} adapter_module.to_tags(Map.merge(tags, default_tags)) end # Server @typep connect_opt :: {:adapter, module} | {:sleep_ms, pos_integer} @typep metric_opt :: {:adapter, module} @typep state :: nil @impl true @spec init([connect_opt]) :: {:ok, state} def init(opts) do connect_adapter(opts) {:ok, nil} end @impl true @spec handle_cast({:send_metric, atom, any, [metric_opt]}, state) :: {:noreply, state} def handle_cast({:send_metric, method, args, opts}, state) do adapter_module = opts[:adapter_module] || @adapter try do apply(adapter_module, method, args) rescue e -> Logger.error(fn -> "Metric #{inspect(args)} not sent: #{inspect(e)}" end) end {:noreply, state} end @impl true def handle_info({:connect_adapter, opts}, state) do connect_adapter(opts) {:noreply, state} end @spec connect_adapter([connect_opt]) :: :ok defp connect_adapter(opts) do adapter = opts[:adapter_module] || @adapter sleep_time = opts[:sleep_ms] || 5000 try do adapter.connect() :ok rescue e -> Logger.error(fn -> "Could not connect to adapter: #{inspect(e)}. Retry in #{sleep_time} ms" end) Process.send_after(self(), {:connect_adapter, opts}, sleep_time) end end end