@@ -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 )
0 commit comments