diff --git a/lib/tower_honeybadger/honeybadger/client.ex b/lib/tower_honeybadger/honeybadger/client.ex new file mode 100644 index 0000000..b5c000e --- /dev/null +++ b/lib/tower_honeybadger/honeybadger/client.ex @@ -0,0 +1,39 @@ +defmodule TowerHoneybadger.Honeybadger.Client do + @base_url "https://api.honeybadger.io/v1" + @api_key_header ~c"X-API-Key" + + def post(path, payload) when is_map(payload) do + case :httpc.request( + :post, + { + ~c"#{@base_url}#{path}", + [{@api_key_header, api_key()}], + ~c"application/json", + Jason.encode!(payload) + }, + [ + ssl: [ + verify: :verify_peer, + cacerts: :public_key.cacerts_get(), + # Support wildcard certificates + customize_hostname_check: [ + match_fun: :public_key.pkix_verify_hostname_match_fun(:https) + ] + ] + ], + [] + ) do + {:ok, result} -> + result + |> IO.inspect() + + {:error, reason} -> + reason + |> IO.inspect() + end + end + + defp api_key do + Application.fetch_env!(:tower_honeybadger, :api_key) + end +end diff --git a/lib/tower_honeybadger/honeybadger/notice.ex b/lib/tower_honeybadger/honeybadger/notice.ex new file mode 100644 index 0000000..76e0961 --- /dev/null +++ b/lib/tower_honeybadger/honeybadger/notice.ex @@ -0,0 +1,65 @@ +defmodule TowerHoneybadger.Honeybadger.Notice do + def from_exception(exception, stacktrace, options \\ []) + when is_exception(exception) and is_list(stacktrace) do + plug_conn = Keyword.get(options, :plug_conn) + + %{ + "error" => %{ + "class" => inspect(exception.__struct__), + "message" => Exception.message(exception), + "backtrace" => backtrace(stacktrace) + }, + "server" => %{ + "environment_name" => environment() + } + } + |> maybe_put_request_data(plug_conn) + end + + defp backtrace(stacktrace) do + stacktrace + |> Enum.map(fn {m, f, a, location} -> + backtrace_entry = %{ + "method" => Exception.format_mfa(m, f, a) + } + + backtrace_entry = + if location[:file] do + Map.put(backtrace_entry, "file", to_string(location[:file])) + else + backtrace_entry + end + + if location[:line] do + Map.put(backtrace_entry, "number", location[:line]) + else + backtrace_entry + end + end) + end + + defp maybe_put_request_data(notice, %Plug.Conn{} = conn) do + notice + |> Map.put("request", request_data(conn)) + end + + defp request_data(%Plug.Conn{} = conn) do + conn = + conn + |> Plug.Conn.fetch_cookies() + |> Plug.Conn.fetch_query_params() + + %{ + "url" => "#{conn.scheme}://#{conn.host}:#{conn.port}#{conn.request_path}", + "params" => + case conn.params do + %Plug.Conn.Unfetched{aspect: :params} -> "unfetched" + other -> other + end + } + end + + defp environment do + Application.fetch_env!(:tower_honeybadge, :environment) + end +end diff --git a/lib/tower_honeybadger/reporter.ex b/lib/tower_honeybadger/reporter.ex new file mode 100644 index 0000000..7807b2c --- /dev/null +++ b/lib/tower_honeybadger/reporter.ex @@ -0,0 +1,30 @@ +defmodule TowerHoneybadger.Reporter do + @behaviour Tower.Reporter + + alias TowerHoneybadger.Honeybadger + + @impl true + def report_exception(exception, stacktrace, metadata \\ %{}) + when is_exception(exception) and is_list(stacktrace) do + if enabled?() do + Honeybadger.Client.post( + "/notices", + Honeybadger.Notice.from_exception(exception, stacktrace, plug_conn: plug_conn(metadata)) + ) + else + IO.puts("TowerHoneybadger NOT enabled, ignoring exception report...") + end + end + + defp plug_conn(%{conn: conn}) do + conn + end + + defp plug_conn(_) do + nil + end + + defp enabled? do + Application.get_env(:tower_honeybadger, :enabled, false) + end +end diff --git a/mix.exs b/mix.exs index 6d8a5c8..63ca233 100644 --- a/mix.exs +++ b/mix.exs @@ -14,15 +14,16 @@ defmodule TowerHoneybadger.MixProject do # Run "mix help compile.app" to learn about applications. def application do [ - extra_applications: [:logger] + extra_applications: [:logger, :public_key] ] end # Run "mix help deps" to learn about dependencies. defp deps do [ - # {:dep_from_hexpm, "~> 0.3.0"}, - # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + {:jason, "~> 1.4"}, + {:tower, github: "mimiquate/tower"}, + {:plug, "~> 1.16"} ] end end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..6944878 --- /dev/null +++ b/mix.lock @@ -0,0 +1,8 @@ +%{ + "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, + "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, + "plug": {:hex, :plug, "1.16.0", "1d07d50cb9bb05097fdf187b31cf087c7297aafc3fed8299aac79c128a707e47", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cbf53aa1f5c4d758a7559c0bd6d59e286c2be0c6a1fac8cc3eee2f638243b93e"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "tower": {:git, "https://github.com/mimiquate/tower.git", "140a358d321ede1f3f2225341430143c2ec47d47", []}, +}