Current section
Files
Jump to
Current section
Files
lib/meta_logger.ex
defmodule MetaLogger do
@moduledoc """
A logger wrapper to keep logger metadata of caller processes.
"""
require Logger
@type chardata_or_fun :: IO.chardata() | String.Chars.t() | (any() -> any())
@type metadata :: keyword()
@metadata ~w(dictionary $logger_metadata$)a
Enum.each(~w(debug error info warn)a, fn level ->
@doc """
Logs a #{level} message keeping logger metadata from caller processes.
Returns `:ok` or an `{:error, reason}` tuple.
## Examples
#{inspect(__MODULE__)}.#{level}("hello?")
#{inspect(__MODULE__)}.#{level}(fn -> "dynamically calculated debug" end)
#{inspect(__MODULE__)}.#{level}(fn -> {"dynamically calculated #{level}", [additional: :metadata]} end)
"""
def unquote(level)(chardata_or_fun, metadata \\ []) do
merge_logger_metadata_from_parent_processes()
Logger.unquote(level)(chardata_or_fun, metadata)
end
end)
@doc """
Logs a message with given `level` keeping logger metadata from caller processes.
Can accept a custom struct if it implements MetaLogger.Formatter protocol.
Returns `:ok` or an `{:error, reason}` tuple.
## Examples
#{inspect(__MODULE__)}.log(:info, "mission accomplished")
#{inspect(__MODULE__)}.log(:error, fn -> "dynamically calculated info" end)
#{inspect(__MODULE__)}.log(:warn, fn -> {"dynamically calculated info", [additional: :metadata]} end)
"""
@spec log(atom(), struct() | list() | chardata_or_fun(), metadata()) :: :ok
def log(level, payload, metadata \\ [])
def log(level, data_struct, metadata) when is_struct(data_struct) do
formatted_log = MetaLogger.Formatter.format(data_struct)
log(level, formatted_log, metadata)
end
def log(level, logs, metadata) when is_atom(level) and is_list(logs),
do: Enum.each(logs, &log(level, &1, metadata))
def log(level, chardata_or_fun, metadata) when is_atom(level) do
merge_logger_metadata_from_parent_processes()
Logger.log(level, chardata_or_fun, metadata)
end
@spec merge_logger_metadata_from_parent_processes() :: :ok
defp merge_logger_metadata_from_parent_processes do
:"$callers"
|> Process.get()
|> List.wrap()
|> Enum.each(&get_process_logger_metadata/1)
end
@spec get_process_logger_metadata(pid()) :: :ok
defp get_process_logger_metadata(process) do
process
|> Process.info()
|> get_in(@metadata)
|> case do
nil -> :ok
metadata -> Logger.metadata(metadata)
end
end
end