lib/file/only/logger.ex

defmodule File.Only.Logger do
  @moduledoc """
  A simple logger which writes messages to log files only (not to the console).
  """

  @doc """
  Either aliases `File.Only.Logger` (this module) and requires the alias or
  imports `File.Only.Logger`. In the latter case, you could instead simply
  `import File.Only.Logger`.

  ## Examples

      use File.Only.Logger, alias: FileLogger

      use File.Only.Logger

      import File.Only.Logger
  """
  defmacro __using__(options) do
    alias = options[:alias]

    if alias do
      quote do
        alias unquote(__MODULE__), as: unquote(alias)
        require unquote(alias)
      end
    else
      quote do
        import unquote(__MODULE__)
      end
    end
  end

  defmacro debug(message_id, variables, do_block)

  defmacro debug(message_id, variables, do: message) do
    quote do
      def debug(unquote(message_id), unquote(variables)) do
        File.Only.Logger.Proxy.log(:debug, unquote(message))
      end
    end
  end

  @doc ~S'''
  Injects function `info` into the caller's module.

  The function will write the `message` returned by the `do_block`
  to the configured info log file.

  ## Examples

      use File.Only.Logger

      info :game_state, {player, game} do
        """
        \nNote that #{player.name}...
        • Has joined game #{inspect(game.name)}
        • Game state: #{inspect(game.state)}
        """
      end
  '''
  defmacro info(message_id, variables, do_block)

  defmacro info(message_id, variables, do: message) do
    quote do
      def info(unquote(message_id), unquote(variables)) do
        File.Only.Logger.Proxy.log(:info, unquote(message))
      end
    end
  end

  defmacro notice(message_id, variables, do_block)

  defmacro notice(message_id, variables, do: message) do
    quote do
      def notice(unquote(message_id), unquote(variables)) do
        File.Only.Logger.Proxy.log(:notice, unquote(message))
      end
    end
  end

  defmacro warning(message_id, variables, do_block)

  defmacro warning(message_id, variables, do: message) do
    quote do
      def warning(unquote(message_id), unquote(variables)) do
        File.Only.Logger.Proxy.log(:warning, unquote(message))
      end
    end
  end

  defmacro warn(message_id, variables, do_block)

  defmacro warn(message_id, variables, do: message) do
    quote do
      def warn(unquote(message_id), unquote(variables)) do
        File.Only.Logger.Proxy.log(:warn, unquote(message))
      end
    end
  end

  @doc ~S'''
  Injects function `error` into the caller's module.

  The function will write the `message` returned by the `do_block`
  to the configured error log file.

  ## Examples

      use File.Only.Logger

      error :exit, {reason} do
        """
        \n'exit' caught...
        • Reason:
          #{inspect(reason)}
        """
      end
  '''
  defmacro error(message_id, variables, do_block)

  defmacro error(message_id, variables, do: message) do
    quote do
      def error(unquote(message_id), unquote(variables)) do
        File.Only.Logger.Proxy.log(:error, unquote(message))
      end
    end
  end

  defmacro critical(message_id, variables, do_block)

  defmacro critical(message_id, variables, do: message) do
    quote do
      def critical(unquote(message_id), unquote(variables)) do
        File.Only.Logger.Proxy.log(:critical, unquote(message))
      end
    end
  end

  defmacro alert(message_id, variables, do_block)

  defmacro alert(message_id, variables, do: message) do
    quote do
      def alert(unquote(message_id), unquote(variables)) do
        File.Only.Logger.Proxy.log(:alert, unquote(message))
      end
    end
  end

  defmacro emergency(message_id, variables, do_block)

  defmacro emergency(message_id, variables, do: message) do
    quote do
      def emergency(unquote(message_id), unquote(variables)) do
        File.Only.Logger.Proxy.log(:emergency, unquote(message))
      end
    end
  end

  @doc ~S'''
  Returns string "<module>.<function>/<arity>" e.g. "My.Math.sqrt/1"
  for the given [environment](`Macro.Env`).

  ## Examples

      use File.Only.Logger

      error :exit, {reason, env} do
        """
        \n'exit' caught...
        • Reason:
          #{inspect(reason)}
        • Inside function:
          #{fun(env)}
        """
      end
  '''
  defmacro fun(env) do
    quote bind_quoted: [env: env] do
      case env do
        %Macro.Env{function: {name, arity}} ->
          if to_string(name) |> String.contains?(" "),
            do: "#{inspect(env.module)}.'#{name}'/#{arity}",
            else: "#{inspect(env.module)}.#{name}/#{arity}"

        %Macro.Env{function: nil} ->
          "'not inside a function'"
      end
    end
  end

  defmacro app do
    quote do
      case :application.get_application() do
        {:ok, app} -> app
        :undefined -> Application.get_application(__MODULE__) || :undefined
      end
    end
  end

  defmacro lib do
    quote do
      unquote(Mix.Project.config()[:app])
    end
  end

  defmacro mod do
    quote do
      "#{inspect(__MODULE__)}"
    end
  end

  defmacro from do
    quote do
      """
      • App: #{app()}
      • Library: #{lib()}
      • Module: #{mod()}
      """
      |> String.trim_trailing()
    end
  end
end