defmodule ExJsonLogger do
@moduledoc """
A Logger formatter that converts logs to single line JSON.
## Logging additional information
Use Logger's metadata to add additional data to the log output.
This can be done through `Logger.metadata/1` or by passing Keywords to standard Logger calls in the optional second parameter.
Logger filters metadata so any additional keys will need to be whitelisted in the backend's configuration.
## Usage
config :logger, :console,
format: {ExJsonLogger, :format},
metadata: [
:request_id,
:additional_key
]
*Currently tested with the `:console` logger backend.*
"""
import Logger.Formatter, only: [format_date: 1, format_time: 1]
@pid_str "#PID"
@doc """
Function referenced in the `:format` config.
"""
@spec format(Logger.level(), Logger.message(), Logger.Formatter.time(), Keyword.t()) :: iodata()
def format(level, msg, timestamp, metadata) do
logger_info = %{
level: level,
time: format_timestamp(timestamp),
msg: IO.iodata_to_binary(msg)
}
metadata
|> Map.new(fn {k, v} -> {k, format_metadata(v)} end)
|> Map.merge(logger_info)
|> encode()
rescue
_ ->
encode(%{
level: :error,
time: format_timestamp(timestamp),
msg: "ExJsonLogger could not format: #{inspect({level, msg, metadata})}"
})
end
defp encode(log_event) do
[Jason.encode_to_iodata!(log_event), ?\n]
end
defp format_timestamp({date, time}) do
unsafe_fragment([format_date(date), ?\s, format_time(time)])
end
defp format_metadata(pid) when is_pid(pid) do
unsafe_fragment([@pid_str | :erlang.pid_to_list(pid)])
end
defp format_metadata(ref) when is_reference(ref) do
unsafe_fragment(:erlang.ref_to_list(ref))
end
defp format_metadata(other), do: other
defp unsafe_fragment(data) do
Jason.Fragment.new([?", data, ?"])
end
end