defmodule ExRollbar do @moduledoc """ Error handling for RollBar. """ alias ExRollbar.RestAPI alias RestAPI.Payload.Data alias Data.Body @doc """ Sets up ExRollbar args must contain at least these keys: `access_token` `environment` args may also contain: * Person Object * `person_id`, (Required to use the Person object) * `person_email` * `person_username` * Custom Object * `custom` a (jsonable) map of custom data. * Logger * `enable_logger` (boolean) enable logger backend * Framework * `framework` IE "Nerves" * Code Version * `code_version` a git sha or similar. """ @spec setup(Keyword.t) :: :ok | {:error, term} def setup(args) do # Token token = Keyword.get(args, :access_token) || raise ArgumentError, "Please supply a token when setting up ExRollbar" Application.put_env(:ex_rollbar, :access_token, token) # Environment env = Keyword.get(args, :environment) || raise ArgumentError, "Please supply an environment when setting up ExRollbar" Application.put_env(:ex_rollbar, :environment, env) # Custom custom = Keyword.get(args, :custom, %{}) Application.put_env(:ex_rollbar, :custom, custom) code_version = Keyword.get(args, :code_version, %{}) Application.put_env(:ex_rollbar, :code_version, code_version) framework = Keyword.get(args, :framework) Application.put_env(:ex_rollbar, :framework, framework) # Person person_id = Keyword.get(args, :person_id) if person_id do Application.put_env(:ex_rollbar, :person_id, person_id) person_email = Keyword.get(args, :person_email) person_username = Keyword.get(args, :person_username) Application.put_env(:ex_rollbar, :person_email, person_email) Application.put_env(:ex_rollbar, :person_username, person_username) end # Logger if Keyword.get(args, :enable_logger, false) do Logger.add_backend(Logger.Backends.ExRollbar) end :ok end @doc false defdelegate uuid, to: UUID, as: :generate @doc """ Reports an item to RollBar. * `level` can be one of :error, :warn, :info, :debug * `value` can be a Exception struct or a string * `stack_trace` is the result of `System.stack_trace()` * `meta` is a bucket of extra key/value data. ## Meta Can contain any of: * `uuid` - a uuid (generated automatically) * `timstamp` - a unix timestamp (generated automatically) * `context` - a context object. * `custom` - any jsonable map of extra data * `request_meta` - A request object from the rollbar docs * `server_meta` - A server object from the rollbar docs * `client_meta` - A client object from the rollbar docs """ @spec report(Data.level, Exception.t | binary, Exception.stack_trace, Keyword.t) :: no_return def report(level, value, stack_trace, meta \\ []) do level = parse_level(level) %Data{ # will change frequently level: level, # Should exist, but could be user provided. uuid: Keyword.get(meta, :uuid, uuid()), timestamp: Keyword.get(meta, :timestamp, :os.system_time(:seconds)), context: Keyword.get(meta, :context), custom: get_custom(meta), # These should not change at runtime. environment: from_env(:environment), code_version: from_env(:code_version), framework: from_env(:framework), # Body. body: generate_body(value, stack_trace, meta), person: generate_person(), request: generate_request(Keyword.get(meta, :request_meta)), server: generate_server(Keyword.get(meta, :server_meta)), client: generate_client(Keyword.get(meta, :client_meta)) } |> RestAPI.emit() end defp get_custom(meta) do env_custom = from_env(:custom, %{}) meta_custom = Keyword.get(meta, :custom, %{}) Map.merge(env_custom, meta_custom) end defp parse_level(_), do: :error defp generate_body(value, stack_trace, metadata) defp generate_body(%{message: message}, stack_trace, meta), do: generate_body(message, stack_trace, meta) defp generate_body(value, [stack_item | _] = stack_trace, meta) do case stack_item do {module, _function, _arity, _file_and_line} -> %{ trace: %Body.Trace{ frames: Enum.map(stack_trace, fn(item) -> {item_module, item_function, _item_arity, item_file_and_line} = item %Body.Trace.Frame{ filename: fn() -> # FIXME thing = Keyword.get(item_file_and_line, :file) if thing do thing |> to_string() else raise(ArgumentError, "Could not find filename for: #{inspect value}") end end.(), lineno: Keyword.get(item_file_and_line, :line), method: item_function, class_name: item_module } end), exception: %Body.Trace.Exception{ class: module, message: value, description: Keyword.get(meta, :description) } } } _ -> generate_body(value, [], meta) end end defp generate_body(value, [], _m), do: %{message: %Body.Message{body: value}} @spec generate_person() :: map | nil defp generate_person() do id = from_env(:person_id) if id do %Data.Person{ id: id, username: from_env(:person_username), email: from_env(:person_email) } else nil end end @spec generate_request(nil | Keyword.t) :: map defp generate_request(nil), do: nil defp generate_request(_meta) do #TODO(Connor) Fixme %{} end @spec generate_server(nil | Keyword.t) :: map defp generate_server(nil), do: nil defp generate_server(_meta) do %{} end @spec generate_client(nil | Keyword.t) :: map defp generate_client(nil), do: nil defp generate_client(_client) do %{} end @spec from_env(atom, any) :: any defp from_env(key, default \\ nil) do Application.get_env(:ex_rollbar, key, default) end end