Skip to content

Commit 5bf5c38

Browse files
Merge pull request #372 from getsentry/no-logger-logging
No logger logging
2 parents f66ee52 + 8dfdfab commit 5bf5c38

File tree

6 files changed

+92
-80
lines changed

6 files changed

+92
-80
lines changed

lib/mix/tasks/sentry.send_test_event.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ defmodule Mix.Tasks.Sentry.SendTestEvent do
5858
{:ok, id} ->
5959
Mix.shell().info("Test event sent! Event ID: #{id}")
6060

61-
:error ->
62-
Mix.shell().info("Error sending event!")
61+
{:error, reason} ->
62+
Mix.shell().info("Error sending event: #{inspect(reason)}")
6363

6464
:excluded ->
6565
Mix.shell().info("No test event was sent because the event was excluded by a filter")

lib/sentry/client.ex

Lines changed: 77 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ defmodule Sentry.Client do
4141
require Logger
4242

4343
@type send_event_result ::
44-
{:ok, Task.t() | String.t() | pid()} | :error | :unsampled | :excluded
45-
@type dsn :: {String.t(), String.t(), String.t()} | :error
44+
{:ok, Task.t() | String.t() | pid()} | {:error, any()} | :unsampled | :excluded
45+
@type dsn :: {String.t(), String.t(), String.t()}
4646
@type result :: :sync | :none | :async
4747
@sentry_version 5
4848
@max_attempts 4
@@ -56,6 +56,7 @@ defmodule Sentry.Client do
5656
Attempts to send the event to the Sentry API up to 4 times with exponential backoff.
5757
5858
The event is dropped if it all retries fail.
59+
Errors will be logged unless the source is the Sentry.LoggerBackend, which can deadlock by logging within a logger.
5960
6061
### Options
6162
* `:result` - Allows specifying how the result should be returned. Options include `:sync`, `:none`, and `:async`. `:sync` will make the API call synchronously, and return `{:ok, event_id}` if successful. `:none` sends the event from an unlinked child process under `Sentry.TaskSupervisor` and will return `{:ok, ""}` regardless of the result. `:async` will start an unlinked task and return a tuple of `{:ok, Task.t}` on success where the Task can be awaited upon to receive the result asynchronously. When used in an OTP behaviour like GenServer, the task will send a message that needs to be matched with `GenServer.handle_info/2`. See `Task.Supervisor.async_nolink/2` for more information. `:async` is the default.
@@ -66,6 +67,7 @@ defmodule Sentry.Client do
6667
def send_event(%Event{} = event, opts \\ []) do
6768
result = Keyword.get(opts, :result, :async)
6869
sample_rate = Keyword.get(opts, :sample_rate) || Config.sample_rate()
70+
should_log = event.event_source != :logger
6971

7072
event = maybe_call_before_send_event(event)
7173

@@ -77,97 +79,96 @@ defmodule Sentry.Client do
7779
:unsampled
7880

7981
{%Event{}, true} ->
80-
encode_and_send(event, result)
82+
encode_and_send(event, result, should_log)
8183
end
8284
end
8385

84-
@spec encode_and_send(Event.t(), result()) :: send_event_result()
85-
defp encode_and_send(event, result) do
86+
@spec encode_and_send(Event.t(), result(), boolean()) :: send_event_result()
87+
defp encode_and_send(event, result, should_log) do
8688
json_library = Config.json_library()
8789

8890
render_event(event)
8991
|> json_library.encode()
9092
|> case do
9193
{:ok, body} ->
92-
do_send_event(event, body, result)
94+
result = do_send_event(event, body, result)
95+
96+
if should_log do
97+
maybe_log_result(result)
98+
end
99+
100+
result
93101

94102
{:error, error} ->
95-
log_api_error("Unable to encode Sentry error - #{inspect(error)}")
96-
:error
103+
{:error, {:invalid_json, error}}
97104
end
98105
end
99106

100-
@spec do_send_event(Event.t(), map(), :async) :: {:ok, Task.t()} | :error
107+
@spec do_send_event(Event.t(), map(), :async) :: {:ok, Task.t()} | {:error, any()}
101108
defp do_send_event(event, body, :async) do
102109
case get_headers_and_endpoint() do
103-
{endpoint, auth_headers} ->
110+
{endpoint, auth_headers} when is_binary(endpoint) ->
104111
{:ok,
105112
Task.Supervisor.async_nolink(Sentry.TaskSupervisor, fn ->
106113
try_request(endpoint, auth_headers, {event, body})
107114
|> maybe_call_after_send_event(event)
108115
end)}
109116

110-
_ ->
111-
:error
117+
{:error, :invalid_dsn} ->
118+
{:error, :invalid_dsn}
112119
end
113120
end
114121

115-
@spec do_send_event(Event.t(), map(), :sync) :: {:ok, String.t()} | :error
122+
@spec do_send_event(Event.t(), map(), :sync) :: {:ok, String.t()} | {:error, any()}
116123
defp do_send_event(event, body, :sync) do
117124
case get_headers_and_endpoint() do
118-
{endpoint, auth_headers} ->
125+
{endpoint, auth_headers} when is_binary(endpoint) ->
119126
try_request(endpoint, auth_headers, {event, body})
120127
|> maybe_call_after_send_event(event)
121128

