Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## master

* Enhancements
* Allow filtering which exceptions are sent via `Sentry.EventFilter` behaviour

* Bug Fixes
* Fix usage of deprecated modules

Expand Down
5 changes: 5 additions & 0 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------------------------

Expand Down
27 changes: 27 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------

Expand Down
3 changes: 3 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----------
Expand Down
44 changes: 39 additions & 5 deletions lib/sentry.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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.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.

# 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"

## 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.EventFilter.exclude_exception?/2`

## Configuring The `Logger` Backend

Expand All @@ -82,11 +111,16 @@ 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
exception
|> Event.transform_exception(opts)
|> send_event()
filter_module = Application.get_env(:sentry, :filter, Sentry.DefaultEventFilter)
{source, opts} = Keyword.pop(opts, :event_source)
if(filter_module.exclude_exception?(exception, source)) do
:excluded
else
Event.transform_exception(exception, opts)
|> send_event()
end
end

@doc """
Expand Down
8 changes: 8 additions & 0 deletions lib/sentry/event_filter.ex
Original file line number Diff line number Diff line change
@@ -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
6 changes: 4 additions & 2 deletions lib/sentry/logger.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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 ->
Expand Down
2 changes: 1 addition & 1 deletion lib/sentry/plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 22 additions & 1 deletion test/sentry_test.exs
Original file line number Diff line number Diff line change
@@ -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)

assert {:ok, _} = Sentry.capture_exception(%RuntimeError{message: "error"}, [event_source: :plug])
assert :excluded = Sentry.capture_exception(%ArithmeticError{message: "error"}, [event_source: :plug])
end
end
6 changes: 6 additions & 0 deletions test/support/test_filter.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
defmodule Sentry.TestFilter do
@behaviour Sentry.EventFilter

def exclude_exception?(%ArithmeticError{}, :plug), do: true
def exclude_exception?(_, _), do: false
end