Current section
Files
Jump to
Current section
Files
lib/slack_logger.ex
defmodule SlackLogger do
@moduledoc """
"""
@behaviour :gen_event
@default_format "$time $metadata[$level] $message\n"
@headers [{"Content-Type", "application/json"}]
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, {Logger, msg, ts, md}}, %{level: min_level, metadata_filter: metadata_filter} = state) do
if (is_nil(min_level) or Logger.compare_levels(level, min_level) != :lt) and metadata_matches?(md, metadata_filter) do
log_event(level, msg, ts, md, state)
else
{:ok, state}
end
end
def handle_event(:flush, state) do
{:ok, state}
end
defp log_event(_level, _msg, _ts, _md, %{url: nil} = state) do
{:ok, state}
end
defp log_event(level, msg, ts, md, %{url: url} = state) when is_binary(url) do
output = format_event(level, msg, ts, md, state)
post_log(url, output)
{:ok, state}
end
defp post_log(url, output) do
case Jason.encode(%{text: :binary.list_to_bin(output)}) do
{:ok, json} ->
case HTTPoison.post(url, json, @headers) do
{:ok, %HTTPoison.Response{body: "ok"}} -> true
{:error, reason} -> IO.inspect(reason)
end
{:error, reason} -> IO.inspect(reason)
end
end
defp configure(name, opts) do
state = %{name: nil, format: nil, level: nil, metadata: nil, metadata_filter: nil, url: 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)
level = Keyword.get(opts, :level)
metadata = Keyword.get(opts, :metadata, [])
format_opts = Keyword.get(opts, :format, @default_format)
format = Logger.Formatter.compile(format_opts)
metadata_filter = Keyword.get(opts, :metadata_filter)
url = Keyword.get(opts, :url)
%{state | name: name, format: format, level: level, metadata: metadata, metadata_filter: metadata_filter, url: url}
end
defp format_event(level, msg, ts, md, %{format: format, metadata: keys}) do
Logger.Formatter.format(format, level, msg, ts, take_metadata(md, keys))
end
@doc false
@spec metadata_matches?(Keyword.t, nil|Keyword.t) :: true|false
def metadata_matches?(_md, nil), do: true
def metadata_matches?(_md, []), do: true # all of the filter keys are present
def metadata_matches?(md, [{key, val}|rest]) do
case Keyword.fetch(md, key) do
{:ok, ^val} ->
metadata_matches?(md, rest)
_ -> false #fail on first mismatch
end
end
defp take_metadata(metadata, keys) do
metadatas = Enum.reduce(keys, [], fn key, acc ->
case Keyword.fetch(metadata, key) do
{:ok, val} -> [{key, val} | acc]
:error -> acc
end
end)
Enum.reverse(metadatas)
end
end