Packages
sentry
12.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_utils.ex
defmodule Sentry.LoggerUtils do
@moduledoc false
# Utilities that are shared between Sentry.LoggerHandler and Sentry.LoggerBackend, plus
# functions that wrap Elixir's Logger and that make sure we use the right options for Logger
# calls. For example, if we (the Sentry SDK) log without using `domain: [:sentry]`, then we can
# run into loops in which Sentry reports messages that the SDK itself logs.
alias Sentry.Config
require Logger
@spec build_sentry_options(
Logger.level(),
keyword() | nil,
map(),
[atom()] | :all,
[atom()]
) ::
keyword()
def build_sentry_options(level, sentry_context, meta, allowed_meta, tags_from_metadata) do
default_extra =
Map.merge(
%{logger_metadata: logger_metadata(meta, allowed_meta), logger_level: level},
Map.take(meta, [:domain])
)
default_tags = logger_tags_from_metadata(meta, tags_from_metadata)
(sentry_context || get_sentry_options_from_callers(meta[:callers]) || %{})
|> Map.new()
|> Map.update(:extra, default_extra, &Map.merge(&1, default_extra))
|> Map.update(:tags, default_tags, &Map.merge(&1, default_tags))
|> Map.merge(%{
event_source: :logger,
level: elixir_logger_level_to_sentry_level(level),
result: :none
})
|> Map.to_list()
end
# Always excludes the :sentry domain, to avoid infinite logging loops.
@spec excluded_domain?([atom()], [atom()]) :: boolean()
def excluded_domain?(logged_domains, excluded_domains) do
Enum.any?(logged_domains, &(&1 == :sentry or &1 in excluded_domains))
end
defp elixir_logger_level_to_sentry_level(level) do
case level do
:emergency -> :fatal
:alert -> :fatal
:critical -> :fatal
:error -> :error
:warning -> :warning
:notice -> :info
:info -> :info
:debug -> :debug
end
end
defp get_sentry_options_from_callers([caller | rest]) when is_pid(caller) do
logger_metadata_key = Sentry.Context.__logger_metadata_key__()
with {:current_node, true} <- {:current_node, node(caller) == Node.self()},
{:dictionary, [_ | _] = dictionary} <- :erlang.process_info(caller, :dictionary),
%{^logger_metadata_key => sentry} <- dictionary[:"$logger_metadata$"] do
sentry
else
_ -> get_sentry_options_from_callers(rest)
end
end
defp get_sentry_options_from_callers(_other) do
nil
end
defp logger_metadata(meta, allowed_meta) do
meta = Map.new(meta)
# Filter allowed meta.
meta =
case allowed_meta do
:all -> meta
allowed_meta -> Map.take(meta, allowed_meta)
end
# Potentially convert to iodata.
:maps.map(fn _key, val -> attempt_to_convert_iodata(val) end, meta)
end
defp logger_tags_from_metadata(meta, tags_from_metadata) do
for {key, value} <- meta,
key in tags_from_metadata,
is_binary(value),
into: %{} do
{key, value}
end
end
defp attempt_to_convert_iodata(list) when is_list(list) do
IO.chardata_to_string(list)
rescue
_exception -> list
else
str -> if String.printable?(str), do: str, else: list
end
defp attempt_to_convert_iodata(other) do
other
end
@spec log(iodata() | (-> iodata()), keyword()) :: :ok
def log(message_or_fun, meta \\ []) when is_list(meta) do
meta = Keyword.merge(meta, domain: [:sentry])
Logger.log(Config.log_level(), message_or_fun, meta)
end
end