Skip to content

Commit dcbd349

Browse files
authored
Don't report events if DSN is not configured (#655)
1 parent ac0aa94 commit dcbd349

14 files changed

+132
-128
lines changed

lib/mix/tasks/sentry.send_test_event.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ defmodule Mix.Tasks.Sentry.SendTestEvent do
4747
Mix.shell().info("Client configuration:")
4848

4949
if Config.dsn() do
50-
{endpoint, public_key, secret_key} = Sentry.Transport.get_dsn()
50+
{endpoint, public_key, secret_key} = Config.dsn()
5151
Mix.shell().info("server: #{endpoint}")
5252
Mix.shell().info("public_key: #{public_key}")
5353
Mix.shell().info("secret_key: #{secret_key}")

lib/sentry.ex

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -318,22 +318,23 @@ defmodule Sentry do
318318
> use cases, use `capture_exception/2` or `capture_message/2`.
319319
"""
320320
@spec send_event(Event.t(), keyword()) :: send_result
321-
def send_event(event, opts \\ [])
321+
def send_event(event, opts \\ []) do
322+
# TODO: remove on v11.0.0, :included_environments was deprecated in 10.0.0.
323+
included_envs = Config.included_environments()
322324

323-
def send_event(%Event{message: nil, exception: []}, _opts) do
324-
Logger.log(Config.log_level(), "Sentry: unable to parse exception")
325+
cond do
326+
is_nil(event.message) and event.exception == [] ->
327+
Logger.log(Config.log_level(), "Sentry: unable to parse exception")
328+
:ignored
325329

326-
:ignored
327-
end
330+
!Config.dsn() ->
331+
:ignored
328332

329-
def send_event(%Event{} = event, opts) do
330-
included_environments = Config.included_environments()
331-
environment_name = to_string(Config.environment_name())
333+
included_envs == :all or to_string(Config.environment_name()) in included_envs ->
334+
Sentry.Client.send_event(event, opts)
332335

333-
if included_environments == :all or environment_name in included_environments do
334-
Sentry.Client.send_event(event, opts)
335-
else
336-
:ignored
336+
true ->
337+
:ignored
337338
end
338339
end
339340

lib/sentry/client.ex

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,6 @@ defmodule Sentry.Client do
224224
defp maybe_log_send_result(send_result, %Event{}) do
225225
message =
226226
case send_result do
227-
{:error, :invalid_dsn} ->
228-
"Cannot send Sentry event because of invalid DSN"
229-
230227
{:error, {:invalid_json, error}} ->
231228
"Unable to encode JSON Sentry error - #{inspect(error)}"
232229

lib/sentry/config.ex

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule Sentry.Config do
33

44
basic_opts_schema = [
55
dsn: [
6-
type: {:or, [:string, nil]},
6+
type: {:or, [nil, {:custom, __MODULE__, :__validate_string_dsn__, []}]},
77
default: nil,
88
type_doc: "`t:String.t/0` or `nil`",
99
doc: """
@@ -316,7 +316,6 @@ defmodule Sentry.Config do
316316
opts
317317
|> normalize_included_environments()
318318
|> normalize_environment()
319-
|> assert_dsn_has_no_query_params!()
320319
|> handle_deprecated_before_send()
321320

