Current section
Files
Jump to
Current section
Files
lib/sentry_logger_backend.ex
defmodule SentryLoggerBackend do
@moduledoc """
Provides a `Logger` backend for Sentry. This will automatically
submit Error level Logger events to Sentry.
### Configuration
Simply add the following to your config:
config :logger, backends: [:console, SentryLoggerBackend]
To set the level threshold:
config :logger, SentryLoggerBackend, level: :error
"""
use GenEvent
defstruct level: :error
def init(__MODULE__) do
{:ok, configure([])}
end
def handle_call({:configure, opts}, state) do
{:ok, :ok, configure(opts, state)}
end
def handle_event({_level, gl, _event}, state) when node(gl) != node() do
# Ignore non-local
{:ok, state}
end
def handle_event({level, _, {Logger, msg, _timestamp, metadata}}, state = %{level: min_level}) do
if meet_level?(level, min_level) && !metadata[:skip_sentry] do
msg = to_string(msg)
opts = case {is_otp_crash(msg), Keyword.pop(metadata, :fingerprint)} do
{false, {nil, remaining}} ->
[ level: normalise_level(level),
extra: process_metadata(remaining) ]
{true, {nil, remaining}} ->
[ level: normalise_level(level),
fingerprint: extract_fingerprint_from_otp_crash(msg),
extra: process_metadata(remaining) ]
{_, {fingerprint, remaining}} ->
[ level: normalise_level(level),
fingerprint: Enum.map(fingerprint, &to_string/1),
extra: process_metadata(remaining) ]
end
Sentry.capture_message(msg, opts)
end
{:ok, state}
end
def handle_event(_data, state) do
{:ok, state}
end
defp configure(opts, state \\ %__MODULE__{}) do
config =
Application.get_env(:logger, __MODULE__, [])
|> Keyword.merge(opts)
Application.put_env(:logger, __MODULE__, config)
%__MODULE__{state | level: config[:level] || :error}
end
defp process_metadata(metadata) do
metadata
|> Enum.map(&stringify_values/1)
|> Enum.into(Map.new)
end
defp meet_level?(lvl, min) do
Logger.compare_levels(lvl, min) != :lt
end
defp is_otp_crash(msg) do
String.starts_with? msg, "Error in process"
end
defp extract_fingerprint_from_otp_crash(msg) do
Regex.run ~r/file: [^\]]*/, msg
end
# Avoid quote marks around string vals, but otherwise inspect
defp stringify_values({k, v}) when is_binary(v), do: {k, v}
defp stringify_values({k, v}), do: {k, inspect(v)}
# Sentry doesn't understand :warn
defp normalise_level(:warn), do: :warning
defp normalise_level(other), do: other
end