lib/membrane/log/logger.ex

defmodule Membrane.Log.Logger do
  @moduledoc deprecated: "Use Elixir `Logger` instead"
  @moduledoc """
  Module containing functions spawning, shutting down, and handling messages
  sent to logger.
  """

  use GenServer

  alias Membrane.Log.Logger.State

  # Type that defines possible return values of start/start_link functions.
  @type on_start :: GenServer.on_start()

  # Type that defines possible process options passed to start/start_link
  # functions.
  @type process_options_t :: GenServer.options()

  # Type that defines possible logger-specific options passed to
  # start/start_link functions.
  @type logger_options_t :: struct | nil

  # Type that defines possible tags attached to the message
  @type tag_t :: atom

  # Type that defines possible messages, witch are passed to logger.
  @type message_t :: list(message_t) | String.t() | {:binary, binary} | integer

  # Type that defines possible log levels.
  @type msg_level_t :: :warn | :debug | :info

  @doc """
  Starts process for logger of given module, initialized with given options and
  links it to the current process in the supervision tree.

  Works similarly to `GenServer.start_link/3` and has the same return values.
  """
  @spec start_link(module, logger_options_t, process_options_t) :: on_start
  def start_link(module, logger_options \\ nil, process_options \\ []) do
    GenServer.start_link(__MODULE__, {module, logger_options}, process_options)
  end

  @doc """
  Starts process for logger of given module, initialized with given options
  outside of the supervision tree.

  Works similarly to `GenServer.start/3` and has the same return values.
  """
  @spec start(module, logger_options_t, process_options_t) :: on_start
  def start(module, logger_options \\ nil, process_options \\ []) do
    GenServer.start(__MODULE__, {module, logger_options}, process_options)
  end

  @doc """
  Stops given logger process.

  It will wait for reply for amount of time passed as second argument
  (in milliseconds).

  Will trigger calling `handle_shutdown/2` logger callback.

  Returns `:ok`.
  """
  @spec shutdown(pid, timeout) :: :ok
  def shutdown(server, timeout \\ 5000) do
    GenServer.stop(server, :normal, timeout)
    :ok
  end

  # Private API

  @impl true
  def init({module, options}) do
    # Call logger's initialization callback
    case module.handle_init(options) do
      {:ok, internal_state} ->
        state = State.new(module, internal_state)
        {:ok, state}

      {:error, reason} ->
        {:stop, reason}
    end
  end

  @impl true
  def terminate(_reason, %State{module: module, internal_state: internal_state}) do
    module.handle_shutdown(internal_state)
  end

  # Callback invoked on incoming buffer.
  #
  # It will delegate actual processing to handle_log/5.
  @impl true
  def handle_info(
        {:membrane_log, level, content, time, tags},
        %State{module: module, internal_state: internal_state} = state
      ) do
    level
    |> module.handle_log(content, time, tags, internal_state)
    |> handle_callback(state)
    |> Membrane.Helper.GenServer.noreply()
  end

  # Generic handler that can be used to convert return value from
  # logger callback to reply that is accepted by GenServer.handle_*.
  #
  # Case when callback returned successfully and requests no further action.
  defp handle_callback({:ok, new_internal_state}, state) do
    {:ok, %{state | internal_state: new_internal_state}}
  end

  # Generic handler that can be used to convert return value from
  # logger callback to reply that is accepted by GenServer.handle_info.
  #
  # Case when callback returned failure.
  defp handle_callback({:error, reason, new_internal_state}, %{module: module} = state) do
    content = ["Error occurred while trying to log message. Reason = ", inspect(reason)]

    case module.handle_log(:warn, content, Membrane.Time.pretty_now(), [], new_internal_state) do
      {:ok, new_internal_state} ->
        {:ok, %{state | internal_state: new_internal_state}}

      {:error, reason, new_internal_state} ->
        {{:error, reason}, %{state | internal_state: new_internal_state}}

      invalid_callback ->
        raise """
        Logger callback replies are expected to be one of:
          {:ok, state}
          {:error, reason, state}
        but got callback reply #{inspect(invalid_callback)}.
        """
    end
  end
end