Packages
sentry
8.0.3
13.3.0
13.2.0
13.1.0
13.0.1
13.0.0
12.0.3
12.0.2
12.0.1
12.0.0
11.0.4
11.0.3
11.0.2
11.0.1
11.0.0
10.10.0
10.9.0
10.8.1
10.8.0
10.7.1
10.7.0
10.6.2
10.6.1
10.6.0
10.5.0
10.4.0
10.3.0
10.2.1
10.2.0
10.2.0-rc.2
10.2.0-rc.1
10.1.0
10.0.3
10.0.2
10.0.1
10.0.0
9.1.0
9.0.0
8.1.0
8.0.6
8.0.5
8.0.4
8.0.3
8.0.2
8.0.1
8.0.0
8.0.0-rc.2
8.0.0-rc.1
8.0.0-rc.0
retired
7.2.5
7.2.4
7.2.3
7.2.2
7.2.1
7.2.0
7.1.0
7.0.6
7.0.5
7.0.4
7.0.3
7.0.2
7.0.1
7.0.0
6.4.2
6.4.1
6.4.0
6.3.0
6.2.1
6.2.0
6.1.0
6.0.5
6.0.4
6.0.3
6.0.2
6.0.1
6.0.0
5.0.1
5.0.0
4.0.3
4.0.2
4.0.1
4.0.0
3.0.0
2.2.0
2.1.0
2.0.2
2.0.1
2.0.0
1.1.2
1.1.1
1.1.0
1.0.0
0.3.2
0.3.1
0.3.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
The Official Elixir client for Sentry
Current section
Files
Jump to
Current section
Files
lib/sentry/logger_backend.ex
defmodule Sentry.LoggerBackend do
@moduledoc """
Report Logger events like crashed processes to Sentry. To include in your
application, add this module to your Logger backends:
config :logger,
backends: [:console, Sentry.LoggerBackend]
Sentry context will be included in metadata in reported events. Example:
Sentry.Context.set_user_context(%{
user_id: current_user.id
})
## Configuration
* `:excluded_domains` - Any messages with a domain in the configured
list will not be sent. Defaults to `[:cowboy]` to avoid double reporting
events from `Sentry.PlugCapture`.
* `:metadata` - To include non-Sentry Logger metadata in reports, the
`:metadata` key can be set to a list of keys. Metadata under those keys will
be added in the `:extra` context under the `:logger_metadata` key. Defaults
to `[]`.
* `:level` - The minimum [Logger level](https://hexdocs.pm/logger/Logger.html#module-levels) to send events for.
Defaults to `:error`.
* `:capture_log_messages` - When `true`, this module will send all Logger
messages. Defaults to `false`, which will only send messages with metadata
that has the shape of an exception and stacktrace.
Example:
config :logger, Sentry.LoggerBackend,
# Also send warn messages
level: :warn,
# Send messages from Plug/Cowboy
excluded_domains: [],
# Include metadata added with `Logger.metadata([foo_bar: "value"])`
metadata: [:foo_bar],
# Send messages like `Logger.error("error")` to Sentry
capture_log_messages: true
"""
@behaviour :gen_event
defstruct level: :error, metadata: [], excluded_domains: [:cowboy], capture_log_messages: false
def init(__MODULE__) do
config = Application.get_env(:logger, __MODULE__, [])
{:ok, struct(%__MODULE__{}, config)}
end
def init({__MODULE__, opts}) when is_list(opts) do
config =
Application.get_env(:logger, __MODULE__, [])
|> Keyword.merge(opts)
{:ok, struct(%__MODULE__{}, config)}
end
def handle_call({:configure, options}, state) do
config =
Application.get_env(:logger, __MODULE__, [])
|> Keyword.merge(options)
Application.put_env(:logger, __MODULE__, config)
{:ok, :ok, struct(state, config)}
end
def handle_event({_level, gl, {Logger, _, _, _}}, state) when node(gl) != node() do
{:ok, state}
end
def handle_event({level, _gl, {Logger, msg, _ts, meta}}, state) do
if Logger.compare_levels(level, state.level) != :lt and
not excluded_domain?(meta[:domain], state) do
log(level, msg, meta, state)
end
{:ok, state}
end
def handle_event(_, state) do
{:ok, state}
end
def handle_info(_, state) do
{:ok, state}
end
def code_change(_old_vsn, state, _extra) do
{:ok, state}
end
def terminate(_reason, _state) do
:ok
end
defp log(level, msg, meta, state) do
sentry = meta[:sentry] || get_sentry_from_callers(meta[:callers] || [])
opts =
[
event_source: :logger,
level: elixir_logger_level_to_sentry_level(level),
extra: %{logger_metadata: logger_metadata(meta, state), logger_level: level},
result: :none
] ++ Map.to_list(sentry)
case meta[:crash_reason] do
{%_{__exception__: true} = exception, stacktrace} when is_list(stacktrace) ->
Sentry.capture_exception(exception, [stacktrace: stacktrace] ++ opts)
{other, stacktrace} when is_list(stacktrace) ->
Sentry.capture_exception(
Sentry.CrashError.exception(other),
[stacktrace: stacktrace] ++ opts
)
_ ->
if state.capture_log_messages do
try do
if is_binary(msg), do: msg, else: :unicode.characters_to_binary(msg)
rescue
_ -> :ok
else
msg -> Sentry.capture_message(msg, opts)
end
end
end
end
defp get_sentry_from_callers([head | tail]) when is_pid(head) do
with {:dictionary, [_ | _] = dictionary} <- :erlang.process_info(head, :dictionary),
%{sentry: sentry} <- dictionary[:"$logger_metadata$"] do
sentry
else
_ -> get_sentry_from_callers(tail)
end
end
defp get_sentry_from_callers(_), do: %{}
defp excluded_domain?([head | _], state), do: head in state.excluded_domains
defp excluded_domain?(_, _), do: false
defp logger_metadata(meta, state) do
for key <- state.metadata,
value = meta[key],
do: {key, value},
into: %{}
end
@spec elixir_logger_level_to_sentry_level(Logger.level()) :: String.t()
defp elixir_logger_level_to_sentry_level(level) do
case level do
:emergency ->
"fatal"
:alert ->
"fatal"
:critical ->
"fatal"
:error ->
"error"
:warning ->
"warning"
:warn ->
"warning"
:notice ->
"info"
:info ->
"info"
:debug ->
"debug"
end
end
end