defmodule Where do
@moduledoc """
Logging and debug printing that include location information
"""
defp debug_label(caller) do
file = Path.relative_to_cwd(caller.file)
case caller.function do
{fun, arity} -> "[#{file}:#{caller.line}@#{module_name(caller.module)}.#{fun}/#{arity}]"
_ -> "[#{file}:#{caller.line}]"
end
end
defp module_name(name) when is_atom(name), do: module_name(Atom.to_string(name))
defp module_name(name) when is_binary(name), do: String.replace_prefix(name, "Elixir.", "")
@doc "IO.inspect with position information, an optional label and it configured not to truncate output."
defmacro dump(thing, label \\ "") do
pre = debug_label(__CALLER__)
quote do
IO.inspect(unquote(thing), label: "#{unquote(pre)} #{unquote(label)}", pretty: true, printable_limit: :infinity)
end
end
@doc "Like `inspect`, but logging at debug level"
defmacro debug(thing, label \\ "") do
pre = debug_label(__CALLER__)
quote do
require Logger
Logger.debug("#{unquote(pre)} #{unquote(label)}: #{inspect(unquote(thing), pretty: true, printable_limit: :infinity)}")
end
end
@doc "Like `inspect`, but logging at warn level"
defmacro warn(thing, label \\ "") do
pre = debug_label(__CALLER__)
quote do
require Logger
Logger.warn("#{unquote(pre)} #{unquote(label)}: #{inspect(unquote(thing), pretty: true, printable_limit: :infinity)}")
end
end
@doc "Like `inspect`, but logging at error level"
defmacro error(thing, label \\ "") do
pre = debug_label(__CALLER__)
quote do
require Logger
Logger.error("#{unquote(pre)} #{unquote(label)}: #{inspect(unquote(thing), pretty: true, printable_limit: :infinity)}")
end
end
@doc "Like `debug`, but will do nothing unless the `:debug` option is truthy"
defmacro debug?(thing, label \\ "", options) do
pre = debug_label(__CALLER__)
opts = Macro.var(:opts, __MODULE__)
thang = Macro.var(:thing, __MODULE__)
quote do
require Logger
unquote(opts) = unquote(options)
unquote(thang) = unquote(thing)
if unquote(opts)[:debug] do
Logger.debug("#{unquote(pre)} #{unquote(label)}: #{inspect(unquote(thang), pretty: true, printable_limit: :infinity)}")
end
unquote(thang)
end
end
@doc "Like `debug?`, but additionally required the `:verbose` option to be set. Intended for large output."
defmacro verbose?(thing, label \\ "", options) do
pre = debug_label(__CALLER__)
opts = Macro.var(:opts, __MODULE__)
thang = Macro.var(:thing, __MODULE__)
quote do
require Logger
unquote(opts) = unquote(options)
unquote(thang) = unquote(thing)
if unquote(opts)[:debug] && unquote(opts)[:verbose] do
Logger.debug("#{unquote(pre)} #{unquote(label)}: #{inspect(unquote(thang), pretty: true, printable_limit: :infinity)}")
end
unquote(thang)
end
end
@doc """
Tries to 'do what i mean'. Requires the `debug` option to be set regardless. If `verbose` is also
set, will inspect else will attempt to print some (hopefully smaller) type-dependent summary of
the data (list length, map keys).
"""
defmacro smart(thing, label \\ "", options) do
pre = debug_label(__CALLER__)
opts = Macro.var(:opts, __MODULE__)
thang = Macro.var(:thing, __MODULE__)
quote do
require Logger
unquote(opts) = unquote(options)
unquote(thang) = unquote(thing)
cond do
!unquote(opts)[:debug] -> nil
unquote(opts)[:verbose] ->
Logger.debug("#{unquote(pre)} #{unquote(label)}: #{inspect(unquote(thing), pretty: true, printable_limit: :infinity)}")
is_list(unquote(thang)) ->
Logger.debug("#{unquote(pre)} #{unquote(label)} (length): #{Enum.count(unquote(thang))}")
is_map(unquote(thang)) and not is_struct(unquote(thang)) ->
Logger.debug("#{unquote(pre)} #{unquote(label)} (keys): #{inspect(Map.keys(unquote(thang)))}")
true ->
Logger.debug("#{unquote(pre)} #{unquote(label)} (inspect elided, pass `:verbose` to see)")
end
unquote(thang)
end
end
end