Current section

Files

Jump to
rollbax lib rollbax item.ex
Raw

lib/rollbax/item.ex

defmodule Rollbax.Item do
@moduledoc false
# This module is responsible for building the payload for a Rollbar "Item".
# Refer to https://rollbar.com/docs/api/items_post for documentation on such
# payload.
@spec draft(String.t() | nil, String.t() | nil, map) :: map
def draft(token, environment, custom)
when (is_binary(token) or is_nil(token)) and (is_binary(environment) or is_nil(environment)) and
is_map(custom) do
data = %{
"server" => %{
"host" => host()
},
"environment" => environment,
"language" => "Elixir v" <> System.version(),
"platform" => System.otp_release(),
"notifier" => notifier()
}
%{
"access_token" => token,
"data" => put_custom(data, custom)
}
end
@spec compose(map, {String.t(), pos_integer, map, map, map}) :: map
def compose(draft, {level, timestamp, body, custom, occurrence_data})
when is_map(draft) and is_binary(level) and is_integer(timestamp) and timestamp > 0 and
is_map(body) and is_map(custom) and is_map(occurrence_data) do
Map.update!(draft, "data", fn data ->
data
|> Map.merge(occurrence_data)
|> put_custom(custom)
|> Map.put("body", body)
|> Map.put("level", level)
|> Map.put("timestamp", timestamp)
end)
end
@doc """
Returns a map representing the body to be used for representing an "exception"
on Rollbar.
`class` and `message` are strings that will be used as the class and message
of the reported exception. `stacktrace` is the stacktrace of the error.
"""
@spec exception_body(String.t(), String.t(), [any]) :: map
def exception_body(class, message, stacktrace)
when is_binary(class) and is_binary(message) and is_list(stacktrace) do
%{
"trace" => %{
"frames" => stacktrace_to_frames(stacktrace),
"exception" => %{
"class" => class,
"message" => message
}
}
}
end
@doc """
Returns a map representing the body to be used for representing a "message" on
Rollbar.
"""
@spec message_body(String.t()) :: map
def message_body(message) when is_binary(message) do
%{"message" => %{"body" => message}}
end
@doc """
Returns the exception class and message for the given Elixir error.
`kind` can be one of `:throw`, `:exit`, or `:error`. A `{class, message}`
tuple is returned.
"""
@spec exception_class_and_message(:throw | :exit | :error, any) :: {String.t(), String.t()}
def exception_class_and_message(kind, value)
def exception_class_and_message(:throw, value) do
{"throw", inspect(value)}
end
def exception_class_and_message(:exit, value) do
message =
if Exception.exception?(value) do
Exception.format_banner(:error, value)
else
Exception.format_exit(value)
end
{"exit", message}
end
def exception_class_and_message(:error, error) do
exception = Exception.normalize(:error, error)
{inspect(exception.__struct__), Exception.message(exception)}
end
defp stacktrace_to_frames(stacktrace) do
Enum.map(stacktrace, &stacktrace_entry_to_frame/1)
end
defp stacktrace_entry_to_frame({module, fun, arity, location}) when is_integer(arity) do
method = Exception.format_mfa(module, fun, arity) <> maybe_format_application(module)
put_location(%{"method" => method}, location)
end
defp stacktrace_entry_to_frame({module, fun, arity, location}) when is_list(arity) do
method = Exception.format_mfa(module, fun, length(arity)) <> maybe_format_application(module)
args = Enum.map(arity, &inspect/1)
put_location(%{"method" => method, "args" => args}, location)
end
defp stacktrace_entry_to_frame({fun, arity, location}) when is_integer(arity) do
%{"method" => Exception.format_fa(fun, arity)}
|> put_location(location)
end
defp stacktrace_entry_to_frame({fun, arity, location}) when is_list(arity) do
%{"method" => Exception.format_fa(fun, length(arity)), "args" => Enum.map(arity, &inspect/1)}
|> put_location(location)
end
defp maybe_format_application(module) do
case :application.get_application(module) do
{:ok, application} ->
" (" <> Atom.to_string(application) <> ")"
:undefined ->
""
end
end
defp put_location(frame, location) do
if file = location[:file] do
frame = Map.put(frame, "filename", List.to_string(file))
if line = location[:line] do
Map.put(frame, "lineno", line)
else
frame
end
else
frame
end
end
defp put_custom(data, custom) do
if map_size(custom) == 0 do
data
else
Map.update(data, "custom", custom, &Map.merge(&1, custom))
end
end
defp host() do
{:ok, host} = :inet.gethostname()
List.to_string(host)
end
defp notifier() do
%{
"name" => "Rollbax",
"version" => unquote(Mix.Project.config()[:version])
}
end
end