defmodule Logrex do @moduledoc """ An Elixir package for more easily adding Logger metadata and formatting the console output so it's easier for humans to parse. It integrates with the Elixir Logger to let you write code like this: ``` name = "Matt" user_info = %{login_count: 1} Logrex.info "New login", [name, user_info.login_count, foo: "bar"] ``` To display this: ``` INFO 20:56:40 New login user=Matt login_count=1 foo=bar ``` """ @doc """ Custom Logger format function, which receives the Logger arguments and returns a string with formatted key/value metadata pairs broken out to the right of the message. """ defdelegate format(level, message, timestamp, metadata), to: Logrex.Formatter defmacro __using__(_opts) do quote do require Logger require Logrex end end @doc false defmacro debug(chardata_or_fun, metadata \\ []) do quote do Logger.debug(unquote(chardata_or_fun), unquote(build_metadata(metadata))) end end @doc false defmacro error(chardata_or_fun, metadata \\ []) do quote do Logger.error(unquote(chardata_or_fun), unquote(build_metadata(metadata))) end end @doc false defmacro info(chardata_or_fun, metadata \\ []) do quote do Logger.info(unquote(chardata_or_fun), unquote(build_metadata(metadata))) end end @doc false defmacro warn(chardata_or_fun, metadata \\ []) do quote do Logger.warn(unquote(chardata_or_fun), unquote(build_metadata(metadata))) end end defp build_metadata(metadata) do metadata |> List.wrap() |> Enum.map(fn {var, _, _} when is_atom(var) -> quote do: {unquote(var), var!(unquote(Macro.var(var, nil)))} {_, _} = var -> var ast -> build_path(ast) end) end defp build_path(ast) do {_, vars} = Macro.prewalk(ast, [], fn {var, _, x} = node, acc when x in [nil] -> {node, [var | acc]} node, acc when node in [:get, Access] -> {node, acc} node, acc when is_atom(node) or is_binary(node) -> {node, [node | acc]} node, acc -> {node, acc} end) [meta_key | _tail] = vars [var | key_path] = Enum.reverse(vars) meta_key = case meta_key do k when is_binary(k) -> :erlang.binary_to_atom(meta_key, :utf8) k -> k end quote do {unquote(meta_key), get_in(Map.delete(var!(unquote(Macro.var(var, nil))), :__struct__), unquote(key_path))} end end end