Skip to content

Commit dfd9a9e

Browse files
authored
Expose DSN via new Sentry.get_dsn/0 (#731)
1 parent 4267098 commit dfd9a9e

File tree

7 files changed

+154
-82
lines changed

7 files changed

+154
-82
lines changed

lib/mix/tasks/sentry.send_test_event.ex

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,10 @@ defmodule Mix.Tasks.Sentry.SendTestEvent do
6060
defp print_environment_info do
6161
Mix.shell().info("Client configuration:")
6262

63-
if Config.dsn() do
64-
{endpoint, public_key, secret_key} = Config.dsn()
65-
Mix.shell().info("server: #{endpoint}")
66-
Mix.shell().info("public_key: #{public_key}")
67-
Mix.shell().info("secret_key: #{secret_key}")
63+
if dsn = Config.dsn() do
64+
Mix.shell().info("server: #{dsn.endpoint_uri}")
65+
Mix.shell().info("public_key: #{dsn.public_key}")
66+
Mix.shell().info("secret_key: #{dsn.secret_key}")
6867
end
6968

7069
Mix.shell().info("current environment_name: #{inspect(to_string(Config.environment_name()))}")

lib/sentry.ex

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,4 +438,18 @@ defmodule Sentry do
438438
@doc since: "10.0.0"
439439
@spec put_config(atom(), term()) :: :ok
440440
defdelegate put_config(key, value), to: Config
441+
442+
@doc """
443+
Returns the currently-set Sentry DSN, *if set* (or `nil` otherwise).
444+
445+
This is useful in situations like capturing user feedback.
446+
"""
447+
@doc since: "10.6.0"
448+
@spec get_dsn() :: String.t() | nil
449+
def get_dsn do
450+
case Config.dsn() do
451+
%Sentry.DSN{original_dsn: original_dsn} -> original_dsn
452+
nil -> nil
453+
end
454+
end
441455
end

lib/sentry/config.ex

Lines changed: 2 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ defmodule Sentry.Config do
6868

6969
basic_opts_schema = [
7070
dsn: [
71-
type: {:or, [nil, {:custom, __MODULE__, :__validate_string_dsn__, []}]},
71+
type: {:or, [nil, {:custom, Sentry.DSN, :parse, []}]},
7272
default: nil,
7373
type_doc: "`t:String.t/0` or `nil`",
7474
doc: """
@@ -459,7 +459,7 @@ defmodule Sentry.Config do
459459
"""
460460
end
461461

462-
@spec dsn() :: nil | {String.t(), String.t(), String.t()}
462+
@spec dsn() :: nil | Sentry.DSN.t()
463463
def dsn, do: get(:dsn)
464464

465465
# TODO: remove me on v11.0.0, :included_environments has been deprecated
@@ -671,69 +671,4 @@ defmodule Sentry.Config do
671671
{:error, "expected #{inspect(key)} to be a #{inspect(mod)} struct, got: #{inspect(term)}"}
672672
end
673673
end
674-
675-
def __validate_string_dsn__(dsn) when is_binary(dsn) do
676-
uri = URI.parse(dsn)
677-
678-
if uri.query do
679-
raise ArgumentError, """
680-
using a Sentry DSN with query parameters is not supported since v9.0.0 of this library.
681-
The configured DSN was:
682-
683-
#{inspect(dsn)}
684-
685-
The query string in that DSN is:
686-
687-
#{inspect(uri.query)}
688-
689-
Please remove the query parameters from your DSN and pass them in as regular
690-
configuration. Check out the guide to upgrade to 9.0.0 at:
691-
692-
https://hexdocs.pm/sentry/upgrade-9.x.html
693-
694-
See the documentation for the Sentry module for more information on configuration
695-
in general.
696-
"""
697-
end
698-
699-
unless is_binary(uri.path) do
700-
throw("missing project ID at the end of the DSN URI: #{inspect(dsn)}")
701-
end
702-
703-
unless is_binary(uri.userinfo) do
704-
throw("missing user info in the DSN URI: #{inspect(dsn)}")
705-
end
706-
707-
{public_key, secret_key} =
708-
case String.split(uri.userinfo, ":", parts: 2) do
709-
[public, secret] -> {public, secret}
710-
[public] -> {public, nil}
711-
end
712-
713-
with {:ok, {base_path, project_id}} <- pop_project_id(uri.path) do
714-
new_path = Enum.join([base_path, "api", project_id, "envelope"], "/") <> "/"
715-
endpoint_uri = URI.merge(%URI{uri | userinfo: nil}, new_path)
716-
717-
{:ok, {URI.to_string(endpoint_uri), public_key, secret_key}}
718-
end
719-
catch
720-
message -> {:error, message}
721-
end
722-
723-
def __validate_string_dsn__(other) do
724-
{:error, "expected :dsn to be a string or nil, got: #{inspect(other)}"}
725-
end
726-
727-
defp pop_project_id(uri_path) do
728-
path = String.split(uri_path, "/")
729-
{project_id, path} = List.pop_at(path, -1)
730-
731-
case Integer.parse(project_id) do
732-
{_project_id, ""} ->
733-
{:ok, {Enum.join(path, "/"), project_id}}
734-
735-
_other ->
736-
{:error, "expected the DSN path to end with an integer project ID, got: #{inspect(path)}"}
737-
end
738-
end
739674
end

lib/sentry/dsn.ex

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
defmodule Sentry.DSN do
2+
@moduledoc false
3+
4+
@type t() :: %__MODULE__{
5+
original_dsn: String.t(),
6+
endpoint_uri: String.t(),
7+
public_key: String.t(),
8+
secret_key: String.t() | nil
9+
}
10+
11+
defstruct [
12+
:original_dsn,
13+
:endpoint_uri,
14+
:public_key,
15+
:secret_key
16+
]
17+
18+
# {PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}{PATH}/{PROJECT_ID}
19+
@spec parse(String.t()) :: {:ok, t()} | {:error, String.t()}
20+
def parse(term)
21+
22+
def parse(dsn) when is_binary(dsn) do
23+
uri = URI.parse(dsn)
24+
25+
if uri.query do
26+
raise ArgumentError, """
27+
using a Sentry DSN with query parameters is not supported since v9.0.0 of this library.
28+
The configured DSN was:
29+
30+
#{inspect(dsn)}
31+
32+
The query string in that DSN is:
33+
34+
#{inspect(uri.query)}
35+
36+
Please remove the query parameters from your DSN and pass them in as regular
37+
configuration. Check out the guide to upgrade to 9.0.0 at:
38+
39+
https://hexdocs.pm/sentry/upgrade-9.x.html
40+
41+
See the documentation for the Sentry module for more information on configuration
42+
in general.
43+
"""
44+
end
45+
46+
unless is_binary(uri.path) do
47+
throw("missing project ID at the end of the DSN URI: #{inspect(dsn)}")
48+
end
49+
50+
unless is_binary(uri.userinfo) do
51+
throw("missing user info in the DSN URI: #{inspect(dsn)}")
52+
end
53+
54+
{public_key, secret_key} =
55+
case String.split(uri.userinfo, ":", parts: 2) do
56+
[public, secret] -> {public, secret}
57+
[public] -> {public, nil}
58+
end
59+
60+
with {:ok, {base_path, project_id}} <- pop_project_id(uri.path) do
61+
new_path = Enum.join([base_path, "api", project_id, "envelope"], "/") <> "/"
62+
endpoint_uri = URI.merge(%URI{uri | userinfo: nil}, new_path)
63+
64+
parsed_dsn = %__MODULE__{
65+
endpoint_uri: URI.to_string(endpoint_uri),
66+
public_key: public_key,
67+
secret_key: secret_key,
68+
original_dsn: dsn
69+
}
70+
71+
{:ok, parsed_dsn}
72+
end
73+
catch
74+
message -> {:error, message}
75+
end
76+
77+
def parse(other) do
78+
{:error, "expected :dsn to be a string or nil, got: #{inspect(other)}"}
79+
end
80+
81+
## Helpers
82+
83+
defp pop_project_id(uri_path) do
84+
path = String.split(uri_path, "/")
85+
{project_id, path} = List.pop_at(path, -1)
86+
87+
case Integer.parse(project_id) do
88+
{_project_id, ""} ->
89+
{:ok, {Enum.join(path, "/"), project_id}}
90+
91+
_other ->
92+
{:error, "expected the DSN path to end with an integer project ID, got: #{inspect(path)}"}
93+
end
94+
end
95+
end

lib/sentry/transport.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,15 @@ defmodule Sentry.Transport do
8888
end
8989

9090
defp get_endpoint_and_headers do
91-
{endpoint, public_key, secret_key} = Config.dsn()
91+
%Sentry.DSN{} = dsn = Config.dsn()
9292

9393
auth_query =
9494
[
9595
sentry_version: @sentry_version,
9696
sentry_client: @sentry_client,
9797
sentry_timestamp: System.system_time(:second),
98-
sentry_key: public_key,
99-
sentry_secret: secret_key
98+
sentry_key: dsn.public_key,
99+
sentry_secret: dsn.secret_key
100100
]
101101
|> Enum.reject(fn {_, value} -> is_nil(value) end)
102102
|> Enum.map_join(", ", fn {name, value} -> "#{name}=#{value}" end)
@@ -106,6 +106,6 @@ defmodule Sentry.Transport do
106106
{"X-Sentry-Auth", "Sentry " <> auth_query}
107107
]
108108

109-
{endpoint, auth_headers}
109+
{dsn.endpoint_uri, auth_headers}
110110
end
111111
end

test/sentry/config_test.exs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,24 @@ defmodule Sentry.ConfigTest do
55

66
describe "validate!/0" do
77
test ":dsn from option" do
8-
assert Config.validate!(dsn: "https://public:[email protected]/1")[:dsn] ==
9-
{"https://app.getsentry.com/api/1/envelope/", "public", "secret"}
8+
assert %Sentry.DSN{} =
9+
dsn = Config.validate!(dsn: "https://public:[email protected]/1")[:dsn]
10+
11+
assert dsn.endpoint_uri == "https://app.getsentry.com/api/1/envelope/"
12+
assert dsn.public_key == "public"
13+
assert dsn.secret_key == "secret"
14+
assert dsn.original_dsn == "https://public:[email protected]/1"
1015

1116
assert Config.validate!(dsn: nil)[:dsn] == nil
1217
end
1318

1419
test ":dsn from system environment" do
1520
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"}
21+
assert %Sentry.DSN{} = dsn = Config.validate!([])[:dsn]
22+
assert dsn.endpoint_uri == "https://app.getsentry.com/api/1/envelope/"
23+
assert dsn.public_key == "public"
24+
assert dsn.secret_key == "secret"
25+
assert dsn.original_dsn == "https://public:[email protected]/1"
1826
end)
1927
end
2028

@@ -212,8 +220,12 @@ defmodule Sentry.ConfigTest do
212220
new_dsn = "https://public:[email protected]/2"
213221
assert :ok = Config.put_config(:dsn, new_dsn)
214222

215-
assert Config.dsn() ==
216-
{"https://app.getsentry.com/api/2/envelope/", "public", "secret"}
223+
assert %Sentry.DSN{
224+
original_dsn: ^new_dsn,
225+
endpoint_uri: "https://app.getsentry.com/api/2/envelope/",
226+
public_key: "public",
227+
secret_key: "secret"
228+
} = Config.dsn()
217229
end
218230

219231
test "validates the given key" do

test/sentry_test.exs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,4 +201,21 @@ defmodule SentryTest do
201201
assert {:ok, "1923"} = Sentry.capture_check_in(status: :ok, monitor_slug: "default-slug")
202202
end
203203
end
204+
205+
describe "get_dsn/0" do
206+
test "returns nil if the :dsn option is not configured" do
207+
put_test_config(dsn: nil)
208+
assert Sentry.get_dsn() == nil
209+
end
210+
211+
test "returns the DSN if it's configured" do
212+
random_string = fn -> 5 |> :crypto.strong_rand_bytes() |> Base.encode16() end
213+
214+
random_dsn =
215+
"https://#{random_string.()}:#{random_string.()}@#{random_string.()}:3000/#{System.unique_integer([:positive])}"
216+
217+
put_test_config(dsn: random_dsn)
218+
assert Sentry.get_dsn() == random_dsn
219+
end
220+
end
204221
end

0 commit comments

Comments
 (0)