Skip to content

Commit 095415b

Browse files
Merge pull request #355 from getsentry/fix-plug-duplication
fix Plug duplicate error
2 parents daf4c2e + 216cda4 commit 095415b

File tree

5 files changed

+73
-20
lines changed

5 files changed

+73
-20
lines changed

lib/sentry/logger_backend.ex

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,13 @@ defmodule Sentry.LoggerBackend do
3636
pairs with with binary, atom or number values from `Logger.metadata/0`
3737
and include that dictionary under the `:logger_metadata` key in an
3838
event's `:extra` metadata. This option defaults to `false`.
39+
* `:ignore_plug` - Enabling this option will ignore any events that
40+
appear to be from a Plug process crashing. This is to prevent
41+
duplicate errors being reported to Sentry alongside `Sentry.Plug`.
3942
"""
4043
@behaviour :gen_event
4144

42-
defstruct level: nil, include_logger_metadata: false
45+
defstruct level: nil, include_logger_metadata: false, ignore_plug: true
4346

4447
def init(__MODULE__) do
4548
config = Application.get_env(:logger, __MODULE__, [])
@@ -69,7 +72,7 @@ defmodule Sentry.LoggerBackend do
6972
end
7073

7174
def handle_event({_level, _gl, {Logger, _msg, _ts, meta}}, state) do
72-
%{include_logger_metadata: include_logger_metadata} = state
75+
%{include_logger_metadata: include_logger_metadata, ignore_plug: ignore_plug} = state
7376

7477
opts =
7578
if include_logger_metadata do
@@ -84,12 +87,20 @@ defmodule Sentry.LoggerBackend do
8487

8588
case Keyword.get(meta, :crash_reason) do
8689
{reason, stacktrace} ->
87-
opts =
88-
opts
89-
|> Keyword.put(:event_source, :logger)
90-
|> Keyword.put(:stacktrace, stacktrace)
91-
92-
Sentry.capture_exception(reason, opts)
90+
if ignore_plug &&
91+
Enum.any?(stacktrace, fn {module, function, arity, _file_line} ->
92+
match?({^module, ^function, ^arity}, {Plug.Cowboy.Handler, :init, 2}) ||
93+
match?({^module, ^function, ^arity}, {Phoenix.Endpoint.Cowboy2Handler, :init, 2})
94+
end) do
95+
:ok
96+
else
97+
opts =
98+
opts
99+
|> Keyword.put(:event_source, :logger)
100+
|> Keyword.put(:stacktrace, stacktrace)
101+
102+
Sentry.capture_exception(reason, opts)
103+
end
93104

94105
reason when is_atom(reason) and not is_nil(reason) ->
95106
Sentry.capture_exception(reason, [{:event_source, :logger} | opts])
@@ -122,9 +133,19 @@ defmodule Sentry.LoggerBackend do
122133
end
123134

124135
defp init(config, %__MODULE__{} = state) do
125-
level = Keyword.get(config, :level)
126-
include_logger_metadata = Keyword.get(config, :include_logger_metadata)
127-
%{state | level: level, include_logger_metadata: include_logger_metadata}
136+
level = Keyword.get(config, :level, state.level)
137+
138+
include_logger_metadata =
139+
Keyword.get(config, :include_logger_metadata, state.include_logger_metadata)
140+
141+
ignore_plug = Keyword.get(config, :ignore_plug, state.ignore_plug)
142+
143+
%{
144+
state
145+
| level: level,
146+
include_logger_metadata: include_logger_metadata,
147+
ignore_plug: ignore_plug
148+
}
128149
end
129150

130151
defp build_logger_metadata(meta) do

mix.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
33
"bypass": {:hex, :bypass, "1.0.0", "b78b3dcb832a71aca5259c1a704b2e14b55fd4e1327ff942598b4e7d1a7ad83d", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}], "hexpm"},
44
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
5-
"cowboy": {:hex, :cowboy, "2.6.1", "f2e06f757c337b3b311f9437e6e072b678fcd71545a7b2865bdaa154d078593f", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
6-
"cowlib": {:hex, :cowlib, "2.7.0", "3ef16e77562f9855a2605900cedb15c1462d76fb1be6a32fc3ae91973ee543d2", [:rebar3], [], "hexpm"},
5+
"cowboy": {:hex, :cowboy, "2.6.3", "99aa50e94e685557cad82e704457336a453d4abcb77839ad22dbe71f311fcc06", [:rebar3], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
6+
"cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm"},
77
"credo": {:hex, :credo, "1.1.0", "e0c07b2fd7e2109495f582430a1bc96b2c71b7d94c59dfad120529f65f19872f", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
88
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"},
99
"earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"},
@@ -20,8 +20,8 @@
2020
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
2121
"phoenix": {:hex, :phoenix, "1.4.0", "56fe9a809e0e735f3e3b9b31c1b749d4b436e466d8da627b8d82f90eaae714d2", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
2222
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm"},
23-
"plug": {:hex, :plug, "1.7.1", "8516d565fb84a6a8b2ca722e74e2cd25ca0fc9d64f364ec9dbec09d33eb78ccd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"},
24-
"plug_cowboy": {:hex, :plug_cowboy, "2.0.1", "d798f8ee5acc86b7d42dbe4450b8b0dadf665ce588236eb0a751a132417a980e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
23+
"plug": {:hex, :plug, "1.8.2", "0bcce1daa420f189a6491f3940cc77ea7fb1919761175c9c3b59800d897440fc", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
24+
"plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
2525
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
2626
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
2727
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},

test/logger_backend_test.exs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,38 @@ defmodule Sentry.LoggerBackendTest do
149149
end)
150150
end
151151

152+
test "only sends one error when a Plug process crashes" do
153+
Code.compile_string("""
154+
defmodule SentryApp do
155+
use Plug.Router
156+
use Plug.ErrorHandler
157+
use Sentry.Plug
158+
plug :match
159+
plug :dispatch
160+
forward("/", to: Sentry.ExampleApp)
161+
end
162+
""")
163+
164+
bypass = Bypass.open()
165+
pid = self()
166+
modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1")
167+
168+
{:ok, _plug_pid} = Plug.Cowboy.http(SentryApp, [], port: 8003)
169+
170+
Bypass.expect(bypass, fn conn ->
171+
{:ok, body, conn} = Plug.Conn.read_body(conn)
172+
_json = Jason.decode!(body)
173+
send(pid, "API called")
174+
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
175+
end)
176+
177+
capture_log(fn ->
178+
:hackney.get("http://127.0.0.1:8003/error_route", [], "", [])
179+
assert_receive "API called"
180+
refute_receive "API called"
181+
end)
182+
end
183+
152184
if :erlang.system_info(:otp_release) >= '21' do
153185
test "includes Logger.metadata when enabled if the key and value are safely JSON-encodable" do
154186
Logger.configure_backend(Sentry.LoggerBackend, include_logger_metadata: true)

test/plug_test.exs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ defmodule Sentry.PlugTest do
4444

4545
conn = conn(:post, "/error_route", %{})
4646

47-
assert_raise(RuntimeError, "Error", fn ->
47+
assert_raise(Plug.Conn.WrapperError, "** (RuntimeError) Error", fn ->
4848
OverrideApp.call(conn, [])
4949
end)
5050

@@ -75,7 +75,7 @@ defmodule Sentry.PlugTest do
7575

7676
modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1")
7777

78-
assert_raise(RuntimeError, "Error", fn ->
78+
assert_raise(Plug.Conn.WrapperError, "** (RuntimeError) Error", fn ->
7979
conn(:post, "/error_route", %{
8080
"secret" => "world",
8181
"password" => "test",
@@ -120,7 +120,7 @@ defmodule Sentry.PlugTest do
120120
modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1")
121121
upload = %Plug.Upload{path: "test/fixtures/my_image.png", filename: "my_image.png"}
122122

123-
assert_raise(RuntimeError, "Error", fn ->
123+
assert_raise(Plug.Conn.WrapperError, "** (RuntimeError) Error", fn ->
124124
conn(:post, "/error_route", %{"image" => upload, "password" => "my_password"})
125125
|> put_req_cookie("cookie_key", "cookie_value")
126126
|> put_req_header("accept-language", "en-US")
@@ -154,7 +154,7 @@ defmodule Sentry.PlugTest do
154154

155155
modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1")
156156

157-
assert_raise(RuntimeError, "Error", fn ->
157+
assert_raise(Plug.Conn.WrapperError, "** (RuntimeError) Error", fn ->
158158
conn(:get, "/error_route")
159159
|> update_req_cookie("secret", "secretvalue")
160160
|> update_req_cookie("regular", "value")

test/sources_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ defmodule Sentry.SourcesTest do
4545

4646
modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1")
4747

48-
assert_raise(RuntimeError, "Error", fn ->
48+
assert_raise(Plug.Conn.WrapperError, "** (RuntimeError) Error", fn ->
4949
conn(:get, "/error_route")
5050
|> SourcesApp.call([])
5151
end)

0 commit comments

Comments
 (0)