From eef20b076239619358e2107ee5045423ae8e303a Mon Sep 17 00:00:00 2001 From: Stuart Page <38261603+stuartjohnpage@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:20:42 -0500 Subject: [PATCH 01/11] updated dependencies --- mix.exs | 10 ++++++---- mix.lock | 35 ++++++++++++++++++++++++----------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/mix.exs b/mix.exs index 75755c2..c169614 100644 --- a/mix.exs +++ b/mix.exs @@ -20,7 +20,7 @@ defmodule Rollbax.Mixfile do docs: [ main: "Rollbax", source_ref: "v#{@version}", - source_url: "https://github.com/elixir-addicts/rollbax", + source_url: "https://github.com/ForzaElixir/rollbax", extras: ["pages/Using Rollbax in Plug-based applications.md"] ] ] @@ -40,7 +40,9 @@ defmodule Rollbax.Mixfile do {:jason, "~> 1.0"}, {:ex_doc, "~> 0.18", only: :dev, runtime: false}, {:plug, "~> 1.4", only: :test}, - {:cowboy, "~> 1.1", only: :test} + {:cowboy, "~> 2.0", only: :test}, + {:plug_cowboy, "~> 2.0", only: :test}, + {:telemetry, "~> 1.2", only: :test} ] end @@ -48,13 +50,13 @@ defmodule Rollbax.Mixfile do [ maintainers: ["Aleksei Magusev", "Andrea Leopardi", "Eric Meadows-Jönsson"], licenses: ["ISC"], - links: %{"GitHub" => "https://github.com/elixir-addicts/rollbax"} + links: %{"GitHub" => "https://github.com/ForzaElixir/rollbax"} ] end defp env() do [ - enabled: true, + enabled: false, custom: %{}, api_endpoint: @default_api_endpoint, enable_crash_reports: false, diff --git a/mix.lock b/mix.lock index 9a4ebb8..3169e47 100644 --- a/mix.lock +++ b/mix.lock @@ -1,13 +1,26 @@ %{ - "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "f4763bbe08233eceed6f24bc4fcc8d71c17cfeafa6439157c57349aa1bb4f17c"}, - "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm", "db622da03aa039e6366ab953e31186cc8190d32905e33788a1acb22744e6abd2"}, - "earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [:mix], [], "hexpm", "1b34655872366414f69dd987cb121c049f76984b6ac69f52fff6d8fd64d29cfd"}, - "ex_doc": {:hex, :ex_doc, "0.18.1", "37c69d2ef62f24928c1f4fdc7c724ea04aecfdf500c4329185f8e3649c915baf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm", "f050061c87ad39478c942995b5a20c40f2c0bc06525404613b8b0474cb8bd796"}, - "hackney": {:hex, :hackney, "1.3.2", "43bd07ab88753f5e136e38fddd2a09124bee25733b03361eeb459d0173fc17ab", [:make, :rebar], [{:idna, "~> 1.0.2", [hex: :idna, repo: "hexpm", optional: false]}, {:ssl_verify_hostname, "~> 1.0.5", [hex: :ssl_verify_hostname, repo: "hexpm", optional: false]}], "hexpm", "9b811cff637b29f9c7e2c61abf01986c85cd4f64a9422315fd803993b4e82615"}, - "idna": {:hex, :idna, "1.0.3", "d456a8761cad91c97e9788c27002eb3b773adaf5c893275fc35ba4e3434bbd9b", [:rebar3], [], "hexpm", "357d489a51112db4f216034406834f9172b3c0ff5a12f83fb28b25ca271541d1"}, - "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, - "mime": {:hex, :mime, "1.0.1", "05c393850524767d13a53627df71beeebb016205eb43bfbd92d14d24ec7a1b51", [:mix], [], "hexpm", "8aad5eef6d9d20899918868b10e79fc2dafe72a79102882c2947999c10b30cd9"}, - "plug": {:hex, :plug, "1.4.3", "236d77ce7bf3e3a2668dc0d32a9b6f1f9b1f05361019946aae49874904be4aed", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm", "c1c408c57a1e4c88c365b9aff1198c350e22b765dbb97a460e9e6bd9364c6194"}, - "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm", "6e56493a862433fccc3aca3025c946d6720d8eedf6e3e6fb911952a7071c357f"}, - "ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.6", "45866d958d9ae51cfe8fef0050ab8054d25cba23ace43b88046092aa2c714645", [:make], [], "hexpm", "72b2fc8a8e23d77eed4441137fefa491bbf4a6dc52e9c0045f3f8e92e66243b5"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, + "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, + "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"}, + "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, + "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, + "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [: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", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } From 3f2b8bac81b9d3088ff58d2dc1dbf29f07ce3fc5 Mon Sep 17 00:00:00 2001 From: Stuart Page <38261603+stuartjohnpage@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:21:41 -0500 Subject: [PATCH 02/11] formatting changes and warning fixes --- config/config.exs | 2 +- lib/rollbax/client.ex | 2 +- lib/rollbax/item.ex | 2 +- test/rollbax_test.exs | 16 ++++++++-------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/config/config.exs b/config/config.exs index 0a57a74..4855a39 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,4 +1,4 @@ -use Mix.Config +import Config config :ex_unit, assert_receive_timeout: 800, diff --git a/lib/rollbax/client.ex b/lib/rollbax/client.ex index e069e88..37a58e5 100644 --- a/lib/rollbax/client.ex +++ b/lib/rollbax/client.ex @@ -52,7 +52,7 @@ defmodule Rollbax.Client do event = {Atom.to_string(level), timestamp, body, custom, occurrence_data} GenServer.cast(pid, {:emit, event}) else - Logger.warn( + Logger.warning( "(Rollbax) Trying to report an exception but the :rollbax application has not been started", rollbax: false ) diff --git a/lib/rollbax/item.ex b/lib/rollbax/item.ex index 1ad737b..4b04648 100644 --- a/lib/rollbax/item.ex +++ b/lib/rollbax/item.ex @@ -84,7 +84,7 @@ defmodule Rollbax.Item do def exception_class_and_message(:exit, value) do message = - if Exception.exception?(value) do + if is_exception(value) do Exception.format_banner(:error, value) else Exception.format_exit(value) diff --git a/test/rollbax_test.exs b/test/rollbax_test.exs index 9d07d62..ef2993f 100644 --- a/test/rollbax_test.exs +++ b/test/rollbax_test.exs @@ -16,7 +16,7 @@ defmodule RollbaxTest do describe "report/5" do test "with an error" do - stacktrace = [{Test, :report, 2, [file: 'file.exs', line: 16]}] + stacktrace = [{Test, :report, 2, [file: ~c"file.exs", line: 16]}] exception = RuntimeError.exception("pass") :ok = Rollbax.report(:error, exception, stacktrace, %{}, %{uuid: "d4c7"}) @@ -41,7 +41,7 @@ defmodule RollbaxTest do end test "with an error that is not an exception" do - stacktrace = [{Test, :report, 2, [file: 'file.exs', line: 16]}] + stacktrace = [{Test, :report, 2, [file: ~c"file.exs", line: 16]}] error = {:badmap, nil} :ok = Rollbax.report(:error, error, stacktrace, %{}, %{}) @@ -50,7 +50,7 @@ defmodule RollbaxTest do end test "with an exit" do - stacktrace = [{Test, :report, 2, [file: 'file.exs', line: 16]}] + stacktrace = [{Test, :report, 2, [file: ~c"file.exs", line: 16]}] :ok = Rollbax.report(:exit, :oops, stacktrace) assert %{ @@ -72,7 +72,7 @@ defmodule RollbaxTest do end test "with an exit where the term is an exception" do - stacktrace = [{Test, :report, 2, [file: 'file.exs', line: 16]}] + stacktrace = [{Test, :report, 2, [file: ~c"file.exs", line: 16]}] exception = try do @@ -88,7 +88,7 @@ defmodule RollbaxTest do end test "with a throw" do - stacktrace = [{Test, :report, 2, [file: 'file.exs', line: 16]}] + stacktrace = [{Test, :report, 2, [file: ~c"file.exs", line: 16]}] :ok = Rollbax.report(:throw, :oops, stacktrace) assert %{ @@ -112,9 +112,9 @@ defmodule RollbaxTest do test "includes stacktraces in the function name if there's an application" do # Let's use some modules that belong to an application and some that don't. stacktrace = [ - {:crypto, :strong_rand_bytes, 1, [file: 'crypto.erl', line: 1]}, - {List, :to_string, 1, [file: 'list.ex', line: 10]}, - {NoApp, :for_this_module, 3, [file: 'nofile.ex', line: 1]} + {:crypto, :strong_rand_bytes, 1, [file: ~c"crypto.erl", line: 1]}, + {List, :to_string, 1, [file: ~c"list.ex", line: 10]}, + {NoApp, :for_this_module, 3, [file: ~c"nofile.ex", line: 1]} ] :ok = Rollbax.report(:throw, :oops, stacktrace) From 7884ae8e378fe254b9a719e36f0f76b0fda14176 Mon Sep 17 00:00:00 2001 From: Stuart Page <38261603+stuartjohnpage@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:22:26 -0500 Subject: [PATCH 03/11] Main change: changing from :error_logger to :logger --- lib/rollbax.ex | 11 +- lib/rollbax/logger.ex | 113 ---------------- lib/rollbax/logger_handler.ex | 74 +++++++++++ lib/rollbax/reporter/standard.ex | 219 +++++++++++++++++-------------- 4 files changed, 203 insertions(+), 214 deletions(-) delete mode 100644 lib/rollbax/logger.ex create mode 100644 lib/rollbax/logger_handler.ex diff --git a/lib/rollbax.ex b/lib/rollbax.ex index 012c305..84795af 100644 --- a/lib/rollbax.ex +++ b/lib/rollbax.ex @@ -35,9 +35,9 @@ defmodule Rollbax do * `:api_endpoint` - (binary) the Rollbar endpoint to report exceptions and messages to. Defaults to `https://api.rollbar.com/api/1/item/`. - * `:enable_crash_reports` - see `Rollbax.Logger`. + * `:enable_crash_reports` - see `Rollbax.LoggerHandler`. - * `:reporters` - see `Rollbax.Logger`. + * `:reporters` - see `Rollbax.LoggerHandler`. * `:proxy` - (binary) a proxy that can be used to connect to the Rollbar host. For more information about the format of the proxy, check the proxy URL description in the @@ -68,6 +68,7 @@ defmodule Rollbax do """ use Application + require Logger @allowed_message_levels [:critical, :error, :warning, :info, :debug] @@ -84,9 +85,9 @@ defmodule Rollbax do end if config[:enable_crash_reports] do - # We do this because the handler will read `:reporters` out of the app's environment. - Application.put_env(:rollbax, :reporters, config[:reporters]) - :error_logger.add_report_handler(Rollbax.Logger) + :logger.add_handler(:rollbax_handler, Rollbax.LoggerHandler, %{ + config: %{reporters: config[:reporters]} + }) end children = [ diff --git a/lib/rollbax/logger.ex b/lib/rollbax/logger.ex deleted file mode 100644 index dcf163b..0000000 --- a/lib/rollbax/logger.ex +++ /dev/null @@ -1,113 +0,0 @@ -defmodule Rollbax.Logger do - @moduledoc """ - A module that can be used to report crashes and exits to Rollbar. - - In Elixir and Erlang, crashes from GenServers and other processes are reported through - `:error_logger`. When installed, this module installs an `:error_logger` handler that can be - used to report such crashes to Rollbar automatically. - - In order to use this functionality, you must configure the `:rollbax` application to report - crashes with: - - config :rollbax, :enable_crash_reports, true - - All the configuration options for reporting crashes are documented in detail below. If you are - upgrading from an older version of Rollbax that used this module as a logger backend via - `config :logger, backends: [:console, Rollbax.Logger]` this config should be removed. - - `Rollbax.Logger` implements a mechanism of reporting based on *reporters*, which are modules - that implement the `Rollbax.Reporter` behaviour. Every message received by `Rollbax.Logger` is - run through a list of reporters and the behaviour is determined by the return value of each - reporter's `c:Rollbax.Reporter.handle_event/2` callback: - - * when the callback returns a `Rollbax.Exception` struct, the exception is reported to Rollbar - and no other reporters are called - - * when the callback returns `:next`, the reporter is skipped and Rollbax moves on to the next - reporter - - * when the callback returns `:ignore`, the reported message is ignored and no more reporters - are tried. - - The list of reporters can be configured in the `:reporters` key in the `:rollbax` application - configuration. By default this list only contains `Rollbax.Reporter.Standard` (see its - documentation for more information). Rollbax also comes equipped with a - `Rollbax.Reporter.Silencing` reporter that doesn't report anything it receives. For examples on - how to provide your own reporters, look at the source for `Rollbax.Reporter.Standard`. - - ## Configuration - - The following reporting-related options can be used to configure the `:rollbax` application: - - * `:enable_crash_reports` (boolean) - when `true`, `Rollbax.Logger` is registered as an - `:error_logger` handler and the whole reporting flow described above is executed. - - * `:reporters` (list) - a list of modules implementing the `Rollbax.Reporter` behaviour. - Defaults to `[Rollbax.Reporter.Standard]`. - - """ - - @behaviour :gen_event - - defstruct [:reporters] - - @doc false - def init(_args) do - reporters = Application.get_env(:rollbax, :reporters, [Rollbax.Reporter.Standard]) - {:ok, %__MODULE__{reporters: reporters}} - end - - @doc false - def handle_event(event, state) - - # If the event is on a different node than the current node, we ignore it. - def handle_event({_level, gl, _event}, state) - when node(gl) != node() do - {:ok, state} - end - - def handle_event({level, _gl, event}, %__MODULE__{reporters: reporters} = state) do - :ok = run_reporters(reporters, level, event) - {:ok, state} - end - - @doc false - def handle_call(request, _state) do - exit({:bad_call, request}) - end - - @doc false - def handle_info(_message, state) do - {:ok, state} - end - - @doc false - def terminate(_reason, _state) do - :ok - end - - @doc false - def code_change(_old_vsn, state, _extra) do - {:ok, state} - end - - defp run_reporters([reporter | rest], level, event) do - case reporter.handle_event(level, event) do - %Rollbax.Exception{} = exception -> - Rollbax.report_exception(exception) - - :next -> - run_reporters(rest, level, event) - - :ignore -> - :ok - end - end - - # If no reporter ignored or reported this event, then we're gonna report this - # as a Rollbar "message" with the same logic that Logger uses to translate - # messages (so that it will have Elixir syntax when reported). - defp run_reporters([], _level, _event) do - :ok - end -end diff --git a/lib/rollbax/logger_handler.ex b/lib/rollbax/logger_handler.ex new file mode 100644 index 0000000..9632a4c --- /dev/null +++ b/lib/rollbax/logger_handler.ex @@ -0,0 +1,74 @@ +defmodule Rollbax.LoggerHandler do + @moduledoc """ + More information on this can be found in the documentation for `Logger.Handler` + + 1. This module can be added as a `:logger` handler + 2. Log events come in to the `log/2` callback + 3. It checks the configured reporters + 4. Events are passed to the reporters for possible submission to Rollbar + 5. Depending on the response, the event is either reported to Rollbar, the event is passed to the next reporter, or the event is ignored + + This allows customizing and processing of events via reporters before sending to Rollbar. + """ + + @doc """ + Handle a log message + """ + def log(event, %{config: %{reporters: reporters}}) when is_list(reporters) do + run_reporters(reporters, event) + end + + @doc """ + Handle initialization by making sure we have a valid config + """ + def adding_handler(config) do + {:ok, initialize_config(config)} + end + + @doc """ + Handle updated config by making sure the internal portion is valid + """ + def changing_config(:update, _old_config, new_config) do + {:ok, initialize_config(new_config)} + end + + @doc """ + Create a valid Rollbax.LoggerHandler config by filling in any missing options + """ + def initialize_config(existing) do + config = + existing + |> Map.get(:config, %{}) + |> Map.put_new(:reporters, [Rollbax.Reporter.Standard]) + + existing + |> Map.put(:config, config) + |> Map.put_new(:initialized, true) + end + + defp run_reporters([reporter | rest], %{level: level, meta: meta, msg: {:string, msg}} = event) do + case reporter.handle_event(level, {Logger, msg, meta[:time], meta}) do + %Rollbax.Exception{} = exception -> + Rollbax.report_exception(exception) + + :next -> + run_reporters(rest, event) + + :ignore -> + :ok + end + end + + defp run_reporters([_ | _], event) do + # remove or convert to a Logger message after this has been tested extensively + IO.inspect(event, label: "UNHANDLED EVENT SHAPE") + :error + end + + # If no reporter ignored or reported this event, then we're gonna report this + # as a Rollbar "message" with the same logic that Logger uses to translate + # messages (so that it will have Elixir syntax when reported). + defp run_reporters([], _event) do + :ok + end +end diff --git a/lib/rollbax/reporter/standard.ex b/lib/rollbax/reporter/standard.ex index 8857395..af7a858 100644 --- a/lib/rollbax/reporter/standard.ex +++ b/lib/rollbax/reporter/standard.ex @@ -6,154 +6,181 @@ defmodule Rollbax.Reporter.Standard do @behaviour Rollbax.Reporter - def handle_event(:error, {_pid, format, data}) do - handle_error_format(format, data) + def handle_event(:error, {_Logger, msg, _timestamp, meta}) do + msg + |> process_error() + |> format_exception(meta) end - def handle_event(_type, _event) do + def handle_event(_level, _event) do :next end - # Errors in a GenServer. - defp handle_error_format('** Generic server ' ++ _, [name, last_message, state, reason]) do - {class, message, stacktrace} = format_as_exception(reason, "GenServer terminating") + # Handles exceptions raised by GenServer callbacks + defp format_exception( + ["GenServer ", pid, " terminating", details | last_message_parts] = msg, + meta + ) do + last_message = parse_last_message(last_message_parts) + {class, message} = parse_exception(meta[:crash_reason], details, msg) %Rollbax.Exception{ - class: class, + class: full_class("GenServer terminating", class), message: message, - stacktrace: stacktrace, + stacktrace: stacktrace(meta[:crash_reason]), custom: %{ - "name" => inspect(name), - "last_message" => inspect(last_message), - "state" => inspect(state) + "name" => pid, + "last_message" => last_message } } end - # Errors in a GenEvent handler. - defp handle_error_format('** gen_event handler ' ++ _, [ - name, - manager, - last_message, - state, - reason - ]) do - {class, message, stacktrace} = format_as_exception(reason, "gen_event handler terminating") + # Handles exceptions raised by GenEvent handlers + defp format_exception( + [ + ":gen_event handler ", + module, + " installed in ", + _pid, + " terminating", + details | last_message_parts + ] = msg, + meta + ) do + last_message = parse_last_message(last_message_parts) + {class, message} = parse_exception(meta[:crash_reason], details, msg) %Rollbax.Exception{ - class: class, + class: full_class("gen_event handler terminating", class), message: message, - stacktrace: stacktrace, + stacktrace: stacktrace(meta[:crash_reason]), custom: %{ - "name" => inspect(name), - "manager" => inspect(manager), - "last_message" => inspect(last_message), - "state" => inspect(state) + "name" => module, + "manager" => inspect(meta[:pid]), + "last_message" => last_message } } end - # Errors in a task. - defp handle_error_format('** Task ' ++ _, [name, starter, function, arguments, reason]) do - {class, message, stacktrace} = format_as_exception(reason, "Task terminating") + # Handles exceptions raised by Task callbacks + defp format_exception( + ["Task " <> _ = _error, details | function_details] = msg, + meta + ) do + {function, args} = parse_function_and_args(function_details) + {class, message} = parse_exception(meta[:crash_reason], details, msg) %Rollbax.Exception{ - class: class, + class: full_class("Task terminating", class), message: message, - stacktrace: stacktrace, + stacktrace: stacktrace(meta[:crash_reason]), custom: %{ - "name" => inspect(name), - "started_from" => inspect(starter), - "function" => inspect(function), - "arguments" => inspect(arguments) + "name" => inspect(meta[:pid]), + "started_from" => inspect(hd(meta[:callers] || [])), + "function" => function, + "arguments" => args } } end - defp handle_error_format('** State machine ' ++ _ = message, data) do - if charlist_contains?(message, 'Callback mode') do - :next - else - handle_gen_fsm_error(data) - end - end + # Handles exceptions raised by processes + defp format_exception( + ["Process ", pid, " raised an exception" | error_details], + meta + ) do + {message, name} = + case meta[:crash_reason] do + {exception, stacktrace} when is_exception(exception) and is_list(stacktrace) -> + {Exception.message(exception), inspect(exception.__struct__)} - # Errors in a regular process. - defp handle_error_format('Error in process ' ++ _, [pid, {reason, stacktrace}]) do - exception = Exception.normalize(:error, reason) + _ -> + {IO.iodata_to_binary(error_details), "Unknown Exception"} + end %Rollbax.Exception{ - class: "error in process (#{inspect(exception.__struct__)})", - message: Exception.message(exception), - stacktrace: stacktrace, + class: "error in process (#{name})", + message: message, + stacktrace: stacktrace(meta[:crash_reason]), custom: %{ - "pid" => inspect(pid) + "pid" => pid } } end - # Any other error (for example, the ones logged through - # :error_logger.error_msg/1). This reporter doesn't report those to Rollbar. - defp handle_error_format(_format, _data) do + # ignore other messages, such as those logged directly by the application + defp format_exception(_msg, _meta) do :next end - defp handle_gen_fsm_error([name, last_event, state, data, reason]) do - {class, message, stacktrace} = format_as_exception(reason, "State machine terminating") - - %Rollbax.Exception{ - class: class, - message: message, - stacktrace: stacktrace, - custom: %{ - "name" => inspect(name), - "last_event" => inspect(last_event), - "state" => inspect(state), - "data" => inspect(data) - } - } + # Plug errors seem to have an extra level of nesting that we need to remove before + # parsing the message. + defp process_error(error) when is_list(hd(error)) do + hd(error) end - defp handle_gen_fsm_error(_data) do - :next + defp process_error(error) do + error end - defp format_as_exception({maybe_exception, [_ | _] = maybe_stacktrace} = reason, class) do - # We do this &Exception.format_stacktrace_entry/1 dance just to ensure that - # "maybe_stacktrace" is a valid stacktrace. If it's not, - # Exception.format_stacktrace_entry/1 will raise an error and we'll treat it - # as not a stacktrace. - try do - Enum.each(maybe_stacktrace, &Exception.format_stacktrace_entry/1) - catch - :error, _ -> - format_stop_as_exception(reason, class) - else - :ok -> - format_error_as_exception(maybe_exception, maybe_stacktrace, class) - end + defp stacktrace({_, trace} = _crash_reason) when is_list(trace), do: trace + defp stacktrace(_crash_reason), do: nil + + defp exception_name(%{__struct__: struct}), do: inspect(struct) + defp exception_name(_), do: "Unknown" + + defp parse_last_message(["\nLast message", _list, ": " | rest] = _message_parts) do + IO.iodata_to_binary(rest) + rescue + _ -> nil end - defp format_as_exception(reason, class) do - format_stop_as_exception(reason, class) + defp parse_last_message(["\nLast message: ", message] = _message_parts) do + message end - defp format_stop_as_exception(reason, class) do - {class <> " (stop)", Exception.format_exit(reason), _stacktrace = []} + defp parse_last_message([_first | rest] = _message_parts), + do: parse_last_message(rest) + + defp parse_last_message(_), do: nil + + defp parse_function_and_args(["\nFunction: " <> function, args | _rest] = _message_parts) do + parsed_args = String.replace(args, ~r/^\s*Args: /, "") + {function, parsed_args} + rescue + _ -> {nil, nil} end - defp format_error_as_exception(reason, stacktrace, class) do - case Exception.normalize(:error, reason, stacktrace) do - %ErlangError{} -> - {class, Exception.format_exit(reason), stacktrace} + defp parse_function_and_args([_first | rest] = _message_parts), + do: parse_function_and_args(rest) + + defp parse_function_and_args(_), do: {nil, nil} + + defp parse_exception({exception, stacktrace} = _crash_reason, _error_details, _full_msg) + when is_list(stacktrace) and is_exception(exception) do + { + exception_name(exception), + Exception.message(exception) + } + end - exception -> - class = class <> " (" <> inspect(exception.__struct__) <> ")" - {class, Exception.message(exception), stacktrace} - end + defp parse_exception({:stop_reason, []} = _crash_reason, _error_details, full_msg) do + { + "stop", + IO.iodata_to_binary(full_msg) + } end - defp charlist_contains?(charlist, part) do - :string.str(charlist, part) != 0 + defp parse_exception(_crash_reason, error_details, _full_msg) do + exception_message = + case error_details do + [[_prefix | message] | _] -> message + _ -> "Unknown Exception" + end + + {"", exception_message} end + + defp full_class(base, "" = _suffix), do: base + + defp full_class(base, suffix), do: "#{base} (#{suffix})" end From 776746bc78627e15f8ec23d2018297a44f3151ed Mon Sep 17 00:00:00 2001 From: Stuart Page <38261603+stuartjohnpage@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:22:36 -0500 Subject: [PATCH 04/11] Documentation and test changes --- lib/rollbax/reporter.ex | 18 +++++++++-- pages/The Rollbax Logger Handler.md | 47 +++++++++++++++++++++++++++++ test/rollbax/logger_test.exs | 25 +++++---------- test/test_helper.exs | 18 ++++++----- 4 files changed, 80 insertions(+), 28 deletions(-) create mode 100644 pages/The Rollbax Logger Handler.md diff --git a/lib/rollbax/reporter.ex b/lib/rollbax/reporter.ex index 0e8d8c2..3633e01 100644 --- a/lib/rollbax/reporter.ex +++ b/lib/rollbax/reporter.ex @@ -1,7 +1,21 @@ defmodule Rollbax.Reporter do @moduledoc """ - Behaviour to be implemented by Rollbax reporters that wish to report `:error_logger` messages to - Rollbar. See `Rollbax.Logger` for more information. + Behaviour to be implemented by Rollbax reporters that wish to report `:logger` messages to + Rollbar. See `Rollbax.LoggerHandler` for more information. + + The Event shape is taken from the 'logger_backends' package as specified here: https://github.com/elixir-lang/logger_backends/blob/master/lib/logger_backend.ex#L15 + + The handler should be designed to handle the following events: + + - {level, group_leader, {Logger, message, timestamp, metadata}} where: + - level is one of :debug, :info, :warn, or :error, as previously described (for compatibility with pre 1.10 backends the :notice will be translated to :info and all messages above :error will be translated to :error) + - group_leader is the group leader of the process which logged the message + - {Logger, message, timestamp, metadata} is a tuple containing information about the logged message: + - the first element is always the atom Logger + - message is the actual message (as chardata) + - timestamp is the timestamp for when the message was logged, as a {{year, month, day}, {hour, minute, second, millisecond}} tuple + - metadata is a keyword list of metadata used when logging the message + - : flush """ @callback handle_event(type :: term, event :: term) :: Rollbax.Exception.t() | :next | :ignore diff --git a/pages/The Rollbax Logger Handler.md b/pages/The Rollbax Logger Handler.md new file mode 100644 index 0000000..544866e --- /dev/null +++ b/pages/The Rollbax Logger Handler.md @@ -0,0 +1,47 @@ + ### Rollbax Logger Handler + + An Erlang :logger handler that passes log events to Rollbax reporters for sending to Rollbar. + + ### Why + + In Elixir and Erlang, crashes and exits from GenServers and other processes are reported through + the `:logger` module. This module can be added as a `:logger` handler to process logged events. It will check the configured + `:reporters` and send any relevant events to those reporters for submission to Rollbar. + + The reporters implement the `Rollbax.Reporter` behaviour. Every message received by + `Rollbax.LoggerHandler` is run through the list of reporters and the behaviour is determined by + the return value of each reporter's `Rollbax.Reporter.handle_event/2` callback: + + - When the callback returns a `Rollbax.Exception` struct, the exception is reported to Rollbar + and no other reporters are called + + - When the callback returns `:next`, the reporter is skipped and it moves on to the next reporter + + - When the callback returns `:ignore`, the reported message is ignored and no more reporters are + tried + + ### Key Points: + + - To use this, You must configure the `:rollbax` application with: `config :rollbax, :enable_crash_reports, true` + + - The `:reporters` can also be configured, defaulting to `[Rollbax.Reporter.Standard]`. + + - It requires the `:reporters` option to be configured, which should be a list of module names that implement the `Rollbax.Reporter` behaviour. + + - On initialization, it will validate the provided configuration. + + - On config updates, it will re-validate the configuration to make sure Rollbax can still function properly. + + - The `log/2` callback handles the actual logging event by checking the `:reporters` config and running each one. + + ### Summary + + 1. This module can be added as a `:logger` handler + 2. Log events come in to the `log/2` callback + 3. It checks the configured reporters + 4. Events are passed to the reporters for possible submission to Rollbar + 5. Depending on the response, the event is either reported to Rollbar, the event is passed to the next reporter, or the event is ignored + + This allows customizing and processing of events via reporters before sending to Rollbar. + + More information on configuring reporters can be found in the `Rollbax` module docs. \ No newline at end of file diff --git a/test/rollbax/logger_test.exs b/test/rollbax/logger_test.exs index db44c33..16307eb 100644 --- a/test/rollbax/logger_test.exs +++ b/test/rollbax/logger_test.exs @@ -12,17 +12,13 @@ defmodule Rollbax.LoggerTest do setup context do {:ok, _pid} = RollbarAPI.start(self()) - if reporters = context[:reporters] do - Application.put_env(:rollbax, :reporters, reporters) - else - Application.delete_env(:rollbax, :reporters) - end + config = if context[:reporters], do: %{config: %{reporters: context[:reporters]}}, else: %{} - :error_logger.add_report_handler(Rollbax.Logger) + :logger.add_handler(:rollbax_handler, Rollbax.LoggerHandler, config) on_exit(fn -> RollbarAPI.stop() - :error_logger.delete_report_handler(Rollbax.Logger) + :logger.remove_handler(:rollbax_handler) end) end @@ -56,7 +52,6 @@ defmodule Rollbax.LoggerTest do assert data["custom"]["last_message"] =~ "$gen_cast" assert data["custom"]["name"] == inspect(gen_server) - assert data["custom"]["state"] == "{}" after purge_module(MyGenServer) end @@ -91,7 +86,6 @@ defmodule Rollbax.LoggerTest do assert data["custom"]["last_message"] =~ "$gen_cast" assert data["custom"]["name"] == inspect(gen_server) - assert data["custom"]["state"] == "{}" after purge_module(MyGenServer) end @@ -126,7 +120,6 @@ defmodule Rollbax.LoggerTest do assert data["custom"]["last_message"] =~ "$gen_cast" assert data["custom"]["name"] == inspect(gen_server) - assert data["custom"]["state"] == "{}" after purge_module(MyGenServer) end @@ -151,16 +144,13 @@ defmodule Rollbax.LoggerTest do data = assert_performed_request()["data"] # Check the exception. - assert data["body"]["trace"]["exception"] == %{ - "class" => "GenServer terminating (stop)", - "message" => ":stop_reason" - } + assert data["body"]["trace"]["exception"]["class"] == "GenServer terminating (stop)" + assert data["body"]["trace"]["exception"]["message"] =~ ":stop_reason" assert data["body"]["trace"]["frames"] == [] - assert data["custom"]["last_message"] =~ "$gen_cast" + assert data["custom"]["name"] == inspect(gen_server) - assert data["custom"]["state"] == "{}" after purge_module(MyGenServer) end @@ -201,8 +191,7 @@ defmodule Rollbax.LoggerTest do assert data["custom"] == %{ "name" => "MyGenEventHandler", "manager" => inspect(manager), - "last_message" => ":raise_error", - "state" => "{}" + "last_message" => ":raise_error" } end) after diff --git a/test/test_helper.exs b/test/test_helper.exs index 475a68d..97fcce0 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -18,13 +18,14 @@ defmodule ExUnit.RollbaxCase do api_endpoint \\ "http://localhost:4004", proxy \\ nil ) do - Rollbax.Client.start_link( - api_endpoint: api_endpoint, - access_token: token, - environment: env, - enabled: true, - custom: custom, - proxy: proxy + start_supervised( + {Rollbax.Client, + api_endpoint: api_endpoint, + access_token: token, + environment: env, + enabled: true, + custom: custom, + proxy: proxy} ) end @@ -52,11 +53,12 @@ end defmodule RollbarAPI do alias Plug.Conn - alias Plug.Adapters.Cowboy + alias Plug.Cowboy import Conn def start(pid, port \\ 4004) do + Application.ensure_all_started(:telemetry) Cowboy.http(__MODULE__, [test: pid], port: port) end From 1825b9e1c1c239686e40fd9c31620047c7dd8b67 Mon Sep 17 00:00:00 2001 From: Stuart Page <38261603+stuartjohnpage@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:45:06 -0500 Subject: [PATCH 05/11] adds a test for supervised task termination --- test/rollbax/logger_test.exs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/rollbax/logger_test.exs b/test/rollbax/logger_test.exs index 16307eb..2056874 100644 --- a/test/rollbax/logger_test.exs +++ b/test/rollbax/logger_test.exs @@ -269,6 +269,31 @@ defmodule Rollbax.LoggerTest do purge_module(MyModule) end + test "captures supervised task termination" do + defmodule TestSupervisor do + use Supervisor + + def start_link(_) do + Supervisor.start_link(__MODULE__, :ok, name: __MODULE__) + end + + def init(:ok) do + children = [ + {Task, fn -> raise "boom" end} + ] + Supervisor.init(children, strategy: :one_for_one) + end + end + + capture_log(fn -> + {:ok, _pid} = TestSupervisor.start_link([]) + data = assert_performed_request()["data"] + assert data["body"]["trace"]["exception"] == %{"class" => "Task terminating (RuntimeError)", "message" => "boom"} + end) + after + purge_module(TestSupervisor) + end + if List.to_integer(:erlang.system_info(:otp_release)) < 19 do test "gen_fsm terminating" do defmodule Elixir.MyGenFsm do From 071345b5188276b46bb33531b84adea37d80eefd Mon Sep 17 00:00:00 2001 From: Stuart Page <38261603+stuartjohnpage@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:50:12 -0500 Subject: [PATCH 06/11] removes unncessary function: events always follow format --- lib/rollbax/logger_handler.ex | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/rollbax/logger_handler.ex b/lib/rollbax/logger_handler.ex index 9632a4c..1473051 100644 --- a/lib/rollbax/logger_handler.ex +++ b/lib/rollbax/logger_handler.ex @@ -59,12 +59,6 @@ defmodule Rollbax.LoggerHandler do end end - defp run_reporters([_ | _], event) do - # remove or convert to a Logger message after this has been tested extensively - IO.inspect(event, label: "UNHANDLED EVENT SHAPE") - :error - end - # If no reporter ignored or reported this event, then we're gonna report this # as a Rollbar "message" with the same logic that Logger uses to translate # messages (so that it will have Elixir syntax when reported). From 748acc0ef8851e36d97440b05680ae5d0f2f4c16 Mon Sep 17 00:00:00 2001 From: Stuart Page <38261603+stuartjohnpage@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:50:35 -0500 Subject: [PATCH 07/11] formatting --- test/rollbax/logger_test.exs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/rollbax/logger_test.exs b/test/rollbax/logger_test.exs index 2056874..3dbec97 100644 --- a/test/rollbax/logger_test.exs +++ b/test/rollbax/logger_test.exs @@ -281,6 +281,7 @@ defmodule Rollbax.LoggerTest do children = [ {Task, fn -> raise "boom" end} ] + Supervisor.init(children, strategy: :one_for_one) end end @@ -288,7 +289,11 @@ defmodule Rollbax.LoggerTest do capture_log(fn -> {:ok, _pid} = TestSupervisor.start_link([]) data = assert_performed_request()["data"] - assert data["body"]["trace"]["exception"] == %{"class" => "Task terminating (RuntimeError)", "message" => "boom"} + + assert data["body"]["trace"]["exception"] == %{ + "class" => "Task terminating (RuntimeError)", + "message" => "boom" + } end) after purge_module(TestSupervisor) From abe3adfd3d0cddf95cfe23fc0fefe9e7e7b26bcd Mon Sep 17 00:00:00 2001 From: Stuart Page <38261603+stuartjohnpage@users.noreply.github.com> Date: Wed, 24 Jul 2024 12:54:38 -0500 Subject: [PATCH 08/11] renamed function; changed to pattern matching --- lib/rollbax/reporter/standard.ex | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/rollbax/reporter/standard.ex b/lib/rollbax/reporter/standard.ex index af7a858..aabeae5 100644 --- a/lib/rollbax/reporter/standard.ex +++ b/lib/rollbax/reporter/standard.ex @@ -8,7 +8,7 @@ defmodule Rollbax.Reporter.Standard do def handle_event(:error, {_Logger, msg, _timestamp, meta}) do msg - |> process_error() + |> unwrap_message() |> format_exception(meta) end @@ -112,14 +112,15 @@ defmodule Rollbax.Reporter.Standard do :next end - # Plug errors seem to have an extra level of nesting that we need to remove before - # parsing the message. - defp process_error(error) when is_list(hd(error)) do - hd(error) + # Sometimes the main message is in a list with extraneous information + # and we need to parse the message out of it. + + defp unwrap_message([message | _rest]) when is_list(message) do + message end - defp process_error(error) do - error + defp unwrap_message(message) do + message end defp stacktrace({_, trace} = _crash_reason) when is_list(trace), do: trace From a1c15deb37c859ef496cf89deb06770e82356a7f Mon Sep 17 00:00:00 2001 From: Stuart Page <38261603+stuartjohnpage@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:04:31 -0500 Subject: [PATCH 09/11] formatting --- lib/rollbax.ex | 6 +++--- lib/rollbax/client.ex | 30 +++++++++++++----------------- lib/rollbax/item.ex | 20 ++++++++------------ lib/rollbax/logger_handler.ex | 8 ++++++++ lib/rollbax/reporter/standard.ex | 30 ++++++------------------------ test/test_helper.exs | 23 ++++++----------------- 6 files changed, 44 insertions(+), 73 deletions(-) diff --git a/lib/rollbax.ex b/lib/rollbax.ex index 84795af..37ad1c5 100644 --- a/lib/rollbax.ex +++ b/lib/rollbax.ex @@ -68,6 +68,7 @@ defmodule Rollbax do """ use Application + require Logger @allowed_message_levels [:critical, :error, :warning, :info, :debug] @@ -97,7 +98,7 @@ defmodule Rollbax do Supervisor.start_link(children, strategy: :one_for_one) end - defp init_config() do + defp init_config do env = Application.get_all_env(:rollbax) config = @@ -189,8 +190,7 @@ defmodule Rollbax do """ @spec report(:error | :exit | :throw, any, [any], map, map) :: :ok def report(kind, value, stacktrace, custom \\ %{}, occurrence_data \\ %{}) - when kind in [:error, :exit, :throw] and is_list(stacktrace) and is_map(custom) and - is_map(occurrence_data) do + when kind in [:error, :exit, :throw] and is_list(stacktrace) and is_map(custom) and is_map(occurrence_data) do {class, message} = Rollbax.Item.exception_class_and_message(kind, value) report_exception(%Rollbax.Exception{ diff --git a/lib/rollbax/client.ex b/lib/rollbax/client.ex index 37a58e5..ef72d8c 100644 --- a/lib/rollbax/client.ex +++ b/lib/rollbax/client.ex @@ -1,18 +1,19 @@ defmodule Rollbax.Client do - @moduledoc false + @moduledoc """ + This GenServer keeps a pre-built bare-bones version of an exception (a + "draft") to be reported to Rollbar, which is then filled with the data + related to each specific exception when such exception is being + reported. This GenServer is also responsible for actually sending data to + the Rollbar API and receiving responses from said API. - # This GenServer keeps a pre-built bare-bones version of an exception (a - # "draft") to be reported to Rollbar, which is then filled with the data - # related to each specific exception when such exception is being - # reported. This GenServer is also responsible for actually sending data to - # the Rollbar API and receiving responses from said API. + """ use GenServer - require Logger - alias Rollbax.Item + require Logger + @name __MODULE__ @hackney_pool __MODULE__ @headers [{"content-type", "application/json"}] @@ -46,8 +47,8 @@ defmodule Rollbax.Client do end def emit(level, timestamp, body, custom, occurrence_data) - when is_atom(level) and is_integer(timestamp) and timestamp > 0 and is_map(body) and - is_map(custom) and is_map(occurrence_data) do + when is_atom(level) and is_integer(timestamp) and timestamp > 0 and is_map(body) and is_map(custom) and + is_map(occurrence_data) do if pid = Process.whereis(@name) do event = {Atom.to_string(level), timestamp, body, custom, occurrence_data} GenServer.cast(pid, {:emit, event}) @@ -162,11 +163,7 @@ defmodule Rollbax.Client do %{state | hackney_responses: Map.delete(responses, ref)} end - defp handle_hackney_response( - ref, - {:status, code, description}, - %{hackney_responses: responses} = state - ) do + defp handle_hackney_response(ref, {:status, code, description}, %{hackney_responses: responses} = state) do if code != 200 do Logger.error("(Rollbax) unexpected API status: #{code}/#{description}") end @@ -187,8 +184,7 @@ defmodule Rollbax.Client do end end - defp handle_hackney_response(ref, body_chunk, %{hackney_responses: responses} = state) - when is_binary(body_chunk) do + defp handle_hackney_response(ref, body_chunk, %{hackney_responses: responses} = state) when is_binary(body_chunk) do responses = Map.update!(responses, ref, fn {code, body} -> {code, [body | body_chunk]} diff --git a/lib/rollbax/item.ex b/lib/rollbax/item.ex index 4b04648..fa51cee 100644 --- a/lib/rollbax/item.ex +++ b/lib/rollbax/item.ex @@ -7,8 +7,7 @@ defmodule Rollbax.Item do @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 + 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() @@ -27,8 +26,8 @@ defmodule Rollbax.Item do @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 + 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) @@ -47,8 +46,7 @@ defmodule Rollbax.Item do 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 + 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), @@ -114,13 +112,11 @@ defmodule Rollbax.Item do end defp stacktrace_entry_to_frame({fun, arity, location}) when is_integer(arity) do - %{"method" => Exception.format_fa(fun, arity)} - |> put_location(location) + put_location(%{"method" => Exception.format_fa(fun, arity)}, 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) + put_location(%{"method" => Exception.format_fa(fun, length(arity)), "args" => Enum.map(arity, &inspect/1)}, location) end defp maybe_format_application(module) do @@ -155,12 +151,12 @@ defmodule Rollbax.Item do end end - defp host() do + defp host do {:ok, host} = :inet.gethostname() List.to_string(host) end - defp notifier() do + defp notifier do %{ "name" => "Rollbax", "version" => unquote(Mix.Project.config()[:version]) diff --git a/lib/rollbax/logger_handler.ex b/lib/rollbax/logger_handler.ex index 1473051..1f08d84 100644 --- a/lib/rollbax/logger_handler.ex +++ b/lib/rollbax/logger_handler.ex @@ -11,6 +11,8 @@ defmodule Rollbax.LoggerHandler do This allows customizing and processing of events via reporters before sending to Rollbar. """ + require Logger + @doc """ Handle a log message """ @@ -59,6 +61,12 @@ defmodule Rollbax.LoggerHandler do end end + defp run_reporters([_ | _], event) do + # I think all messages will have a the format of {:string, msg} as defined above: but just in case + Logger.error("Rollbax.LoggerHandler: Unexpected event format: #{inspect(event)}") + :ok + end + # If no reporter ignored or reported this event, then we're gonna report this # as a Rollbar "message" with the same logic that Logger uses to translate # messages (so that it will have Elixir syntax when reported). diff --git a/lib/rollbax/reporter/standard.ex b/lib/rollbax/reporter/standard.ex index aabeae5..0706279 100644 --- a/lib/rollbax/reporter/standard.ex +++ b/lib/rollbax/reporter/standard.ex @@ -17,10 +17,7 @@ defmodule Rollbax.Reporter.Standard do end # Handles exceptions raised by GenServer callbacks - defp format_exception( - ["GenServer ", pid, " terminating", details | last_message_parts] = msg, - meta - ) do + defp format_exception(["GenServer ", pid, " terminating", details | last_message_parts] = msg, meta) do last_message = parse_last_message(last_message_parts) {class, message} = parse_exception(meta[:crash_reason], details, msg) @@ -37,14 +34,7 @@ defmodule Rollbax.Reporter.Standard do # Handles exceptions raised by GenEvent handlers defp format_exception( - [ - ":gen_event handler ", - module, - " installed in ", - _pid, - " terminating", - details | last_message_parts - ] = msg, + [":gen_event handler ", module, " installed in ", _pid, " terminating", details | last_message_parts] = msg, meta ) do last_message = parse_last_message(last_message_parts) @@ -63,10 +53,7 @@ defmodule Rollbax.Reporter.Standard do end # Handles exceptions raised by Task callbacks - defp format_exception( - ["Task " <> _ = _error, details | function_details] = msg, - meta - ) do + defp format_exception(["Task " <> _ = _error, details | function_details] = msg, meta) do {function, args} = parse_function_and_args(function_details) {class, message} = parse_exception(meta[:crash_reason], details, msg) @@ -84,10 +71,7 @@ defmodule Rollbax.Reporter.Standard do end # Handles exceptions raised by processes - defp format_exception( - ["Process ", pid, " raised an exception" | error_details], - meta - ) do + defp format_exception(["Process ", pid, " raised an exception" | error_details], meta) do {message, name} = case meta[:crash_reason] do {exception, stacktrace} when is_exception(exception) and is_list(stacktrace) -> @@ -139,8 +123,7 @@ defmodule Rollbax.Reporter.Standard do message end - defp parse_last_message([_first | rest] = _message_parts), - do: parse_last_message(rest) + defp parse_last_message([_first | rest] = _message_parts), do: parse_last_message(rest) defp parse_last_message(_), do: nil @@ -151,8 +134,7 @@ defmodule Rollbax.Reporter.Standard do _ -> {nil, nil} end - defp parse_function_and_args([_first | rest] = _message_parts), - do: parse_function_and_args(rest) + defp parse_function_and_args([_first | rest] = _message_parts), do: parse_function_and_args(rest) defp parse_function_and_args(_), do: {nil, nil} diff --git a/test/test_helper.exs b/test/test_helper.exs index 97fcce0..719eebb 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -11,21 +11,10 @@ defmodule ExUnit.RollbaxCase do end end - def start_rollbax_client( - token, - env, - custom \\ %{}, - api_endpoint \\ "http://localhost:4004", - proxy \\ nil - ) do + def start_rollbax_client(token, env, custom \\ %{}, api_endpoint \\ "http://localhost:4004", proxy \\ nil) do start_supervised( {Rollbax.Client, - api_endpoint: api_endpoint, - access_token: token, - environment: env, - enabled: true, - custom: custom, - proxy: proxy} + api_endpoint: api_endpoint, access_token: token, environment: env, enabled: true, custom: custom, proxy: proxy} ) end @@ -45,24 +34,24 @@ defmodule ExUnit.RollbaxCase do end) end - def assert_performed_request() do + def assert_performed_request do assert_receive {:api_request, body} Jason.decode!(body) end end defmodule RollbarAPI do + import Conn + alias Plug.Conn alias Plug.Cowboy - import Conn - def start(pid, port \\ 4004) do Application.ensure_all_started(:telemetry) Cowboy.http(__MODULE__, [test: pid], port: port) end - def stop() do + def stop do Process.sleep(100) Cowboy.shutdown(__MODULE__.HTTP) Process.sleep(100) From 93639ba058621ffced99eb9b9934f596e84ab188 Mon Sep 17 00:00:00 2001 From: Stuart Page <38261603+stuartjohnpage@users.noreply.github.com> Date: Thu, 1 Aug 2024 11:04:10 -0500 Subject: [PATCH 10/11] fixed tests --- test/test_helper.exs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/test_helper.exs b/test/test_helper.exs index 719eebb..3c58cbe 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -41,9 +41,7 @@ defmodule ExUnit.RollbaxCase do end defmodule RollbarAPI do - import Conn - - alias Plug.Conn + import Plug.Conn alias Plug.Cowboy def start(pid, port \\ 4004) do @@ -61,7 +59,7 @@ defmodule RollbarAPI do Keyword.fetch!(opts, :test) end - def call(%Conn{method: "POST"} = conn, test) do + def call(%Plug.Conn{method: "POST"} = conn, test) do {:ok, body, conn} = read_body(conn) Process.sleep(30) send(test, {:api_request, body}) From ba05067ac7b128e3a208cdace9a8904e0956236d Mon Sep 17 00:00:00 2001 From: Stuart Page <38261603+stuartjohnpage@users.noreply.github.com> Date: Thu, 1 Aug 2024 11:05:25 -0500 Subject: [PATCH 11/11] formatting --- lib/rollbax.ex | 3 ++- lib/rollbax/client.ex | 12 +++++++++--- lib/rollbax/item.ex | 17 +++++++++++++---- lib/rollbax/reporter/standard.ex | 17 ++++++++++++++--- test/test_helper.exs | 15 +++++++++++++-- 5 files changed, 51 insertions(+), 13 deletions(-) diff --git a/lib/rollbax.ex b/lib/rollbax.ex index 37ad1c5..7ce29a3 100644 --- a/lib/rollbax.ex +++ b/lib/rollbax.ex @@ -190,7 +190,8 @@ defmodule Rollbax do """ @spec report(:error | :exit | :throw, any, [any], map, map) :: :ok def report(kind, value, stacktrace, custom \\ %{}, occurrence_data \\ %{}) - when kind in [:error, :exit, :throw] and is_list(stacktrace) and is_map(custom) and is_map(occurrence_data) do + when kind in [:error, :exit, :throw] and is_list(stacktrace) and is_map(custom) and + is_map(occurrence_data) do {class, message} = Rollbax.Item.exception_class_and_message(kind, value) report_exception(%Rollbax.Exception{ diff --git a/lib/rollbax/client.ex b/lib/rollbax/client.ex index ef72d8c..634e300 100644 --- a/lib/rollbax/client.ex +++ b/lib/rollbax/client.ex @@ -47,7 +47,8 @@ defmodule Rollbax.Client do end def emit(level, timestamp, body, custom, occurrence_data) - when is_atom(level) and is_integer(timestamp) and timestamp > 0 and is_map(body) and is_map(custom) and + when is_atom(level) and is_integer(timestamp) and timestamp > 0 and is_map(body) and + is_map(custom) and is_map(occurrence_data) do if pid = Process.whereis(@name) do event = {Atom.to_string(level), timestamp, body, custom, occurrence_data} @@ -163,7 +164,11 @@ defmodule Rollbax.Client do %{state | hackney_responses: Map.delete(responses, ref)} end - defp handle_hackney_response(ref, {:status, code, description}, %{hackney_responses: responses} = state) do + defp handle_hackney_response( + ref, + {:status, code, description}, + %{hackney_responses: responses} = state + ) do if code != 200 do Logger.error("(Rollbax) unexpected API status: #{code}/#{description}") end @@ -184,7 +189,8 @@ defmodule Rollbax.Client do end end - defp handle_hackney_response(ref, body_chunk, %{hackney_responses: responses} = state) when is_binary(body_chunk) do + defp handle_hackney_response(ref, body_chunk, %{hackney_responses: responses} = state) + when is_binary(body_chunk) do responses = Map.update!(responses, ref, fn {code, body} -> {code, [body | body_chunk]} diff --git a/lib/rollbax/item.ex b/lib/rollbax/item.ex index fa51cee..60ad226 100644 --- a/lib/rollbax/item.ex +++ b/lib/rollbax/item.ex @@ -7,7 +7,8 @@ defmodule Rollbax.Item do @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 + 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() @@ -26,7 +27,8 @@ defmodule Rollbax.Item do @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 + 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 @@ -46,7 +48,8 @@ defmodule Rollbax.Item do 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 + 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), @@ -116,7 +119,13 @@ defmodule Rollbax.Item do end defp stacktrace_entry_to_frame({fun, arity, location}) when is_list(arity) do - put_location(%{"method" => Exception.format_fa(fun, length(arity)), "args" => Enum.map(arity, &inspect/1)}, location) + put_location( + %{ + "method" => Exception.format_fa(fun, length(arity)), + "args" => Enum.map(arity, &inspect/1) + }, + location + ) end defp maybe_format_application(module) do diff --git a/lib/rollbax/reporter/standard.ex b/lib/rollbax/reporter/standard.ex index 0706279..08f835e 100644 --- a/lib/rollbax/reporter/standard.ex +++ b/lib/rollbax/reporter/standard.ex @@ -17,7 +17,10 @@ defmodule Rollbax.Reporter.Standard do end # Handles exceptions raised by GenServer callbacks - defp format_exception(["GenServer ", pid, " terminating", details | last_message_parts] = msg, meta) do + defp format_exception( + ["GenServer ", pid, " terminating", details | last_message_parts] = msg, + meta + ) do last_message = parse_last_message(last_message_parts) {class, message} = parse_exception(meta[:crash_reason], details, msg) @@ -34,7 +37,14 @@ defmodule Rollbax.Reporter.Standard do # Handles exceptions raised by GenEvent handlers defp format_exception( - [":gen_event handler ", module, " installed in ", _pid, " terminating", details | last_message_parts] = msg, + [ + ":gen_event handler ", + module, + " installed in ", + _pid, + " terminating", + details | last_message_parts + ] = msg, meta ) do last_message = parse_last_message(last_message_parts) @@ -134,7 +144,8 @@ defmodule Rollbax.Reporter.Standard do _ -> {nil, nil} end - defp parse_function_and_args([_first | rest] = _message_parts), do: parse_function_and_args(rest) + defp parse_function_and_args([_first | rest] = _message_parts), + do: parse_function_and_args(rest) defp parse_function_and_args(_), do: {nil, nil} diff --git a/test/test_helper.exs b/test/test_helper.exs index 3c58cbe..d5cc07d 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -11,10 +11,21 @@ defmodule ExUnit.RollbaxCase do end end - def start_rollbax_client(token, env, custom \\ %{}, api_endpoint \\ "http://localhost:4004", proxy \\ nil) do + def start_rollbax_client( + token, + env, + custom \\ %{}, + api_endpoint \\ "http://localhost:4004", + proxy \\ nil + ) do start_supervised( {Rollbax.Client, - api_endpoint: api_endpoint, access_token: token, environment: env, enabled: true, custom: custom, proxy: proxy} + api_endpoint: api_endpoint, + access_token: token, + environment: env, + enabled: true, + custom: custom, + proxy: proxy} ) end