From ef87d0b35f51a1de64dbbeb3c58da4c2fa33875d Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Wed, 7 Dec 2016 16:46:00 -0600 Subject: [PATCH 1/8] add filter behavior --- lib/sentry/event_filter.ex | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 lib/sentry/event_filter.ex diff --git a/lib/sentry/event_filter.ex b/lib/sentry/event_filter.ex new file mode 100644 index 00000000..5fd21d59 --- /dev/null +++ b/lib/sentry/event_filter.ex @@ -0,0 +1,8 @@ +defmodule Sentry.EventFilter do + @callback exclude_exception?(atom, Exception.t) :: any +end + +defmodule Sentry.DefaultEventFilter do + @behaviour Sentry.EventFilter + def exclude_exception?(_, _), do: false +end From 11f0364820cddefa179ec3fb06a84cb90c317124 Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Wed, 7 Dec 2016 16:46:50 -0600 Subject: [PATCH 2/8] filtering functionality --- lib/sentry.ex | 9 ++++++--- lib/sentry/logger.ex | 6 ++++-- lib/sentry/plug.ex | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/sentry.ex b/lib/sentry.ex index 916302b6..dd6f9fd5 100644 --- a/lib/sentry.ex +++ b/lib/sentry.ex @@ -84,9 +84,12 @@ defmodule Sentry do """ @spec capture_exception(Exception.t, Keyword.t) :: {:ok, String.t} | :error def capture_exception(exception, opts \\ []) do - exception - |> Event.transform_exception(opts) - |> send_event() + filter_module = Application.get_env(:sentry, :filter, Sentry.DefaultEventFilter) + {source, opts} = Keyword.pop(opts, :event_source) + unless(filter_module.exclude_exception?(exception, source)) do + Event.transform_exception(exception, opts) + |> send_event() + end end @doc """ diff --git a/lib/sentry/logger.ex b/lib/sentry/logger.ex index 05031739..20937aa6 100644 --- a/lib/sentry/logger.ex +++ b/lib/sentry/logger.ex @@ -34,10 +34,12 @@ defmodule Sentry.Logger do case error_info do {_kind, {exception, stacktrace}, _stack} when is_list(stacktrace) -> opts = Keyword.put(opts, :stacktrace, stacktrace) - Sentry.capture_exception(exception, opts) + |> Keyword.put(:event_source, :logger) + Sentry.capture_exception(exception, opts) {_kind, exception, stacktrace} -> opts = Keyword.put(opts, :stacktrace, stacktrace) - Sentry.capture_exception(exception, opts) + |> Keyword.put(:event_source, :logger) + Sentry.capture_exception(exception, opts) end rescue ex -> diff --git a/lib/sentry/plug.ex b/lib/sentry/plug.ex index 2fd51140..06e091fe 100644 --- a/lib/sentry/plug.ex +++ b/lib/sentry/plug.ex @@ -87,7 +87,7 @@ defmodule Sentry.Plug do request_id_header: unquote(request_id_header)] request = Sentry.Plug.build_request_interface_data(conn, opts) exception = Exception.normalize(kind, reason, stack) - Sentry.capture_exception(exception, [stacktrace: stack, request: request]) + Sentry.capture_exception(exception, [stacktrace: stack, request: request, event_source: :plug]) end end end From 7dc1d1585b1d31a3cf25e23c84f9675053f88c68 Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Wed, 7 Dec 2016 16:47:02 -0600 Subject: [PATCH 3/8] test --- test/sentry_test.exs | 23 ++++++++++++++++++++++- test/support/test_filter.ex | 6 ++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 test/support/test_filter.ex diff --git a/test/sentry_test.exs b/test/sentry_test.exs index ec6b8cc3..ecae8fa5 100644 --- a/test/sentry_test.exs +++ b/test/sentry_test.exs @@ -1,3 +1,24 @@ defmodule SentryTest do - use ExUnit.Case, async: true + use ExUnit.Case + + test "excludes events properly" do + Application.put_env(:sentry, :filter, Sentry.TestFilter) + + + bypass = Bypass.open + Bypass.expect bypass, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + assert body =~ "RuntimeError" + assert conn.request_path == "/api/1/store/" + assert conn.method == "POST" + Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) + end + + Application.put_env(:sentry, :dsn, "http://public:secret@localhost:#{bypass.port}/1") + Application.put_env(:sentry, :included_environments, [:test]) + Application.put_env(:sentry, :environment_name, :test) + + Sentry.capture_exception(%RuntimeError{message: "error"}, [event_source: :plug]) + Sentry.capture_exception(%ArithmeticError{message: "error"}, [event_source: :plug]) + end end diff --git a/test/support/test_filter.ex b/test/support/test_filter.ex new file mode 100644 index 00000000..7fcdafb1 --- /dev/null +++ b/test/support/test_filter.ex @@ -0,0 +1,6 @@ +defmodule Sentry.TestFilter do + @behaviour Sentry.Filter + + def exclude_exception?(%ArithmeticError{}, :plug), do: true + def exclude_exception?(_, _), do: false +end From 2c01fe84a51ff383a6b64439e9d6bd47d0e62928 Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Thu, 8 Dec 2016 15:41:40 -0600 Subject: [PATCH 4/8] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0d97ffd..8d64fd3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## master +* Enhancements + * Allow filtering which exceptions are sent via `Sentry.Filter` behaviour + * Bug Fixes * Fix usage of deprecated modules From baada7e29ada3638a29a90180f7032b4582f75ab Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Thu, 8 Dec 2016 16:05:01 -0600 Subject: [PATCH 5/8] add hex docs --- lib/sentry.ex | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/sentry.ex b/lib/sentry.ex index dd6f9fd5..f2b6e34a 100644 --- a/lib/sentry.ex +++ b/lib/sentry.ex @@ -51,11 +51,40 @@ defmodule Sentry do our local development machines, exceptions will never be sent, because the default value is not in the list of `included_environments`. + ## Filtering Exceptions + + If you would like to prevent certain exceptions, the :filter configuration option + allows you to implement the `Sentry.Filter` behaviour. The first argument is the + source of the event, and the second is the exception to be sent. `Sentry.Plug` + will have a source of `:plug`, and `Sentry.Logger` will have a source of `:logger`. + If an exception does not come from either of those sources, the source will be nil + unless the `:event_source` option is passed to `Sentry.capture_exception/2` + + A configuration like below will prevent sending `Phoenix.Router.NoRouteError` from `Sentry.Plug`, but + allows other exceptions to be sent. + + # sentry_event_filter.exs + defmodule MyApp.SentryEventFilter do + @behaviour Sentry.Filter + + def exclude_exception?(:plug, %Elixir.Phoenix.Router.NoRouteError{}), do: true + def exclude_exception?(_, ), do: false + end + + # config.exs + config :sentry, filter: MyApp.SentryEventFilter, + included_environments: ~w(production staging), + environment_name: System.get_env("RELEASE_LEVEL") || "development" + ## Capturing Exceptions - Simply calling `capture_exception\2` will send the event. + Simply calling `capture_exception/2` will send the event. Sentry.capture_exception(my_exception) + Sentry.capture_exception(other_exception, [source_name: :my_source]) + + ### Options + * `:event_source` - The source passed as the first argument to `Sentry.Filter.exclude_exception?/2` ## Configuring The `Logger` Backend From edd78f8e242472743989cd0f3bf31aba7e287708 Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Thu, 8 Dec 2016 16:10:54 -0600 Subject: [PATCH 6/8] use correct behaviour name --- CHANGELOG.md | 2 +- lib/sentry.ex | 6 +++--- test/support/test_filter.ex | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d64fd3e..07286ffe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## master * Enhancements - * Allow filtering which exceptions are sent via `Sentry.Filter` behaviour + * Allow filtering which exceptions are sent via `Sentry.EventFilter` behaviour * Bug Fixes * Fix usage of deprecated modules diff --git a/lib/sentry.ex b/lib/sentry.ex index f2b6e34a..fa173df0 100644 --- a/lib/sentry.ex +++ b/lib/sentry.ex @@ -54,7 +54,7 @@ defmodule Sentry do ## Filtering Exceptions If you would like to prevent certain exceptions, the :filter configuration option - allows you to implement the `Sentry.Filter` behaviour. The first argument is the + allows you to implement the `Sentry.EventFilter` behaviour. The first argument is the source of the event, and the second is the exception to be sent. `Sentry.Plug` will have a source of `:plug`, and `Sentry.Logger` will have a source of `:logger`. If an exception does not come from either of those sources, the source will be nil @@ -65,7 +65,7 @@ defmodule Sentry do # sentry_event_filter.exs defmodule MyApp.SentryEventFilter do - @behaviour Sentry.Filter + @behaviour Sentry.EventFilter def exclude_exception?(:plug, %Elixir.Phoenix.Router.NoRouteError{}), do: true def exclude_exception?(_, ), do: false @@ -84,7 +84,7 @@ defmodule Sentry do Sentry.capture_exception(other_exception, [source_name: :my_source]) ### Options - * `:event_source` - The source passed as the first argument to `Sentry.Filter.exclude_exception?/2` + * `:event_source` - The source passed as the first argument to `Sentry.EventFilter.exclude_exception?/2` ## Configuring The `Logger` Backend diff --git a/test/support/test_filter.ex b/test/support/test_filter.ex index 7fcdafb1..dcd76ac1 100644 --- a/test/support/test_filter.ex +++ b/test/support/test_filter.ex @@ -1,5 +1,5 @@ defmodule Sentry.TestFilter do - @behaviour Sentry.Filter + @behaviour Sentry.EventFilter def exclude_exception?(%ArithmeticError{}, :plug), do: true def exclude_exception?(_, _), do: false From 9b8ec9c2d99b90f9de8c4902b392067c23fac2f8 Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Thu, 8 Dec 2016 16:12:39 -0600 Subject: [PATCH 7/8] return :excluded instead of nil --- lib/sentry.ex | 6 ++++-- test/sentry_test.exs | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/sentry.ex b/lib/sentry.ex index fa173df0..43b70101 100644 --- a/lib/sentry.ex +++ b/lib/sentry.ex @@ -111,11 +111,13 @@ defmodule Sentry do @doc """ Parses and submits an exception to Sentry if current environment is in included_environments. """ - @spec capture_exception(Exception.t, Keyword.t) :: {:ok, String.t} | :error + @spec capture_exception(Exception.t, Keyword.t) :: {:ok, String.t} | :error | :excluded def capture_exception(exception, opts \\ []) do filter_module = Application.get_env(:sentry, :filter, Sentry.DefaultEventFilter) {source, opts} = Keyword.pop(opts, :event_source) - unless(filter_module.exclude_exception?(exception, source)) do + if(filter_module.exclude_exception?(exception, source)) do + :excluded + else Event.transform_exception(exception, opts) |> send_event() end diff --git a/test/sentry_test.exs b/test/sentry_test.exs index ecae8fa5..4bbb50fb 100644 --- a/test/sentry_test.exs +++ b/test/sentry_test.exs @@ -18,7 +18,7 @@ defmodule SentryTest do Application.put_env(:sentry, :included_environments, [:test]) Application.put_env(:sentry, :environment_name, :test) - Sentry.capture_exception(%RuntimeError{message: "error"}, [event_source: :plug]) - Sentry.capture_exception(%ArithmeticError{message: "error"}, [event_source: :plug]) + assert {:ok, _} = Sentry.capture_exception(%RuntimeError{message: "error"}, [event_source: :plug]) + assert :excluded = Sentry.capture_exception(%ArithmeticError{message: "error"}, [event_source: :plug]) end end From e695f448304f07000cdd9a08f5d9724994dab1cc Mon Sep 17 00:00:00 2001 From: Mitchell Henke Date: Thu, 8 Dec 2016 16:37:17 -0600 Subject: [PATCH 8/8] sentry docs --- docs/config.rst | 5 +++++ docs/index.rst | 27 +++++++++++++++++++++++++++ docs/usage.rst | 3 +++ 3 files changed, 35 insertions(+) diff --git a/docs/config.rst b/docs/config.rst index f39b5d7d..cdb558ad 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -52,6 +52,11 @@ Optional settings Set this to true if you want to capture all exceptions that occur even outside of a request cycle. This defaults to false. +.. describe:: filter + + Set this to a module that implements the ``Sentry.EventFilter`` behaviour if you would like to prevent + certain exceptions from being sent. See below for further documentation. + Testing Your Configuration -------------------------- diff --git a/docs/index.rst b/docs/index.rst index 0fc8cd1e..85618191 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -107,6 +107,33 @@ new process and it fails you might lose your context. That said using the contex # adds an breadcrumb to the request to help debug Sentry.Context.add_breadcrumb(%{my: "crumb"}) +Filtering Events +-------------- + +If you would like to prevent certain exceptions, the :filter configuration option +allows you to implement the ``Sentry.EventFilter`` behaviour. The first argument is the +source of the event, and the second is the exception to be sent. ``Sentry.Plug`` +will have a source of ``:plug``, and ``Sentry.Logger`` will have a source of ``:logger``. +If an exception does not come from either of those sources, the source will be nil +unless the ``:event_source`` option is passed to ``Sentry.capture_exception/2`` + +A configuration like below will prevent sending ``Phoenix.Router.NoRouteError`` from ``Sentry.Plug``, but +allows other exceptions to be sent. + +.. code-block:: elixir + # sentry_event_filter.exs + defmodule MyApp.SentryEventFilter do + @behaviour Sentry.EventFilter + + def exclude_exception?(:plug, %Elixir.Phoenix.Router.NoRouteError{}), do: true + def exclude_exception?(_, ), do: false + end + + # config.exs + config :sentry, filter: MyApp.SentryEventFilter, + included_environments: ~w(production staging), + environment_name: System.get_env("RELEASE_LEVEL") || "development" + Deep Dive --------- diff --git a/docs/usage.rst b/docs/usage.rst index 86d88559..c546be88 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -71,6 +71,9 @@ With calls to ``capture_exception`` additional data can be supplied as a keyword "email" => "clever-girl" } +.. describe:: event_source + + The source of the event. Used by the `Sentry.EventFilter` behaviour. Breadcrumbs -----------