Current section
Files
Jump to
Current section
Files
lib/logger.ex
defmodule Argos.Logger do
@moduledoc """
Handler de Logger para Argos con formateo basado en Aurora.
Redirige eventos de `Logger` al módulo configurado (por defecto
`Argos.Logger.Default`). Filtra niveles según configuración del proyecto.
## Configuración
Cada proyecto puede configurar su propio logger y niveles permitidos:
config :argos, :logger, MiProyecto.Logger.Custom
config :argos, :logger_levels, [:debug, :info, :warning, :error]
Los errores siempre se muestran, independientemente de la configuración.
En modo develop, todos los niveles configurados se muestran.
En modo producción, solo se muestran errores a menos que se configuren otros niveles.
## Modo Raw
Los loggers pueden implementar `log_raw/3` para soportar modo raw,
donde pueden calcular el tamaño del TUI y posicionar los logs debajo del TUI.
"""
@behaviour :gen_event
@impl true
def init(__MODULE__) do
logger_impl = Application.get_env(:argos, :logger, Argos.Logger.Default)
unless function_exported?(logger_impl, :log, 3) do
raise ArgumentError,
"Logger implementation #{inspect(logger_impl)} must implement Argos.Logger.Behaviour"
end
env = current_env()
app_name = get_app_name()
configured_levels = get_configured_levels(app_name, env)
{:ok,
%{
logger_impl: logger_impl,
env: env,
handler_id: Argos.Logger,
app_name: app_name,
configured_levels: configured_levels,
raw_mode: Application.get_env(app_name, :logger_raw_mode, false)
}}
end
@impl true
def handle_event({_level, gl, _event}, state) when node(gl) != node() do
{:ok, state}
end
@impl true
def handle_event({level, _gl, {Logger, msg, ts, md}}, state) do
if should_log?(state.env, level, state.configured_levels) do
try do
metadata = prepare_metadata(md, ts, level)
message = extract_message(msg)
if state.raw_mode && function_exported?(state.logger_impl, :log_raw, 3) do
state.logger_impl.log_raw(level, message, metadata)
else
state.logger_impl.log(level, message, metadata)
end
rescue
e ->
IO.puts(:stderr, "Argos.Logger error: #{inspect(e)}")
end
end
{:ok, state}
end
@impl true
def handle_event(_event, state) do
{:ok, state}
end
@impl true
def handle_call(:get_config, state) do
{:ok, state, state}
end
@impl true
def handle_call({:configure, new_config}, state) do
{:ok, :ok, Map.merge(state, new_config)}
end
@impl true
def handle_info(_msg, state) do
{:ok, state}
end
@impl true
def code_change(_old_vsn, state, _extra) do
{:ok, state}
end
@impl true
def terminate(_reason, _state) do
:ok
end
defp should_log?(_env, level, _configured_levels)
when level in [:error, :critical, :emergency, :alert],
do: true
defp should_log?(:prod, level, configured_levels) do
level in configured_levels
end
defp should_log?(_env, level, configured_levels) do
level in configured_levels
end
defp current_env do
case System.get_env("ARGOS_ENV") || Application.get_env(:argos, :env, "dev") do
"prod" -> :prod
"dev" -> :dev
"test" -> :test
:prod -> :prod
:dev -> :dev
:test -> :test
_ -> :dev
end
end
defp get_app_name do
case Application.get_env(:argos, :logger_app_name) do
nil ->
case Application.get_env(:argos, :logger) do
nil ->
:argos
module when is_atom(module) ->
module
|> Module.split()
|> List.first()
|> String.downcase()
|> String.to_atom()
_ ->
:argos
end
app_name ->
app_name
end
end
defp get_configured_levels(app_name, env) do
case Application.get_env(app_name, :logger_levels) do
nil ->
case env do
:prod -> [:error, :critical, :emergency, :alert]
_ -> [:debug, :info, :warning, :error, :critical, :emergency, :alert]
end
levels when is_list(levels) ->
error_levels = [:error, :critical, :emergency, :alert]
(levels ++ error_levels) |> Enum.uniq()
end
end
defp prepare_metadata(md, ts, level) do
base_md = [
timestamp: ts,
level: level,
handler_id: Argos.Logger
]
useful_md =
Enum.filter(md, fn
{:module, _} -> true
{:function, _} -> true
{:file, _} -> true
{:line, _} -> true
{:application, _} -> true
{:pid, _} -> true
_ -> false
end)
base_md ++ useful_md
end
defp extract_message(msg) when is_binary(msg), do: msg
defp extract_message({:string, chardata}), do: IO.chardata_to_string(chardata)
defp extract_message({:fmt, format, args}),
do: :io_lib.format(format, args) |> IO.chardata_to_string()
defp extract_message(other), do: inspect(other)
end