Skip to content

Commit 8247f62

Browse files
Merge pull request #402 from getsentry/plug-capture
PlugCapture and PlugContext
2 parents 3bb8d1a + d9bf43c commit 8247f62

25 files changed

+627
-540
lines changed

.formatter.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[
2+
import_deps: [:plug],
23
inputs: [
34
"lib/**/*.ex",
45
"config/*.exs",

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
* Change default event send type to :none instead of :async (#341)
1414
* Make hackney an optional dependency, and simplify Sentry.HTTPClient behaviour (#400)
1515
* Use Logger.metadata for Sentry.Context, no longer return metadata values on set_* functions, and rename `set_http_context` to `set_request_context`
16+
* Move excluded exceptions from Sentry.Plug to Sentry.DefaultEventFilter
17+
* Remove Sentry.Plug and Sentry.Phoenix.Endpoint in favor of Sentry.PlugContext and Sentry.PlugCapture
18+
* Remove feedback form rendering and configuration
1619

1720
## 7.2.4 (2020-03-09)
1821

README.md

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,10 @@
22

33
[![Build Status](https://img.shields.io/travis/getsentry/sentry-elixir.svg?style=flat)](https://travis-ci.org/getsentry/sentry-elixir)
44
[![hex.pm version](https://img.shields.io/hexpm/v/sentry.svg?style=flat)](https://hex.pm/packages/sentry)
5-
6-
The Official Sentry Client for Elixir which provides a simple API to capture exceptions, automatically handle Plug Exceptions and provides a backend for the Elixir Logger.
7-
85
[Documentation](https://hexdocs.pm/sentry/readme.html)
96

10-
## Note on upgrading from Sentry 6.x to 7.x
11-
12-
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.
7+
The Official Sentry Client for Elixir which provides a simple API to capture exceptions, automatically handle Plug Exceptions and provides a backend for the Elixir Logger. This documentation represents unreleased features, for documentation on the current release, see [here](https://hexdocs.pm/sentry/readme.html).
138

14-
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).
15-
16-
If you would like to upgrade a project to use Sentry 7.x, see [here](https://gist.github.com/mitchellhenke/4ab6dd8d0ebeaaf9821fb625e0037a4d).
179

1810
## Installation
1911

@@ -30,26 +22,67 @@ defp deps do
3022
end
3123
```
3224

33-
### Setup with Plug or Phoenix
25+
### Setup with Plug and Phoenix
26+
Capturing errors in Plug applications is done with `Sentry.PlugContext` and `Sentry.PlugCapture`. `Sentry.PlugContext` adds contextual metadata from the current request which is then included in errors that are captured and reported by `Sentry.PlugCapture`.
3427

35-
In your Plug.Router or Phoenix.Router, add the following lines:
28+
If you are using Phoenix, first add `Sentry.PlugCapture` above the `use Phoenix.Endpoint` line in your endpoint file. `Sentry.PlugContext` should be added below `Plug.Parsers`.
3629

3730
```diff
38-
# lib/my_app_web/router.ex
39-
defmodule MyAppWeb.Router do
40-
use MyAppWeb, :router
41-
+ use Plug.ErrorHandler
42-
+ use Sentry.Plug
31+
defmodule MyAppWeb.Endpoint
32+
+ use Sentry.PlugCapture
33+
use Phoenix.Endpoint, otp_app: :my_app
34+
# ...
35+
plug Plug.Parsers,
36+
parsers: [:urlencoded, :multipart, :json],
37+
pass: ["*/*"],
38+
json_decoder: Phoenix.json_library()
39+
40+
+ plug Sentry.PlugContext
4341
```
4442

45-
If you are using Phoenix, you can also include [Sentry.Phoenix.Endpoint](https://hexdocs.pm/sentry/Sentry.Phoenix.Endpoint.html) in your Endpoint. This module captures errors occurring in the Phoenix pipeline before the request reaches the Router:
43+
If you are in a non-Phoenix Plug application, add `Sentry.PlugCapture` at the top of your Plug application, and add `Sentry.PlugContext` below `Plug.Parsers` (if it is in your stack).
4644

4745
```diff
48-
use Phoenix.Endpoint, otp_app: :my_app
49-
+use Sentry.Phoenix.Endpoint
46+
defmodule MyApp.Router do
47+
+ use Sentry.PlugCapture
48+
use Plug.Router
49+
# ...
50+
plug Plug.Parsers,
51+
parsers: [:urlencoded, :multipart]
52+
+ plug Sentry.PlugContext
5053
```
5154

52-
More information on why this may be necessary can be found here: https://github.com/getsentry/sentry-elixir/issues/229 and https://github.com/phoenixframework/phoenix/issues/2791
55+
#### Capturing User Feedback
56+
57+
If you would like to capture user feedback as described [here](https://docs.sentry.io/enriching-error-data/user-feedback), the `Sentry.get_last_event_id_and_source()` function can be used to see if Sentry has sent an event within the current Plug process, and the source of that event. `:plug` will be the source for events coming from `Sentry.PlugCapture`. The options described in the Sentry documentation linked above can be encoded into the response as well.
58+
59+
An example Phoenix application setup that wanted to display the user feedback form on 500 responses on requests accepting HTML could look like:
60+
61+
```elixir
62+
defmodule MyAppWeb.ErrorView do
63+
# ...
64+
def render("500.html", _assigns) do
65+
case Sentry.get_last_event_id_and_source() do
66+
{event_id, :plug} when is_binary(event_id) ->
67+
opts =
68+
# can do %{eventId: event_id, title: "My custom title"}
69+
%{eventId: event_id}
70+
|> Jason.encode!()
71+
72+
~E"""
73+
<script src="https://browser.sentry-cdn.com/5.9.1/bundle.min.js" integrity="sha384-/x1aHz0nKRd6zVUazsV6CbQvjJvr6zQL2CHbQZf3yoLkezyEtZUpqUNnOLW9Nt3v" crossorigin="anonymous"></script>
74+
<script>
75+
Sentry.init({ dsn: '<%= Sentry.Config.dsn() %>' });
76+
Sentry.showReportDialog(<%= raw opts %>)
77+
</script>
78+
"""
79+
80+
_ ->
81+
"Error"
82+
end
83+
# ...
84+
end
85+
```
5386

5487
### Capture Crashed Process Exceptions
5588

config/test.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ config :sentry,
99

1010
config :ex_unit,
1111
assert_receive_timeout: 500
12+
13+
config :phoenix, :json_library, Jason

lib/sentry.ex

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,25 @@ defmodule Sentry do
126126
end
127127
end
128128

129+
@doc """
130+
Puts the last event ID sent to the server for the current process in
131+
the process dictionary.
132+
"""
133+
@spec put_last_event_id_and_source(String.t()) :: {String.t(), atom() | nil} | nil
134+
def put_last_event_id_and_source(event_id, source \\ nil) when is_binary(event_id) do
135+
Process.put(:sentry_last_event_id_and_source, {event_id, source})
136+
end
137+
138+
@doc """
139+
Gets the last event ID sent to the server from the process dictionary.
140+
Since it uses the process dictionary, it will only return the last event
141+
ID sent within the current process.
142+
"""
143+
@spec get_last_event_id_and_source() :: {String.t(), atom() | nil} | nil
144+
def get_last_event_id_and_source do
145+
Process.get(:sentry_last_event_id_and_source)
146+
end
147+
129148
@doc """
130149
Reports a message to Sentry.
131150

lib/sentry/client.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ defmodule Sentry.Client do
119119
maybe_log_result(result)
120120
end
121121

122+
if match?({:ok, _}, result) do
123+
Sentry.put_last_event_id_and_source(event.event_id, event.event_source)
124+
end
125+
122126
result
123127

124128
{:error, error} ->

lib/sentry/default_event_filter.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,11 @@ defmodule Sentry.DefaultEventFilter do
33

44
@moduledoc false
55

6+
def exclude_exception?(%x{}, :plug) when x in [Phoenix.Router.NoRouteError] do
7+
true
8+
end
9+
10+
def exclude_exception?(%FunctionClauseError{function: :do_match, arity: 4}, :plug), do: true
11+
612
def exclude_exception?(_, _), do: false
713
end

lib/sentry/event_filter.ex

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ defmodule Sentry.EventFilter do
33
This module defines a Behaviour for filtering Sentry events.
44
There is one callback to implement. The first argument will be
55
the exception reported, and the second is the source. Events
6-
from `Sentry.Plug` will have :plug as a source, `Sentry.Phoenix.Endpoint`
7-
will have `:endpoint` and events from `Sentry.LoggerBackend` will have
8-
`:logger` as the source. A custom source can also be specified by passing
9-
the `event_source` option to `Sentry.capture_exception/2`.
6+
from `Sentry.PlugCapture` will have :plug as a source and events from
7+
`Sentry.LoggerBackend` will have `:logger` as the source. A custom
8+
source can also be specified by passing the `event_source` option
9+
to `Sentry.capture_exception/2`.
1010
1111
As an example, if you wanted to exclude any `ArithmeticError` exceptions:
1212
@@ -16,6 +16,8 @@ defmodule Sentry.EventFilter do
1616
def exclude_exception?(%ArithmeticError{}, _source), do: true
1717
def exclude_exception?(_exception, _source), do: false
1818
end
19+
20+
Sentry uses `Sentry.DefaultEventFilter` by default.
1921
"""
2022

2123
@doc """

lib/sentry/phoenix_endpoint.ex

Lines changed: 0 additions & 52 deletions
This file was deleted.

lib/sentry/plug_capture.ex

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
if Code.ensure_loaded?(Plug) do
2+
defmodule Sentry.PlugCapture do
3+
@moduledoc """
4+
Provides basic functionality to handle and send errors occurring within
5+
Plug applications, including Phoenix.
6+
It is intended for usage with `Sentry.PlugContext`.
7+
8+
#### Usage
9+
In a Phoenix application, it is important to use this module before
10+
the Phoenix endpoint itself. It should be added to your endpoint.ex:
11+
12+
13+
defmodule MyApp.Endpoint
14+
use Sentry.PlugCapture
15+
use Phoenix.Endpoint, otp_app: :my_app
16+
# ...
17+
end
18+
19+
In a Plug application, it can be added below your router:
20+
21+
defmodule MyApp.PlugRouter do
22+
use Plug.Router
23+
use Sentry.PlugCapture
24+
# ...
25+
end
26+
"""
27+
defmacro __using__(_opts) do
28+
quote do
29+
@before_compile Sentry.PlugCapture
30+
end
31+
end
32+
33+
defmacro __before_compile__(_) do
34+
quote do
35+
defoverridable call: 2
36+
37+
def call(conn, opts) do
38+
try do
39+
super(conn, opts)
40+
rescue
41+
e in Plug.Conn.WrapperError ->
42+
Sentry.capture_exception(e.reason, stacktrace: e.stack, event_source: :plug)
43+
Plug.Conn.WrapperError.reraise(e)
44+
45+
e ->
46+
Sentry.capture_exception(e, stacktrace: __STACKTRACE__, event_source: :plug)
47+
:erlang.raise(:error, e, __STACKTRACE__)
48+
end
49+
end
50+
end
51+
end
52+
end
53+
end

0 commit comments

Comments
 (0)