322321
{:error, error} ->
@@ -365,7 +364,7 @@ defmodule Sentry.Config do
365364
"""
366365
end
367366

368-
@spec dsn() :: String.t() | nil
367+
@spec dsn() :: nil | {String.t(), String.t(), String.t()}
369368
def dsn, do: get(:dsn)
370369

371370
# TODO: remove me on v11.0.0, :included_environments has been deprecated
@@ -500,35 +499,6 @@ defmodule Sentry.Config do
500499
Keyword.update!(config, :environment_name, &to_string/1)
501500
end
502501

503-
defp assert_dsn_has_no_query_params!(config) do
504-
if sentry_dsn = Keyword.get(config, :dsn) do
505-
uri_dsn = URI.parse(sentry_dsn)
506-
507-
if uri_dsn.query do
508-
raise ArgumentError, """
509-
using a Sentry DSN with query parameters is not supported since v9.0.0 of this library.
510-
The configured DSN was:
511-
512-
#{inspect(sentry_dsn)}
513-
514-
The query string in that DSN is:
515-
516-
#{inspect(uri_dsn.query)}
517-
518-
Please remove the query parameters from your DSN and pass them in as regular
519-
configuration. Check out the guide to upgrade to 9.0.0 at:
520-
521-
https://hexdocs.pm/sentry/upgrade-9.x.html
522-
523-
See the documentation for the Sentry module for more information on configuration
524-
in general.
525-
"""
526-
end
527-
end
528-
529-
config
530-
end
531-
532502
@compile {:inline, fetch!: 1}
533503
defp fetch!(key) do
534504
:persistent_term.get({:sentry_config, key})
@@ -583,4 +553,69 @@ defmodule Sentry.Config do
583553
{:error, "expected #{inspect(key)} to be a #{inspect(mod)} struct, got: #{inspect(term)}"}
584554
end
585555
end
556+
557+
def __validate_string_dsn__(dsn) when is_binary(dsn) do
558+
uri = URI.parse(dsn)
559+
560+
if uri.query do
561+
raise ArgumentError, """
562+
using a Sentry DSN with query parameters is not supported since v9.0.0 of this library.
563+
The configured DSN was:
564+
565+
#{inspect(dsn)}
566+
567+
The query string in that DSN is:
568+
569+
#{inspect(uri.query)}
570+
571+
Please remove the query parameters from your DSN and pass them in as regular
572+
configuration. Check out the guide to upgrade to 9.0.0 at:
573+
574+
https://hexdocs.pm/sentry/upgrade-9.x.html
575+
576+
See the documentation for the Sentry module for more information on configuration
577+
in general.
578+
"""
579+
end
580+
581+
unless is_binary(uri.path) do
582+
throw("missing project ID at the end of the DSN URI: #{inspect(dsn)}")
583+
end
584+
585+
unless is_binary(uri.userinfo) do
586+
throw("missing user info in the DSN URI: #{inspect(dsn)}")
587+
end
588+
589+
{public_key, secret_key} =
590+
case String.split(uri.userinfo, ":", parts: 2) do
591+
[public, secret] -> {public, secret}
592+
[public] -> {public, nil}
593+
end
594+
595+
with {:ok, {base_path, project_id}} <- pop_project_id(uri.path) do
596+
new_path = Enum.join([base_path, "api", project_id, "envelope"], "/") <> "/"
597+
endpoint_uri = URI.merge(%URI{uri | userinfo: nil}, new_path)
598+
599+
{:ok, {URI.to_string(endpoint_uri), public_key, secret_key}}
600+
end
601+
catch
602+
message -> {:error, message}
603+
end
604+
605+
def __validate_string_dsn__(other) do
606+
{:error, "expected :dsn to be a string or nil, got: #{inspect(other)}"}
607+
end
608+
609+
defp pop_project_id(uri_path) do
610+
path = String.split(uri_path, "/")
611+
{project_id, path} = List.pop_at(path, -1)
612+
613+
case Integer.parse(project_id) do
614+
{_project_id, ""} ->
615+
{:ok, {Enum.join(path, "/"), project_id}}
616+
617+
_other ->
618+
{:error, "expected the DSN path to end with an integer project ID, got: #{inspect(path)}"}
619+
end
620+
end
586621
end

lib/sentry/transport.ex

Lines changed: 11 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ defmodule Sentry.Transport do
2020
{:ok, envelope_id :: String.t()} | {:error, term()}
2121
def post_envelope(%Envelope{} = envelope, client, retries \\ @default_retries)
2222
when is_atom(client) and is_list(retries) do
23-
with {:json, {:ok, body}} <- {:json, Envelope.to_binary(envelope)},
24-
{:ok, endpoint, headers} <- get_endpoint_and_headers() do
25-
post_envelope_with_retries(client, endpoint, headers, body, retries)
26-
else
27-
{:json, {:error, reason}} -> {:error, {:invalid_json, reason}}
28-
{:error, _reason} = error -> error
23+
case Envelope.to_binary(envelope) do
24+
{:ok, body} ->
25+
{endpoint, headers} = get_endpoint_and_headers()
26+
post_envelope_with_retries(client, endpoint, headers, body, retries)
27+
28+
{:error, reason} ->
29+
{:error, {:invalid_json, reason}}
2930
end
3031
end
3132

@@ -64,43 +65,8 @@ defmodule Sentry.Transport do
6465
end
6566

6667
defp get_endpoint_and_headers do
67-
case get_dsn() do
68-
{endpoint, public_key, secret_key} ->
69-
{:ok, endpoint, authorization_headers(public_key, secret_key)}
70-
71-
{:error, :invalid_dsn} ->
72-
{:error, :invalid_dsn}
73-
end
74-
end
75-
76-
# Made public for testing.
77-
@spec get_dsn() :: {String.t(), String.t(), String.t()} | {:error, :invalid_dsn}
78-
def get_dsn do
79-
with dsn when is_binary(dsn) <- Config.dsn(),
80-
%URI{userinfo: userinfo, host: host, port: port, path: path, scheme: protocol}
81-
when is_binary(path) and is_binary(userinfo) <- URI.parse(dsn),
82-
[public_key, secret_key] <- keys_from_userinfo(userinfo),
83-
uri_path <- String.split(path, "/"),
84-
{binary_project_id, uri_path} <- List.pop_at(uri_path, -1),
85-
base_path <- Enum.join(uri_path, "/"),
86-
{project_id, ""} <- Integer.parse(binary_project_id),
87-
endpoint <- "#{protocol}://#{host}:#{port}#{base_path}/api/#{project_id}/envelope/" do
88-
{endpoint, public_key, secret_key}
89-
else
90-
_ ->
91-
{:error, :invalid_dsn}
92-
end
93-
end
68+
{endpoint, public_key, secret_key} = Config.dsn()
9469

95-
defp keys_from_userinfo(userinfo) do
96-
case String.split(userinfo, ":", parts: 2) do
97-
[public, secret] -> [public, secret]
98-
[public] -> [public, nil]
99-
_ -> :error
100-
end
101-
end
102-
103-
defp authorization_headers(public_key, secret_key) do
10470
auth_query =
10571
[
10672
sentry_version: @sentry_version,
@@ -112,9 +78,11 @@ defmodule Sentry.Transport do
11278
|> Enum.reject(fn {_, value} -> is_nil(value) end)
11379
|> Enum.map_join(", ", fn {name, value} -> "#{name}=#{value}" end)
11480

115-
[
81+
auth_headers = [
11682
{"User-Agent", @sentry_client},
11783
{"X-Sentry-Auth", "Sentry " <> auth_query}
11884
]
85+
86+
{endpoint, auth_headers}
11987
end
12088
end

lib/sentry/transport/sender.ex

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ defmodule Sentry.Transport.Sender do
5454
else
5555
message =
5656
case send_result do
57-
{:error, :invalid_dsn} ->
58-
"Cannot send Sentry event because of invalid DSN"
59-
6057
{:error, {:invalid_json, error}} ->
6158
"Unable to encode JSON Sentry error - #{inspect(error)}"
6259

test/logger_backend_test.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,8 @@ defmodule Sentry.LoggerBackendTest do
343343
before_send: fn event ->
344344
send(pid, {ref, event})
345345
false
346-
end
346+
end,
347+
dsn: "http://public:secret@localhost:9392/1"
347348
)
348349

349350
ref

test/sentry/config_test.exs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ defmodule Sentry.ConfigTest do
55

66
describe "validate!/0" do
77
test ":dsn from option" do
8-
dsn = "https://public:[email protected]/1"
9-
assert Config.validate!(dsn: dsn)[:dsn] == dsn
8+
assert Config.validate!(dsn: "https://public:[email protected]/1")[:dsn] ==
9+
{"https://app.getsentry.com/api/1/envelope/", "public", "secret"}
10+
11+
assert Config.validate!(dsn: nil)[:dsn] == nil
1012
end
1113

1214
test ":dsn from system environment" do
13-
dsn = "https://public:[email protected]/1"
14-
15-
with_system_env("SENTRY_DSN", dsn, fn ->
16-
assert Config.validate!([])[:dsn] == dsn
15+
with_system_env("SENTRY_DSN", "https://public:[email protected]/1", fn ->
16+
assert Config.validate!([])[:dsn] ==
17+
{"https://app.getsentry.com/api/1/envelope/", "public", "secret"}
1718
end)
1819
end
1920

@@ -26,9 +27,27 @@ defmodule Sentry.ConfigTest do
2627
end
2728

2829
test "invalid :dsn" do
30+
# Not a string.
2931
assert_raise ArgumentError, ~r/invalid value for :dsn option/, fn ->
3032
Config.validate!(dsn: :not_a_string)
3133
end
34+
35+
# Project ID is missing.
36+
assert_raise ArgumentError, ~r/missing project ID at the end of the DSN URI/, fn ->
37+
Config.validate!(dsn: "https://public:[email protected]")
38+
end
39+
40+
# Project ID is not an integer.
41+
assert_raise ArgumentError, ~r/DSN path to end with an integer project ID/, fn ->
42+
Config.validate!(dsn: "https://public:[email protected]/not-an-int")
43+
end
44+
45+
# Userinfo is missing.
46+
for dsn <- ["https://app.getsentry.com/1", "https://@app.getsentry.com/1"] do
47+
assert_raise ArgumentError, ~r/missing user info in the DSN URI/, fn ->
48+
Config.validate!(dsn: dsn)
49+
end
50+
end
3251
end
3352

3453
test ":release from option" do
@@ -186,7 +205,8 @@ defmodule Sentry.ConfigTest do
186205
new_dsn = "https://public:[email protected]/2"
187206
assert :ok = Config.put_config(:dsn, new_dsn)
188207

189-
assert Config.dsn() == new_dsn
208+
assert Config.dsn() ==
209+
{"https://app.getsentry.com/api/2/envelope/", "public", "secret"}
190210
end
191211

192212
test "validates the given key" do

test/sentry/logger_handler_test.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,8 @@ defmodule Sentry.LoggerHandlerTest do
332332
before_send: fn event ->
333333
send(pid, {ref, event})
334334
false
335-
end
335+
end,
336+
dsn: "http://public:secret@localhost:9392/1"
336337
)
337338

338339
%{sender_ref: ref}

test/sentry/transport_test.exs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -192,26 +192,4 @@ defmodule Sentry.TransportTest do
192192
assert_received {:request, ^ref}
193193
end
194194
end
195-
196-
describe "get_dsn/0" do
197-
test "parses correct DSNs" do
198-
put_test_config(dsn: "http://public:secret@localhost:3000/1")
199-
assert {"http://localhost:3000/api/1/envelope/", "public", "secret"} = Transport.get_dsn()
200-
end
201-
202-
test "errors on bad public keys" do
203-
put_test_config(dsn: "https://app.getsentry.com/1")
204-
assert {:error, :invalid_dsn} = Transport.get_dsn()
205-
end
206-
207-
test "errors on non-integer project_id" do
208-
put_test_config(dsn: "https://public:[email protected]/Mitchell")
209-
assert {:error, :invalid_dsn} = Transport.get_dsn()
210-
end
211-
212-
test "errors on no project_id" do
213-
put_test_config(dsn: "https://public:[email protected]")
214-
assert {:error, :invalid_dsn} = Transport.get_dsn()
215-
end
216-
end
217195
end

0 commit comments

Comments
 (0)