122-
_ ->
123-
:error
129+
{:error, :invalid_dsn} ->
130+
{:error, :invalid_dsn}
124131
end
125132
end
126133

127134
@spec do_send_event(Event.t(), map(), :none) ::
128-
{:ok, DynamicSupervisor.on_start_child()} | :error
135+
{:ok, DynamicSupervisor.on_start_child()} | {:error, any()}
129136
defp do_send_event(event, body, :none) do
130137
case get_headers_and_endpoint() do
131-
{endpoint, auth_headers} ->
138+
{endpoint, auth_headers} when is_binary(endpoint) ->
132139
Task.Supervisor.start_child(Sentry.TaskSupervisor, fn ->
133140
try_request(endpoint, auth_headers, {event, body})
134141
|> maybe_call_after_send_event(event)
135142
end)
136143

137144
{:ok, ""}
138145

139-
_ ->
140-
:error
146+
{:error, :invalid_dsn} ->
147+
{:error, :invalid_dsn}
141148
end
142149
end
143150

144151
@spec try_request(
145152
String.t(),
146153
list({String.t(), String.t()}),
147154
{Event.t(), String.t()},
148-
pos_integer()
149-
) :: {:ok, String.t()} | :error
150-
defp try_request(url, headers, event_body_tuple, current_attempt \\ 1)
155+
{pos_integer(), any()}
156+
) :: {:ok, String.t()} | {:error, {:request_failure, any()}}
157+
defp try_request(url, headers, event_body_tuple, current_attempt_and_error \\ {1, nil})
151158

152-
defp try_request(_, _, _, current_attempt)
159+
defp try_request(_url, _headers, {_event, _body}, {current_attempt, last_error})
153160
when current_attempt > @max_attempts,
154-
do: :error
161+
do: {:error, {:request_failure, last_error}}
155162

156-
defp try_request(url, headers, {event, body}, current_attempt) do
163+
defp try_request(url, headers, {event, body}, {current_attempt, _last_error}) do
157164
case request(url, headers, body) do
158165
{:ok, id} ->
159166
{:ok, id}
160167

161168
{:error, error} ->
162-
if(current_attempt == 1) do
163-
log_api_error("Event ID: #{event.event_id} - #{inspect(error)} - #{body}")
164-
else
165-
log_api_error("Event ID: #{event.event_id} - #{inspect(error)}")
166-
end
167-
168169
if current_attempt < @max_attempts, do: sleep(current_attempt)
169170

170-
try_request(url, headers, {event, body}, current_attempt + 1)
171+
try_request(url, headers, {event, body}, {current_attempt + 1, error})
171172
end
172173
end
173174

@@ -225,20 +226,12 @@ defmodule Sentry.Client do
225226
"Sentry " <> query
226227
end
227228

228-
@spec authorization_headers(String.t(), String.t()) :: list({String.t(), String.t()})
229-
defp authorization_headers(public_key, secret_key) do
230-
[
231-
{"User-Agent", @sentry_client},
232-
{"X-Sentry-Auth", authorization_header(public_key, secret_key)}
233-
]
234-
end
235-
236229
@doc """
237230
Get a Sentry DSN which is simply a URI.
238231
239232
{PROTOCOL}://{PUBLIC_KEY}[:{SECRET_KEY}]@{HOST}/{PATH}{PROJECT_ID}
240233
"""
241-
@spec get_dsn :: dsn
234+
@spec get_dsn :: dsn | {:error, :invalid_dsn}
242235
def get_dsn do
243236
dsn = Config.dsn()
244237

@@ -252,8 +245,7 @@ defmodule Sentry.Client do
252245
{endpoint, public_key, secret_key}
253246
else
254247
_ ->
255-
log_api_error("Cannot send event because of invalid DSN")
256-
:error
248+
{:error, :invalid_dsn}
257249
end
258250
end
259251

@@ -338,6 +330,43 @@ defmodule Sentry.Client do
338330
end
339331
end
340332

333+
def maybe_log_result(result) do
334+
message =
335+
case result do
336+
{:error, :invalid_dsn} ->
337+
"Cannot send Sentry event because of invalid DSN"
338+
339+
{:error, {:invalid_json, error}} ->
340+
"Unable to encode JSON Sentry error - #{inspect(error)}"
341+
342+
{:error, {:request_failure, last_error}} ->
343+
"Error in HTTP Request to Sentry - #{inspect(last_error)}"
344+
345+
{:error, error} ->
346+
inspect(error)
347+
348+
_ ->
349+
nil
350+
end
351+
352+
if message != nil do
353+
Logger.log(
354+
Config.log_level(),
355+
fn ->
356+
["Failed to send Sentry event.", message]
357+
end
358+
)
359+
end
360+
end
361+
362+
@spec authorization_headers(String.t(), String.t()) :: list({String.t(), String.t()})
363+
defp authorization_headers(public_key, secret_key) do
364+
[
365+
{"User-Agent", @sentry_client},
366+
{"X-Sentry-Auth", authorization_header(public_key, secret_key)}
367+
]
368+
end
369+
341370
defp keys_from_userinfo(userinfo) do
342371
case String.split(userinfo, ":", parts: 2) do
343372
[public, secret] -> [public, secret]
@@ -346,25 +375,18 @@ defmodule Sentry.Client do
346375
end
347376
end
348377

