lib/logflare_ex.ex

defmodule LogflareEx do
  alias LogflareEx.Client
  alias LogflareEx.Batcher
  alias LogflareEx.BatchedEvent
  alias LogflareEx.Repo
  alias LogflareEx.BatcherSup

  @moduledoc """
  Documentation for `LogflareEx`.
  """

  @doc """
  Creates a client for interacting with Logflare.

  See `LogflareEx.Client`.
  """

  defdelegate client(opts), to: __MODULE__.Client, as: :new

  @doc """
  Send a singular event to Logflare.

  See `send_events/2`

  No local caching is performed. This is less efficient than batching events.
  """
  @spec send_event(Client.t(), map()) :: {:ok, map()} | {:error, Tesla.Env.t()}
  def send_event(client, %{} = event) do
    send_events(client, [event])
  end

  @doc """
  Sends events directly to the Logflare API without local caching. All batching configurations on the client will be ignored.

  It is advised to use `send_batched_events/2` instead to spread out API requests.

  ### Example

  ```elixir
  iex> client = LogflareEx.client()
  %LogflareEx.Client{...}

  # singular event
  iex> LogflareEx.send_event(client, %{my: "event"})
  {:ok, %{"message"=> "Logged!}}

  # multiple events
  iex> LogflareEx.send_events(client, [%{my: "event"}, ...])
  {:ok, %{"message"=> "Logged!}}

  # a tesla result will be returned on error
  iex> client |> LogflareEx.send_event(%{my: "event"})
  {:error, %Tesla.Env{...}}

  ```
  """
  @spec send_events(Client.t(), [map()]) :: {:ok, map()} | {:error, Tesla.Env.t()}
  def send_events(_client, []), do: {:error, :no_events}

  def send_events(%Client{source_token: nil, source_name: nil}, _batch), do: {:error, :no_source}

  def send_events(client, [%{} | _] = batch) do
    body = Bertex.encode(%{"batch" => batch, "source" => client.source_token})

    case Tesla.post(client.tesla_client, "/api/logs", body) do
      {:ok, %Tesla.Env{status: status, body: body}} when status < 300 ->
        {:ok, Jason.decode!(body)}

      {_result, %Tesla.Env{} = result} ->
        # on_error callback
        case Map.get(client, :on_error) do
          {m, f, 1} -> apply(m, f, [result])
          cb when is_function(cb) -> cb.(result)
          _ -> :noop
        end

        {:error, result}
    end
  end

  @doc """
  Sends a batched event.

  See `send_batched_events/2`
  """
  @spec send_batched_events(Client.t(), [map()]) :: :ok
  def send_batched_event(client, %{} = event), do: send_batched_events(client, [event])

  @doc """
  Sends events in batches. Configuration of the batching is dependent on the provided client.

  Batched events are cached locally and flushed at regular intervals.

  ### Example

  ```elixir
  # create a client
  iex> client = LogflareEx.client()
  %LogflareEx.Client{...}

  # singular event
  iex> LogflareEx.send_batched_event(client, %{...})
  :ok

  # list of events
  iex> LogflareEx.send_batched_event(client, [%{...}, ...])
  :ok
  ```
  """
  @spec send_batched_events(Client.t(), [map()]) :: :ok
  def send_batched_events(_client, []), do: :ok

  def send_batched_events(client, events) when is_list(events) do
    BatcherSup.ensure_started(client)

    for event <- events do
      client
      |> Map.take([:source_token, :source_name])
      |> Map.put(:body, event)
      |> Batcher.create_event()
    end

    :ok
  end

  @doc """
  Returns a count of all queued events.
  """
  @spec count_queued_events() :: non_neg_integer()
  def count_queued_events() do
    Repo.all(BatchedEvent)
    |> length()
  end

  @doc """
  Performs a flush for a given client. Attempts to clear the queue of events for the given client.
  """
  @spec flush(Client.t()) :: :ok
  def flush(client) do
    BatcherSup.ensure_started(client)
    Batcher.flush(client)
  end
end