Current section
Files
Jump to
Current section
Files
lib/logger_syslog_backend.ex
defmodule LoggerSyslogBackend do
@moduledoc false
@behaviour :gen_event
use Bitwise
@default_format "[$level] $levelpad$metadata $message"
def init({__MODULE__, name}) do
{:ok, configure(name, [])}
end
def handle_call({:configure, opts}, %{name: name} = state) do
{:ok, :ok, configure(name, opts, state)}
end
def handle_event({_level, gl, _event}, state) when node(gl) != node() do
{:ok, state}
end
def handle_event({level, _gl, {Logger, msg, ts, md}}, %{level: min_level} = state) do
if is_nil(min_level) or Logger.compare_levels(level, min_level) != :lt do
log_event(level, msg, ts, md, state)
end
{:ok, state}
end
def handle_event(:flush, state) do
{:ok, state}
end
def handle_info({:io_reply, _ref, _}, state) do
{:ok, state}
end
## Helpers
defp configure(name, opts) do
state = %{name: nil, format: nil, level: nil, metadata: nil, socket: nil, facility: nil, app_id: nil, path: nil}
configure(name, opts, state)
end
defp configure(name, opts, state) do
env = Application.get_env(:logger, name, [])
opts = Keyword.merge(env, opts)
Application.put_env(:logger, name, opts)
format = Keyword.get(opts, :format, @default_format) |> Logger.Formatter.compile
level = Keyword.get(opts, :level)
metadata = Keyword.get(opts, :metadata, [])
facility = Keyword.get(opts, :facility, :local2) |> facility_code
app_id = Keyword.get(opts, :app_id)
path = Keyword.get_lazy(opts, :path, fn -> default_path() end) |> IO.iodata_to_binary |> String.to_charlist
%{state | format: format, metadata: metadata, level: level, facility: facility, path: path, app_id: app_id}
end
defp default_path do
case :os.type do
{:unix, :darwin} -> "/var/run/syslog"
{:unix, _} -> "/dev/log"
end
end
defp log_event(_level, _msg, _ts, _md, %{path: nil} = state) do
{:ok, state}
end
defp log_event(level, msg, ts, md, %{path: path, socket: nil} = state) when is_list(path) do
case open_socket(path) do
{:ok, socket} -> log_event(level, msg, ts, md, %{state | socket: socket})
_other -> {:ok, state}
end
end
defp log_event(level, msg, ts, md, state) do
ansidata = format_event(level, msg, ts, md, state)
%{facility: facility, app_id: app_id, socket: socket, path: path} = state
app_id = app_id || Application.get_application(md[:module] || :elixir)
pre = :io_lib.format('<~B>~s ~s ~p: ', [facility ||| severity(level), timestamp(ts), app_id, self()])
:gen_udp.send(socket, {:local, path}, 0, [pre, ansidata, ?\n])
end
defp open_socket(_path) do
:gen_udp.open(0, [:local])
end
defp format_event(level, msg, ts, md, %{format: format, metadata: metadata}) do
Logger.Formatter.format(format, level, msg, ts, Keyword.take(md, metadata))
end
defp severity(:debug), do: 7
defp severity(:info), do: 6
defp severity(:warn), do: 4
defp severity(:error), do: 3
defp facility_code(:local0), do: (16 <<< 3)
defp facility_code(:local1), do: (17 <<< 3)
defp facility_code(:local2), do: (18 <<< 3)
defp facility_code(:local3), do: (19 <<< 3)
defp facility_code(:local4), do: (20 <<< 3)
defp facility_code(:local5), do: (21 <<< 3)
defp facility_code(:local6), do: (22 <<< 3)
defp facility_code(:local7), do: (23 <<< 3)
def timestamp({{_year,month,date},{hour,minute,second,_}}) do
mstr = elem({"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}, month - 1)
:io_lib.format("~s ~2..0B ~2..0B:~2..0B:~2..0B", [mstr, date, hour, minute, second])
end
end