378+
@spec get_headers_and_endpoint ::
379+
{String.t(), list({String.t(), String.t()})} | {:error, :invalid_dsn}
349380
defp get_headers_and_endpoint do
350381
case get_dsn() do
351382
{endpoint, public_key, secret_key} ->
352383
{endpoint, authorization_headers(public_key, secret_key)}
353384

354-
_ ->
355-
:error
385+
{:error, :invalid_dsn} ->
386+
{:error, :invalid_dsn}
356387
end
357388
end
358389

359-
defp log_api_error(body) do
360-
Logger.log(
361-
Config.log_level(),
362-
fn ->
363-
["Failed to send Sentry event.", inspect(body)]
364-
end
365-
)
366-
end
367-
368390
@spec sleep(pos_integer()) :: :ok
369391
defp sleep(1), do: :timer.sleep(2000)
370392
defp sleep(2), do: :timer.sleep(4000)

test/client_test.exs

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,41 +44,31 @@ defmodule Sentry.ClientTest do
4444
test "errors on bad public keys" do
4545
modify_env(:sentry, dsn: "https://app.getsentry.com/1")
4646

47-
capture_log(fn ->
48-
assert :error = Sentry.Client.get_dsn()
49-
end)
47+
assert {:error, :invalid_dsn} = Sentry.Client.get_dsn()
5048
end
5149

5250
test "errors on non-integer project_id" do
5351
modify_env(:sentry, dsn: "https://public:[email protected]/Mitchell")
5452

55-
capture_log(fn ->
56-
assert :error = Sentry.Client.get_dsn()
57-
end)
53+
assert {:error, :invalid_dsn} = Sentry.Client.get_dsn()
5854
end
5955

6056
test "errors on no project_id" do
6157
modify_env(:sentry, dsn: "https://public:[email protected]")
6258

63-
capture_log(fn ->
64-
assert :error = Sentry.Client.get_dsn()
65-
end)
59+
assert {:error, :invalid_dsn} = Sentry.Client.get_dsn()
6660
end
6761

6862
test "errors on nil dsn" do
6963
modify_env(:sentry, dsn: nil)
7064

71-
capture_log(fn ->
72-
assert :error = Sentry.Client.get_dsn()
73-
end)
65+
assert {:error, :invalid_dsn} = Sentry.Client.get_dsn()
7466
end
7567

7668
test "errors on atom dsn" do
7769
modify_env(:sentry, dsn: :error)
7870

79-
capture_log(fn ->
80-
assert :error = Sentry.Client.get_dsn()
81-
end)
71+
assert {:error, :invalid_dsn} = Sentry.Client.get_dsn()
8272
end
8373

8474
test "logs api errors" do
@@ -114,7 +104,7 @@ defmodule Sentry.ClientTest do
114104
unencodable_event = %Sentry.Event{message: "error", level: {:a, :b}}
115105

116106
capture_log(fn ->
117-
assert :error = Sentry.Client.send_event(unencodable_event)
107+
assert {:error, {:invalid_json, _}} = Sentry.Client.send_event(unencodable_event)
118108
end)
119109
end
120110

test/mix/sentry.send_test_event_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ defmodule Mix.Tasks.Sentry.SendTestEventTest do
5555
"""
5656
end
5757

58-
test "handles :error when Sentry server is failing" do
58+
test "handles error when Sentry server is failing" do
5959
bypass = Bypass.open()
6060

6161
Bypass.expect(bypass, fn conn ->
@@ -78,7 +78,7 @@ defmodule Mix.Tasks.Sentry.SendTestEventTest do
7878
hackney_opts: [recv_timeout: 50]
7979
8080
Sending test event...
81-
Error sending event!
81+
Error sending event: "Received 500 from Sentry server: "
8282
"""
8383
end) =~ "Failed to send Sentry event"
8484
end

test/sentry_test.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ defmodule SentryTest do
5555
dsn: "http://public:secret@localhost:#{bypass.port}/1"
5656
)
5757

58-
assert capture_log(fn ->
59-
assert :error = Sentry.capture_message("error", [])
60-
end) =~ "Failed to send Sentry event"
58+
capture_log(fn ->
59+
assert {:error, _} = Sentry.capture_message("error", [])
60+
end)
6161

6262
Bypass.pass(bypass)
6363
end

test/support/test_client.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ defmodule Sentry.TestClient do
2626
end
2727
)
2828

29-
:error
29+
{:error, error}
3030
end
3131

3232
{:error, error} ->
@@ -35,7 +35,7 @@ defmodule Sentry.TestClient do
3535
"Error sending in Sentry.TestClient: #{inspect(error)}"
3636
)
3737

38-
:error
38+
{:error, error}
3939
end
4040
end
4141
end

0 commit comments

Comments
 (0)