diff --git a/.credo.exs b/.credo.exs index 1ceea908..a8a8212a 100644 --- a/.credo.exs +++ b/.credo.exs @@ -106,10 +106,6 @@ {Credo.Check.Warning.BoolOperationOnSameValues}, {Credo.Check.Warning.IExPry}, {Credo.Check.Warning.IoInspect}, - {Credo.Check.Warning.NameRedeclarationByAssignment}, - {Credo.Check.Warning.NameRedeclarationByCase}, - {Credo.Check.Warning.NameRedeclarationByDef}, - {Credo.Check.Warning.NameRedeclarationByFn}, {Credo.Check.Warning.OperationOnSameValues}, {Credo.Check.Warning.OperationWithConstantResult}, {Credo.Check.Warning.UnusedEnumOperation}, diff --git a/.travis.yml b/.travis.yml index b4cbed23..74fc718d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,30 +1,18 @@ language: elixir elixir: - - 1.3.4 - - 1.4 - - 1.5 - - 1.6 + - 1.7 otp_release: - - 18.3 - - 19.3 - 20.2 + - 21.0 env: - STRICT=true - STRICT=false matrix: exclude: - - elixir: 1.6 - env: STRICT=false - - elixir: 1.3.4 - env: STRICT=true - - elixir: 1.4 + - otp_release: 20.2 env: STRICT=true - - elixir: 1.5 - env: STRICT=true - - elixir: 1.6 - otp_release: 18.3 - - elixir: 1.3.4 - otp_release: 20.2 + - otp_release: 21.0 + env: STRICT=false notifications: email: - mitch@rokkincat.com @@ -32,7 +20,7 @@ script: - if [ "$STRICT" = "true" ]; then mix compile --warnings-as-errors; fi - mix test - mix credo - - if [ "$STRICT" = "true" ]; then mix dialyzer; fi + - if [ "$STRICT" = "true" ]; then travis_wait mix dialyzer; fi - if [ "$STRICT" = "true" ]; then mix format --dry-run --check-formatted; fi cache: directories: diff --git a/CHANGELOG.md b/CHANGELOG.md index adff0645..54af3a33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ ## master +## 7.0.0 (2018-09-05) + +* Enhancements + * Replace Poison with configurable JSON library + * Implement `Sentry.LoggerBackend` + +* Breaking Changes + * Require Elixir 1.7+ + * Remove `Sentry.Logger` + ## 6.4.2 (2018-09-05) * Enhancements diff --git a/README.md b/README.md index 0d06852e..0990c146 100644 --- a/README.md +++ b/README.md @@ -7,17 +7,25 @@ The Official Sentry Client for Elixir which provides a simple API to capture exc [Documentation](https://hexdocs.pm/sentry/readme.html) +## Note on upgrading from Sentry 6.x to 7.x + +Elixir 1.7 and Erlang/OTP 21 significantly changed how errors are transmitted (See "Erlang/OTP logger integration" [here](https://elixir-lang.org/blog/2018/07/25/elixir-v1-7-0-released/)). Sentry integrated heavily with Erlang's `:error_logger` module, but it is no longer the suggested path towards handling errors. + +Sentry 7.x requires Elixir 1.7 and Sentry 6.x will be maintained for applications running prior versions. Documentation for Sentry 6.x can be found [here](https://hexdocs.pm/sentry/6.4.2/readme.html). + +If you would like to upgrade a project to use Sentry 7.x, see [here](https://gist.github.com/mitchellhenke/4ab6dd8d0ebeaaf9821fb625e0037a4d). + ## Installation -To use Sentry with your projects, edit your mix.exs file to add it as a dependency and add the `:sentry` package to your applications: +To use Sentry with your projects, edit your mix.exs file and add it as a dependency. Sentry does not install a JSON library itself, and requires users to have one available. Sentry will default to trying to use Jason for JSON operations, but can be configured to use other ones. ```elixir -defp application do - [applications: [:sentry, :logger]] -end - defp deps do - [{:sentry, "~> 6.4"}] + [ + # ... + {:sentry, "~> 7.0"}, + {:jason, "~> 1.1"}, + ] end ``` @@ -41,9 +49,9 @@ More information on why this may be necessary can be found here: https://github. ### Capture Crashed Process Exceptions -This library comes with an extension to capture all error messages that the Plug handler might not. This is based on the Erlang [error_logger](http://erlang.org/doc/man/error_logger.html). +This library comes with an extension to capture all error messages that the Plug handler might not. This is based on [Logger.Backend](https://hexdocs.pm/logger/Logger.html#module-backends). -To set this up, add `:ok = :error_logger.add_report_handler(Sentry.Logger)` to your application's start function. Example: +To set this up, add `{:ok, _} = Logger.add_backend(Sentry.LoggerBackend)` to your application's start function. Example: ```elixir def start(_type, _opts) do @@ -54,7 +62,7 @@ def start(_type, _opts) do opts = [strategy: :one_for_one, name: MyApp.Supervisor] - :ok = :error_logger.add_report_handler(Sentry.Logger) + {:ok, _} = Logger.add_backend(Sentry.LoggerBackend) Supervisor.start_link(children, opts) end @@ -69,7 +77,7 @@ try do ThisWillError.reall() rescue my_exception -> - Sentry.capture_exception(my_exception, [stacktrace: System.stacktrace(), extra: %{extra: information}]) + Sentry.capture_exception(my_exception, [stacktrace: __STACKTRACE__, extra: %{extra: information}]) end ``` @@ -109,6 +117,7 @@ For optional settings check the [docs](https://hexdocs.pm/sentry/readme.html). | `source_code_exclude_patterns` | False | `[~r"/_build/", ~r"/deps/", ~r"/priv/"]` | | | `source_code_path_pattern` | False | `"**/*.ex"` | | | `filter` | False | | Module where the filter rules are defined (see [Filtering Exceptions](https://hexdocs.pm/sentry/Sentry.html#module-filtering-exceptions)) | +| `json_library` | False | `Jason` | | An example production config might look like this: diff --git a/config/test.exs b/config/test.exs index a4c6609a..188447a6 100644 --- a/config/test.exs +++ b/config/test.exs @@ -5,3 +5,6 @@ config :sentry, included_environments: [:test], client: Sentry.TestClient, hackney_opts: [recv_timeout: 50] + +config :ex_unit, + assert_receive_timeout: 500 diff --git a/lib/sentry.ex b/lib/sentry.ex index 24d06b4d..5e08dc6c 100644 --- a/lib/sentry.ex +++ b/lib/sentry.ex @@ -103,8 +103,9 @@ defmodule Sentry do ) ] - opts = [strategy: :one_for_one, name: Sentry.Supervisor] + validate_json_config!() + opts = [strategy: :one_for_one, name: Sentry.Supervisor] Supervisor.start_link(children, opts) end @@ -112,7 +113,7 @@ defmodule Sentry do Parses and submits an exception to Sentry if current environment is in included_environments. `opts` argument is passed as the second argument to `Sentry.send_event/2`. """ - @spec capture_exception(Exception.t(), Keyword.t()) :: send_result + @spec capture_exception(Exception.t() | atom() | {atom(), atom()}, Keyword.t()) :: send_result def capture_exception(exception, opts \\ []) do filter_module = Config.filter() {source, opts} = Keyword.pop(opts, :event_source) @@ -164,4 +165,31 @@ defmodule Sentry do :ignored end end + + defp validate_json_config!() do + case Config.json_library() do + nil -> + raise ArgumentError.exception("nil is not a valid :json_library configuration") + + library -> + try do + with {:ok, %{}} <- library.decode("{}"), + {:ok, "{}"} <- library.encode(%{}) do + :ok + else + _ -> + raise ArgumentError.exception( + "configured :json_library #{inspect(library)} does not implement decode/1 and encode/1" + ) + end + rescue + UndefinedFunctionError -> + reraise ArgumentError.exception(""" + configured :json_library #{inspect(library)} is not available or does not implement decode/1 and encode/1. + Do you need to add #{inspect(library)} to your mix.exs? + """), + __STACKTRACE__ + end + end + end end diff --git a/lib/sentry/client.ex b/lib/sentry/client.ex index 336ee839..bea228b6 100644 --- a/lib/sentry/client.ex +++ b/lib/sentry/client.ex @@ -74,8 +74,10 @@ defmodule Sentry.Client do end defp encode_and_send(event, result) do + json_library = Config.json_library() + render_event(event) - |> Poison.encode() + |> json_library.encode() |> case do {:ok, body} -> do_send_event(event, body, result) @@ -156,15 +158,17 @@ defmodule Sentry.Client do Hackney options can be set via the `hackney_opts` configuration option. """ @spec request(String.t(), list({String.t(), String.t()}), String.t()) :: - {:ok, String.t()} | :error + {:ok, String.t()} | {:error, term()} def request(url, headers, body) do + json_library = Config.json_library() + hackney_opts = Config.hackney_opts() |> Keyword.put_new(:pool, @hackney_pool_name) with {:ok, 200, _, client} <- :hackney.request(:post, url, headers, body, hackney_opts), {:ok, body} <- :hackney.body(client), - {:ok, json} <- Poison.decode(body) do + {:ok, json} <- json_library.decode(body) do {:ok, Map.get(json, "id")} else {:ok, status, headers, client} -> diff --git a/lib/sentry/config.ex b/lib/sentry/config.ex index 4c3bae19..2918704b 100644 --- a/lib/sentry/config.ex +++ b/lib/sentry/config.ex @@ -123,6 +123,10 @@ defmodule Sentry.Config do get_config(:report_deps, default: true, check_dsn: false) end + def json_library do + get_config(:json_library, default: Jason, check_dsn: false) + end + defp get_config(key, opts \\ []) when is_atom(key) do default = Keyword.get(opts, :default) check_dsn = Keyword.get(opts, :check_dsn, true) diff --git a/lib/sentry/event.ex b/lib/sentry/event.ex index 440a9e35..2957411d 100644 --- a/lib/sentry/event.ex +++ b/lib/sentry/event.ex @@ -246,10 +246,9 @@ defmodule Sentry.Event do @spec args_from_stacktrace(Exception.stacktrace()) :: map() def args_from_stacktrace([{_m, _f, a, _} | _]) when is_list(a) do Enum.with_index(a) - |> Enum.map(fn {arg, index} -> + |> Enum.into(%{}, fn {arg, index} -> {"arg#{index}", inspect(arg)} end) - |> Enum.into(%{}) end def args_from_stacktrace(_), do: %{} diff --git a/lib/sentry/logger.ex b/lib/sentry/logger.ex deleted file mode 100644 index 1c170dd5..00000000 --- a/lib/sentry/logger.ex +++ /dev/null @@ -1,126 +0,0 @@ -defmodule Sentry.Logger do - require Logger - - @moduledoc """ - This is based on the Erlang [error_logger](http://erlang.org/doc/man/error_logger.html). - - To set this up, add `:ok = :error_logger.add_report_handler(Sentry.Logger)` to your application's start function. Example: - - ```elixir - def start(_type, _opts) do - children = [ - supervisor(Task.Supervisor, [[name: Sentry.TaskSupervisor]]), - :hackney_pool.child_spec(Sentry.Client.hackney_pool_name(), [timeout: Config.hackney_timeout(), max_connections: Config.max_hackney_connections()]) - ] - opts = [strategy: :one_for_one, name: Sentry.Supervisor] - - :ok = :error_logger.add_report_handler(Sentry.Logger) - - Supervisor.start_link(children, opts) - end - ``` - - Your application will then be running a `Sentry.Logger` event handler that receives error report messages and send them to Sentry. - - It is important to note that the same report handler can be added multiple times. If you run an umbrella app, and add the report handler in multiple individual applications, the same error will be reported multiple times (one for each handler). There are two solutions to fix it. - - The first is to ensure that the handler is only added at the primary application entry-point. This will work, but can be brittle, and will not work for applications running the multiple release style. - - The other solution is to check for existing handlers before trying to add another. Example: - - ```elixir - if !(Sentry.Logger in :gen_event.which_handlers(:error_logger)) do - :ok = :error_logger.add_report_handler(Sentry.Logger) - end - ``` - - With this solution, if a `Sentry.Logger` handler is already running, it will not add another. One can add the code to each application, and there will only ever be one handler created. This solution is safer, but slightly more complex to manage. - """ - - @behaviour :gen_event - - def init(_mod), do: {:ok, []} - - def handle_call({:configure, new_keys}, _state), do: {:ok, :ok, new_keys} - - def handle_event({:error, _gl, {_pid, _type, [pid, {exception, stack}]} = info}, state) - when is_list(stack) and is_pid(pid) do - try do - opts = - Keyword.put([], :event_source, :logger) - |> Keyword.put(:stacktrace, stack) - |> Keyword.put(:error_type, :error) - - Sentry.capture_exception(exception, opts) - rescue - ex -> - Logger.warn(fn -> "Unable to notify Sentry due to #{inspect(ex)}! #{inspect(info)}" end) - end - - {:ok, state} - end - - def handle_event({:error_report, _gl, {_pid, _type, [message | _]}}, state) - when is_list(message) do - try do - {kind, exception, stacktrace, module} = - get_exception_and_stacktrace(message[:error_info]) - |> get_initial_call_and_module(message) - - opts = - (get_in(message, ~w[dictionary sentry_context]a) || %{}) - |> Map.take(Sentry.Context.context_keys()) - |> Map.to_list() - |> Keyword.put(:event_source, :logger) - |> Keyword.put(:stacktrace, stacktrace) - |> Keyword.put(:error_type, kind) - |> Keyword.put(:module, module) - - Sentry.capture_exception(exception, opts) - rescue - ex -> - Logger.warn(fn -> "Unable to notify Sentry due to #{inspect(ex)}! #{inspect(message)}" end) - end - - {:ok, state} - end - - def handle_event(_, state) do - {:ok, state} - end - - def handle_info(_msg, state) do - {:ok, state} - end - - def code_change(_old, state, _extra) do - {:ok, state} - end - - def terminate(_reason, _state) do - :ok - end - - defp get_exception_and_stacktrace({kind, {exception, sub_stack}, _stack}) - when is_list(sub_stack) do - {kind, exception, sub_stack} - end - - defp get_exception_and_stacktrace({kind, exception, stacktrace}) do - {kind, exception, stacktrace} - end - - # GenServer exits will usually only report a stacktrace containing core - # GenServer functions, which causes Sentry to group unrelated exits - # together. This gets the `:initial_call` to help disambiguate, as it contains - # the MFA for how the GenServer was started. - defp get_initial_call_and_module({kind, exception, stacktrace}, error_info) do - case Keyword.get(error_info, :initial_call) do - {module, function, arg} -> - {kind, exception, stacktrace ++ [{module, function, arg, []}], module} - - _ -> - {kind, exception, stacktrace, nil} - end - end -end diff --git a/lib/sentry/logger_backend.ex b/lib/sentry/logger_backend.ex new file mode 100644 index 00000000..5d42d1bb --- /dev/null +++ b/lib/sentry/logger_backend.ex @@ -0,0 +1,74 @@ +defmodule Sentry.LoggerBackend do + @moduledoc """ + This module makes use of Elixir 1.7's new Logger metadata to report + crashes processes. It replaces the previous `Sentry.Logger` sytem. + """ + @behaviour :gen_event + + defstruct level: nil + + def init(__MODULE__) do + config = Application.get_env(:logger, :sentry, []) + {:ok, init(config, %__MODULE__{})} + end + + def init({__MODULE__, opts}) when is_list(opts) do + config = + Application.get_env(:logger, :sentry, []) + |> Keyword.merge(opts) + + {:ok, init(config, %__MODULE__{})} + end + + def handle_call({:configure, _options}, state) do + {:ok, :ok, state} + end + + def handle_event({_level, gl, {Logger, _, _, _}}, state) when node(gl) != node() do + {:ok, state} + end + + def handle_event({_level, _gl, {Logger, _msg, _ts, meta}}, state) do + case Keyword.get(meta, :crash_reason) do + {reason, stacktrace} -> + opts = + Keyword.put([], :event_source, :logger) + |> Keyword.put(:stacktrace, stacktrace) + + Sentry.capture_exception(reason, opts) + + reason when is_atom(reason) and not is_nil(reason) -> + Sentry.capture_exception(reason, event_source: :logger) + + _ -> + :ok + end + + {:ok, state} + end + + def handle_event(:flush, state) do + {:ok, state} + end + + def handle_event(_, state) do + {:ok, state} + end + + def handle_info(_, state) do + {:ok, state} + end + + def code_change(_old_vsn, state, _extra) do + {:ok, state} + end + + def terminate(_reason, _state) do + :ok + end + + defp init(config, %__MODULE__{} = state) do + level = Keyword.get(config, :level) + %{state | level: level} + end +end diff --git a/lib/sentry/phoenix_endpoint.ex b/lib/sentry/phoenix_endpoint.ex index 51bd6040..90452762 100644 --- a/lib/sentry/phoenix_endpoint.ex +++ b/lib/sentry/phoenix_endpoint.ex @@ -29,7 +29,7 @@ defmodule Sentry.Phoenix.Endpoint do super(conn, opts) catch kind, reason -> - stacktrace = System.stacktrace() + stacktrace = __STACKTRACE__ request = Sentry.Plug.build_request_interface_data(conn, []) exception = Exception.normalize(kind, reason, stacktrace) diff --git a/lib/sentry/plug.ex b/lib/sentry/plug.ex index 8a8db168..277b4a26 100644 --- a/lib/sentry/plug.ex +++ b/lib/sentry/plug.ex @@ -212,7 +212,7 @@ if Code.ensure_loaded?(Plug) do end defp scrub_map(map) do - Enum.map(map, fn {key, value} -> + Enum.into(map, %{}, fn {key, value} -> value = cond do Enum.member?(@default_scrubbed_param_keys, key) -> @@ -221,7 +221,11 @@ if Code.ensure_loaded?(Plug) do is_binary(value) && Regex.match?(@credit_card_regex, value) -> @scrubbed_value - is_map(value) && !Map.has_key?(value, :__struct__) -> + is_map(value) && Map.has_key?(value, :__struct__) -> + Map.from_struct(value) + |> scrub_map() + + is_map(value) -> scrub_map(value) true -> @@ -230,7 +234,6 @@ if Code.ensure_loaded?(Plug) do {key, value} end) - |> Enum.into(%{}) end end end diff --git a/mix.exs b/mix.exs index a9c82c0d..bb45f65e 100644 --- a/mix.exs +++ b/mix.exs @@ -4,8 +4,8 @@ defmodule Sentry.Mixfile do def project do [ app: :sentry, - version: "6.4.2", - elixir: "~> 1.3", + version: "7.0.0", + elixir: "~> 1.7", description: "The Official Elixir client for Sentry", package: package(), deps: deps(), @@ -25,12 +25,12 @@ defmodule Sentry.Mixfile do defp deps do [ {:hackney, "~> 1.8 or 1.6.5"}, - {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0"}, + {:jason, "~> 1.1", optional: true}, {:plug, "~> 1.6", optional: true}, {:phoenix, "~> 1.3", optional: true}, {:dialyxir, "> 0.0.0", only: [:dev], runtime: false}, - {:ex_doc, "~> 0.18.0", only: :dev}, - {:credo, "~> 0.9.0", only: [:dev, :test], runtime: false}, + {:ex_doc, "~> 0.19.0", only: :dev}, + {:credo, "~> 0.10.0", only: [:dev, :test], runtime: false}, {:bypass, "~> 0.8.0", only: [:test]} ] end diff --git a/mix.lock b/mix.lock index 79bd89f5..ca5b9f7b 100644 --- a/mix.lock +++ b/mix.lock @@ -4,19 +4,23 @@ "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, "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"}, "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"}, - "credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "credo": {:hex, :credo, "0.10.0", "66234a95effaf9067edb19fc5d0cd5c6b461ad841baac42467afed96c78e5e9e", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.18.4", "4406b8891cecf1352f49975c6d554e62e4341ceb41b9338949077b0d4a97b949", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, "hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "jason": {:hex, :jason, "1.1.1", "d3ccb840dfb06f2f90a6d335b536dd074db748b3e7f5b11ab61d239506585eb2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, + "makeup": {:hex, :makeup, "0.5.1", "966c5c2296da272d42f1de178c1d135e432662eca795d6dc12e5e8787514edf7", [:mix], [{:nimble_parsec, "~> 0.2.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.8.0", "1204a2f5b4f181775a0e456154830524cf2207cf4f9112215c05e0b76e4eca8b", [:mix], [{:makeup, "~> 0.5.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.2.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.2.2", "d526b23bdceb04c7ad15b33c57c4526bf5f50aaa70c7c141b4b4624555c68259", [:mix], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, - "phoenix": {:hex, :phoenix, "1.3.3", "bafb5fa408d202e8d9f739e781bdb908446a2c1c1e00797c1158918ed55566a4", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "phoenix": {:hex, :phoenix, "1.3.4", "aaa1b55e5523083a877bcbe9886d9ee180bf2c8754905323493c2ac325903dc5", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], [], "hexpm"}, - "plug": {:hex, :plug, "1.6.1", "c62fe7623d035020cf989820b38490460e6903ab7eee29e234b7586e9b6c91d6", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, + "plug": {:hex, :plug, "1.6.2", "e06a7bd2bb6de5145da0dd950070110dce88045351224bd98e84edfdaaf5ffee", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, diff --git a/test/client_test.exs b/test/client_test.exs index a0e67fdf..154457b6 100644 --- a/test/client_test.exs +++ b/test/client_test.exs @@ -107,7 +107,7 @@ defmodule Sentry.ClientTest do Bypass.expect(bypass, fn conn -> {:ok, body, conn} = Plug.Conn.read_body(conn) - request_map = Poison.decode!(body) + request_map = Jason.decode!(body) assert request_map["extra"] == %{"key" => "value"} assert request_map["user"]["id"] == 1 assert is_nil(request_map["stacktrace"]["frames"]) @@ -142,7 +142,7 @@ defmodule Sentry.ClientTest do Bypass.expect(bypass, fn conn -> {:ok, body, conn} = Plug.Conn.read_body(conn) - request_map = Poison.decode!(body) + request_map = Jason.decode!(body) assert request_map["extra"] == %{"key" => "value", "user_id" => 1} Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) end) @@ -226,7 +226,7 @@ defmodule Sentry.ClientTest do Bypass.expect(bypass, fn conn -> {:ok, body, conn} = Plug.Conn.read_body(conn) - request_map = Poison.decode!(body) + request_map = Jason.decode!(body) assert Enum.count(request_map["stacktrace"]["frames"]) > 0 Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) end) @@ -244,7 +244,7 @@ defmodule Sentry.ClientTest do {:ok, _} = Sentry.capture_exception( e, - stacktrace: System.stacktrace(), + stacktrace: __STACKTRACE__, result: :sync, sample_rate: 1 ) diff --git a/test/event_test.exs b/test/event_test.exs index 60095379..3d22aacc 100644 --- a/test/event_test.exs +++ b/test/event_test.exs @@ -7,292 +7,11 @@ defmodule Sentry.EventTest do try do Event.not_a_function(1, 2, 3) rescue - e -> Event.transform_exception(e, stacktrace: System.stacktrace(), extra: extra) + e -> Event.transform_exception(e, stacktrace: __STACKTRACE__, extra: extra) end end def get_stacktrace_frames_for_elixir() do - cond do - Version.match?(System.version(), "< 1.4.0") -> - [ - %{ - filename: nil, - function: "Sentry.Event.not_a_function/3", - lineno: nil, - module: Sentry.Event, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{"arg0" => "1", "arg1" => "2", "arg2" => "3"} - }, - %{ - filename: "test/event_test.exs", - function: "Sentry.EventTest.event_generated_by_exception/1", - lineno: 8, - module: Sentry.EventTest, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - }, - %{ - filename: "test/event_test.exs", - function: "Sentry.EventTest.\"test parses error exception\"/1", - lineno: 299, - module: Sentry.EventTest, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - }, - %{ - filename: "lib/ex_unit/runner.ex", - function: "ExUnit.Runner.exec_test/1", - lineno: 296, - module: ExUnit.Runner, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - }, - %{ - filename: "timer.erl", - function: ":timer.tc/1", - lineno: 166, - module: :timer, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - }, - %{ - filename: "lib/ex_unit/runner.ex", - function: "anonymous fn/3 in ExUnit.Runner.spawn_test/3", - lineno: 246, - module: ExUnit.Runner, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - } - ] - - Version.match?(System.version(), "< 1.5.0") -> - [ - %{ - filename: nil, - function: "Sentry.Event.not_a_function/3", - lineno: nil, - module: Sentry.Event, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{"arg0" => "1", "arg1" => "2", "arg2" => "3"} - }, - %{ - filename: "test/event_test.exs", - function: "Sentry.EventTest.event_generated_by_exception/1", - lineno: 8, - module: Sentry.EventTest, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - }, - %{ - filename: "test/event_test.exs", - function: "Sentry.EventTest.\"test parses error exception\"/1", - lineno: 299, - module: Sentry.EventTest, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - }, - %{ - filename: "lib/ex_unit/runner.ex", - function: "ExUnit.Runner.exec_test/1", - lineno: 302, - module: ExUnit.Runner, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - }, - %{ - filename: "timer.erl", - function: ":timer.tc/1", - lineno: 166, - module: :timer, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - }, - %{ - filename: "lib/ex_unit/runner.ex", - function: "anonymous fn/3 in ExUnit.Runner.spawn_test/3", - lineno: 250, - module: ExUnit.Runner, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - } - ] - - Version.match?(System.version(), "~> 1.5.0") -> - [ - %{ - filename: nil, - function: "Sentry.Event.not_a_function/3", - lineno: nil, - module: Sentry.Event, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{"arg0" => "1", "arg1" => "2", "arg2" => "3"} - }, - %{ - filename: "test/event_test.exs", - function: "Sentry.EventTest.event_generated_by_exception/1", - lineno: 8, - module: Sentry.EventTest, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - }, - %{ - filename: "test/event_test.exs", - function: "Sentry.EventTest.\"test parses error exception\"/1", - lineno: 299, - module: Sentry.EventTest, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - }, - %{ - filename: "lib/ex_unit/runner.ex", - function: "ExUnit.Runner.exec_test/1", - lineno: 292, - module: ExUnit.Runner, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - }, - %{ - filename: "timer.erl", - function: ":timer.tc/1", - lineno: 166, - module: :timer, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - }, - %{ - filename: "lib/ex_unit/runner.ex", - function: "anonymous fn/3 in ExUnit.Runner.spawn_test/3", - lineno: 240, - module: ExUnit.Runner, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - } - ] - - Version.match?(System.version(), "~> 1.6.0") -> - [ - %{ - filename: nil, - function: "Sentry.Event.not_a_function/3", - lineno: nil, - module: Sentry.Event, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{"arg0" => "1", "arg1" => "2", "arg2" => "3"} - }, - %{ - filename: "test/event_test.exs", - function: "Sentry.EventTest.event_generated_by_exception/1", - lineno: 8, - module: Sentry.EventTest, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - }, - %{ - filename: "test/event_test.exs", - function: "Sentry.EventTest.\"test parses error exception\"/1", - lineno: 299, - module: Sentry.EventTest, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - }, - %{ - filename: "lib/ex_unit/runner.ex", - function: "ExUnit.Runner.exec_test/1", - lineno: 306, - module: ExUnit.Runner, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - }, - %{ - filename: "timer.erl", - function: ":timer.tc/1", - lineno: 166, - module: :timer, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - }, - %{ - filename: "lib/ex_unit/runner.ex", - function: "anonymous fn/4 in ExUnit.Runner.spawn_test/3", - lineno: 245, - module: ExUnit.Runner, - context_line: nil, - post_context: [], - pre_context: [], - in_app: false, - vars: %{} - } - ] - end end test "parses error exception" do @@ -318,7 +37,74 @@ defmodule Sentry.EventTest do assert is_binary(event.server_name) assert event.stacktrace == %{ - frames: get_stacktrace_frames_for_elixir() |> Enum.reverse() + frames: [ + %{ + context_line: nil, + filename: "lib/ex_unit/runner.ex", + function: "anonymous fn/4 in ExUnit.Runner.spawn_test/3", + in_app: false, + lineno: 251, + module: ExUnit.Runner, + post_context: [], + pre_context: [], + vars: %{} + }, + %{ + context_line: nil, + filename: "timer.erl", + function: ":timer.tc/1", + in_app: false, + lineno: 166, + module: :timer, + post_context: [], + pre_context: [], + vars: %{} + }, + %{ + context_line: nil, + filename: "lib/ex_unit/runner.ex", + function: "ExUnit.Runner.exec_test/1", + in_app: false, + lineno: 312, + module: ExUnit.Runner, + post_context: [], + pre_context: [], + vars: %{} + }, + %{ + context_line: nil, + filename: "test/event_test.exs", + function: "Sentry.EventTest.\"test parses error exception\"/1", + in_app: false, + lineno: 18, + module: Sentry.EventTest, + post_context: [], + pre_context: [], + vars: %{} + }, + %{ + context_line: nil, + filename: "test/event_test.exs", + function: "Sentry.EventTest.event_generated_by_exception/1", + in_app: false, + lineno: 8, + module: Sentry.EventTest, + post_context: [], + pre_context: [], + vars: %{} + }, + %{ + context_line: nil, + filename: nil, + function: "Sentry.Event.not_a_function/3", + in_app: false, + lineno: nil, + module: Sentry.Event, + post_context: [], + pre_context: [], + vars: %{"arg0" => "1", "arg1" => "2", "arg2" => "3"} + } + ] } assert event.tags == %{} @@ -461,6 +247,7 @@ defmodule Sentry.EventTest do :credo, :hackney, :idna, + :jason, :metrics, :mime, :mimerl, diff --git a/test/logger_backend_test.exs b/test/logger_backend_test.exs new file mode 100644 index 00000000..99fd849d --- /dev/null +++ b/test/logger_backend_test.exs @@ -0,0 +1,150 @@ +defmodule Sentry.LoggerBackendTest do + use ExUnit.Case + import ExUnit.CaptureLog + import Sentry.TestEnvironmentHelper + + setup do + {:ok, _} = Logger.add_backend(Sentry.LoggerBackend) + + ExUnit.Callbacks.on_exit(fn -> + :ok = Logger.remove_backend(Sentry.LoggerBackend) + end) + end + + test "exception makes call to Sentry API" do + Process.flag(:trap_exit, true) + bypass = Bypass.open() + pid = self() + + Bypass.expect(bypass, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + assert body =~ "RuntimeError" + assert body =~ "Unique Error" + assert conn.request_path == "/api/1/store/" + assert conn.method == "POST" + send(pid, "API called") + Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) + end) + + modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1") + + capture_log(fn -> + Task.start(fn -> + raise "Unique Error" + end) + + assert_receive "API called" + end) + end + + test "GenServer throw makes call to Sentry API" do + self_pid = self() + Process.flag(:trap_exit, true) + bypass = Bypass.open() + + Bypass.expect(bypass, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + json = Jason.decode!(body) + + assert List.first(json["exception"])["value"] == + ~s[Erlang error: {:bad_return_value, "I am throwing"}] + + assert conn.request_path == "/api/1/store/" + assert conn.method == "POST" + send(self_pid, "API called") + Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) + end) + + modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1") + + capture_log(fn -> + {:ok, pid} = Sentry.TestGenServer.start_link(self_pid) + Sentry.TestGenServer.do_throw(pid) + assert_receive "terminating" + assert_receive "API called" + end) + end + + test "abnormal GenServer exit makes call to Sentry API" do + self_pid = self() + Process.flag(:trap_exit, true) + bypass = Bypass.open() + + Bypass.expect(bypass, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + json = Jason.decode!(body) + assert List.first(json["exception"])["type"] == "Elixir.ErlangError" + assert List.first(json["exception"])["value"] == "Erlang error: :bad_exit" + assert conn.request_path == "/api/1/store/" + assert conn.method == "POST" + send(self_pid, "API called") + Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) + end) + + modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1") + + capture_log(fn -> + {:ok, pid} = Sentry.TestGenServer.start_link(self_pid) + Sentry.TestGenServer.bad_exit(pid) + assert_receive "terminating" + assert_receive "API called" + end) + end + + test "Bad function call causing GenServer crash makes call to Sentry API" do + self_pid = self() + Process.flag(:trap_exit, true) + bypass = Bypass.open() + + Bypass.expect(bypass, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + json = Jason.decode!(body) + + assert %{ + "in_app" => false, + "module" => "Elixir.NaiveDateTime", + "context_line" => nil, + "pre_context" => [], + "post_context" => [] + } = List.last(json["stacktrace"]["frames"]) + + assert List.first(json["exception"])["type"] == "Elixir.FunctionClauseError" + assert conn.request_path == "/api/1/store/" + assert conn.method == "POST" + send(self_pid, "API called") + Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) + end) + + modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1") + + capture_log(fn -> + {:ok, pid} = Sentry.TestGenServer.start_link(self_pid) + Sentry.TestGenServer.invalid_function(pid) + assert_receive "terminating" + assert_receive "API called" + end) + end + + test "captures errors from spawn() in Plug app" do + bypass = Bypass.open() + pid = self() + + Bypass.expect(bypass, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + json = Jason.decode!(body) + assert length(json["stacktrace"]["frames"]) == 1 + assert List.first(json["stacktrace"]["frames"])["filename"] == "test/support/test_plug.ex" + send(pid, "API called") + Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) + end) + + modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1") + + capture_log(fn -> + Plug.Test.conn(:get, "/spawn_error_route") + |> Sentry.ExampleApp.call([]) + + assert_receive "API called" + end) + end +end diff --git a/test/logger_test.exs b/test/logger_test.exs deleted file mode 100644 index 5477cb9c..00000000 --- a/test/logger_test.exs +++ /dev/null @@ -1,234 +0,0 @@ -defmodule Sentry.LoggerTest do - use ExUnit.Case - import ExUnit.CaptureLog - import Sentry.TestEnvironmentHelper - - test "exception makes call to Sentry API" do - bypass = Bypass.open() - pid = self() - - Bypass.expect(bypass, fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) - assert body =~ "RuntimeError" - assert body =~ "Unique Error" - assert conn.request_path == "/api/1/store/" - assert conn.method == "POST" - send(pid, "API called") - Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) - end) - - modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1") - :error_logger.add_report_handler(Sentry.Logger) - - capture_log(fn -> - Task.start(fn -> - raise "Unique Error" - end) - - assert_receive "API called" - end) - - :error_logger.delete_report_handler(Sentry.Logger) - end - - test "GenServer throw makes call to Sentry API" do - Process.flag(:trap_exit, true) - bypass = Bypass.open() - - Bypass.expect(bypass, fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) - json = Poison.decode!(body) - assert List.first(json["exception"])["type"] == "exit" - - assert List.first(json["exception"])["value"] == - "** (exit) bad return value: \"I am throwing\"" - - assert conn.request_path == "/api/1/store/" - assert conn.method == "POST" - Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) - end) - - modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1") - :error_logger.add_report_handler(Sentry.Logger) - - capture_log(fn -> - {:ok, pid} = Sentry.TestGenServer.start_link(self()) - Sentry.TestGenServer.do_throw(pid) - assert_receive "terminating" - end) - - :error_logger.delete_report_handler(Sentry.Logger) - end - - test "abnormal GenServer exit makes call to Sentry API" do - Process.flag(:trap_exit, true) - bypass = Bypass.open() - - Bypass.expect(bypass, fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) - json = Poison.decode!(body) - assert List.first(json["exception"])["type"] == "exit" - assert List.first(json["exception"])["value"] == "** (exit) :bad_exit" - assert conn.request_path == "/api/1/store/" - assert conn.method == "POST" - Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) - end) - - modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1") - :error_logger.add_report_handler(Sentry.Logger) - - capture_log(fn -> - {:ok, pid} = Sentry.TestGenServer.start_link(self()) - Sentry.TestGenServer.bad_exit(pid) - assert_receive "terminating" - end) - - :error_logger.delete_report_handler(Sentry.Logger) - end - - test "Bad function call causing GenServer crash makes call to Sentry API" do - Process.flag(:trap_exit, true) - bypass = Bypass.open() - {otp_version, ""} = Float.parse(System.otp_release()) - - Bypass.expect(bypass, fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) - json = Poison.decode!(body) - - cond do - otp_version >= 20.0 -> - assert List.first(json["exception"])["type"] == "Elixir.FunctionClauseError" - assert String.starts_with?(List.first(json["exception"])["value"], "no function clause") - - otp_version < 20.0 -> - assert List.first(json["exception"])["type"] == "exit" - assert List.first(json["exception"])["value"] == "** (exit) :function_clause" - end - - cond do - Version.match?(System.version(), "< 1.4.0") -> - assert List.last(json["stacktrace"]["frames"])["vars"] == %{ - "arg0" => "{}", - "arg1" => "{}" - } - - assert List.last(json["stacktrace"]["frames"])["function"] == "NaiveDateTime.from_erl/2" - assert List.last(json["stacktrace"]["frames"])["filename"] == "lib/calendar.ex" - assert List.last(json["stacktrace"]["frames"])["lineno"] == 878 - - Version.match?(System.version(), "< 1.5.0") -> - assert List.last(json["stacktrace"]["frames"])["vars"] == %{ - "arg0" => "{}", - "arg1" => "{}" - } - - assert List.last(json["stacktrace"]["frames"])["function"] == "NaiveDateTime.from_erl/2" - assert List.last(json["stacktrace"]["frames"])["filename"] == "lib/calendar.ex" - assert List.last(json["stacktrace"]["frames"])["lineno"] == 1214 - - Version.match?(System.version(), "~> 1.5.0") -> - assert List.last(json["stacktrace"]["frames"])["vars"] == %{ - "arg0" => "{}", - "arg1" => "{}", - "arg2" => "{}" - } - - assert List.last(json["stacktrace"]["frames"])["filename"] == - "lib/calendar/naive_datetime.ex" - - assert List.last(json["stacktrace"]["frames"])["function"] == "NaiveDateTime.from_erl/3" - assert List.last(json["stacktrace"]["frames"])["lineno"] == 522 - - Version.match?(System.version(), ">= 1.5.0") -> - assert List.last(json["stacktrace"]["frames"])["vars"] == %{ - "arg0" => "{}", - "arg1" => "{}", - "arg2" => "{}" - } - - assert List.last(json["stacktrace"]["frames"])["filename"] == - "lib/calendar/naive_datetime.ex" - - assert List.last(json["stacktrace"]["frames"])["function"] == "NaiveDateTime.from_erl/3" - assert List.last(json["stacktrace"]["frames"])["lineno"] == 625 - end - - assert %{ - "in_app" => false, - "module" => "Elixir.NaiveDateTime", - "context_line" => nil, - "pre_context" => [], - "post_context" => [] - } = List.last(json["stacktrace"]["frames"]) - - assert conn.request_path == "/api/1/store/" - assert conn.method == "POST" - Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) - end) - - modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1") - :error_logger.add_report_handler(Sentry.Logger) - - capture_log(fn -> - {:ok, pid} = Sentry.TestGenServer.start_link(self()) - Sentry.TestGenServer.invalid_function(pid) - assert_receive "terminating" - end) - - :error_logger.delete_report_handler(Sentry.Logger) - end - - test "error_logger passes context properly" do - bypass = Bypass.open() - pid = self() - - Bypass.expect(bypass, fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) - body = Poison.decode!(body) - assert get_in(body, ["extra", "fruit"]) == "apples" - assert conn.request_path == "/api/1/store/" - assert conn.method == "POST" - send(pid, "API called") - Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) - end) - - modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1") - :error_logger.add_report_handler(Sentry.Logger) - - capture_log(fn -> - Task.start(fn -> - Sentry.Context.set_extra_context(%{fruit: "apples"}) - raise "Unique Error" - end) - - assert_receive "API called" - end) - - :error_logger.delete_report_handler(Sentry.Logger) - end - - test "captures errors from spawn() in Plug app" do - bypass = Bypass.open() - pid = self() - - Bypass.expect(bypass, fn conn -> - {:ok, body, conn} = Plug.Conn.read_body(conn) - json = Poison.decode!(body) - assert length(json["stacktrace"]["frames"]) == 1 - assert List.first(json["stacktrace"]["frames"])["filename"] == "test/support/test_plug.ex" - send(pid, "API called") - Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) - end) - - modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1") - :error_logger.add_report_handler(Sentry.Logger) - - capture_log(fn -> - Plug.Test.conn(:get, "/spawn_error_route") - |> Sentry.ExampleApp.call([]) - - assert_receive "API called" - :error_logger.delete_report_handler(Sentry.Logger) - end) - end -end diff --git a/test/phoenix_endpoint_test.exs b/test/phoenix_endpoint_test.exs index 68474dbb..8de417e0 100644 --- a/test/phoenix_endpoint_test.exs +++ b/test/phoenix_endpoint_test.exs @@ -25,7 +25,7 @@ defmodule Sentry.PhoenixEndpointTest do Bypass.expect(bypass, fn conn -> {:ok, body, conn} = Plug.Conn.read_body(conn) - json = Poison.decode!(body) + json = Jason.decode!(body) assert json["culprit"] == "Sentry.PhoenixEndpointTest.Endpoint.error/2" assert json["message"] == "(RuntimeError) EndpointError" Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) diff --git a/test/plug_test.exs b/test/plug_test.exs index 35fe6138..0de71ad8 100644 --- a/test/plug_test.exs +++ b/test/plug_test.exs @@ -67,7 +67,7 @@ defmodule Sentry.PlugTest do Bypass.expect(bypass, fn conn -> {:ok, body, conn} = Plug.Conn.read_body(conn) - json = Poison.decode!(body) + json = Jason.decode!(body) assert json["request"]["cookies"] == %{} assert json["request"]["headers"] == %{"content-type" => "application/json"} Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) @@ -111,7 +111,7 @@ defmodule Sentry.PlugTest do Bypass.expect(bypass, fn conn -> {:ok, body, conn} = Plug.Conn.read_body(conn) - json = Poison.decode!(body) + json = Jason.decode!(body) assert is_map(json["request"]["data"]["image"]) assert json["request"]["data"]["password"] == "*********" Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) @@ -147,7 +147,7 @@ defmodule Sentry.PlugTest do Bypass.expect(bypass, fn conn -> {:ok, body, conn} = Plug.Conn.read_body(conn) - json = Poison.decode!(body) + json = Jason.decode!(body) assert json["request"]["cookies"] == %{"regular" => "value"} Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) end) diff --git a/test/sources_test.exs b/test/sources_test.exs index cde4e4d3..ddc4856b 100644 --- a/test/sources_test.exs +++ b/test/sources_test.exs @@ -28,7 +28,7 @@ defmodule Sentry.SourcesTest do {:ok, body, conn} = Plug.Conn.read_body(conn) frames = - Poison.decode!(body) + Jason.decode!(body) |> get_in(["stacktrace", "frames"]) |> Enum.reverse() diff --git a/test/support/test_client.exs b/test/support/test_client.exs index d2a96ba1..f03ebcfe 100644 --- a/test/support/test_client.exs +++ b/test/support/test_client.exs @@ -7,7 +7,7 @@ defmodule Sentry.TestClient do event = Sentry.Client.maybe_call_before_send_event(event) Sentry.Client.render_event(event) - |> Poison.encode() + |> Jason.encode() |> case do {:ok, body} -> case Sentry.Client.request(endpoint, [], body) do