Skip to content

Commit 68e808f

Browse files
committed
Improve reporting of crashes in :logger handler
1 parent cab6126 commit 68e808f

File tree

3 files changed

+103
-8
lines changed

3 files changed

+103
-8
lines changed

lib/sentry/logger_handler.ex

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,7 @@ defmodule Sentry.LoggerHandler do
169169
sentry_opts,
170170
%__MODULE__{} = config
171171
) do
172-
message = :unicode.characters_to_binary(unicode_chardata)
173-
log_from_crash_reason(log_event.meta[:crash_reason], message, sentry_opts, config)
172+
log_from_crash_reason(log_event.meta[:crash_reason], unicode_chardata, sentry_opts, config)
174173
end
175174

176175
# "report" here is of type logger:report/0, which is a map or keyword list.
@@ -222,7 +221,7 @@ defmodule Sentry.LoggerHandler do
222221

223222
defp log_from_crash_reason(
224223
{exception, stacktrace},
225-
_string_message,
224+
_chardata_message,
226225
sentry_opts,
227226
%__MODULE__{}
228227
)
@@ -231,23 +230,61 @@ defmodule Sentry.LoggerHandler do
231230
Sentry.capture_exception(exception, sentry_opts)
232231
end
233232

234-
defp log_from_crash_reason({reason, stacktrace}, string_message, sentry_opts, %__MODULE__{})
233+
defp log_from_crash_reason({reason, stacktrace}, chardata_message, sentry_opts, %__MODULE__{})
235234
when is_list(stacktrace) do
236235
sentry_opts =
237236
sentry_opts
238237
|> Keyword.put(:stacktrace, stacktrace)
239238
|> Keyword.update!(:extra, &Map.put(&1, :crash_reason, inspect(reason)))
239+
|> Keyword.update!(:extra, &Map.merge(extra_info_from_message(chardata_message), &1))
240240

241-
Sentry.capture_message(string_message, sentry_opts)
241+
case reason do
242+
{type, {GenServer, :call, [_pid, call, _timeout]}} = reason
243+
when type in [:noproc, :timeout] ->
244+
sentry_opts =
245+
Keyword.put_new(sentry_opts, :fingerprint, [
246+
Atom.to_string(type),
247+
"genserver_call",
248+
inspect(call)
249+
])
250+
251+
Sentry.capture_message(Exception.format_exit(reason), sentry_opts)
252+
253+
_other ->
254+
string_message = :unicode.characters_to_binary(chardata_message)
255+
Sentry.capture_message(string_message, sentry_opts)
256+
end
242257
end
243258

244-
defp log_from_crash_reason(_other_reason, string_message, sentry_opts, %__MODULE__{
259+
defp log_from_crash_reason(_other_reason, chardata_message, sentry_opts, %__MODULE__{
245260
capture_log_messages: true
246261
}) do
262+
string_message = :unicode.characters_to_binary(chardata_message)
247263
Sentry.capture_message(string_message, sentry_opts)
248264
end
249265

250266
defp log_from_crash_reason(_other_reason, _string_message, _sentry_opts, _config) do
251267
:ok
252268
end
269+
270+
defp extra_info_from_message([
271+
[
272+
"GenServer ",
273+
_pid,
274+
" terminating",
275+
_reason,
276+
"\nLast message",
277+
_from,
278+
": ",
279+
last_message
280+
],
281+
"\nState: ",
282+
state | _rest
283+
]) do
284+
%{genserver_state: state, last_message: last_message}
285+
end
286+
287+
defp extra_info_from_message(_message) do
288+
%{}
289+
end
253290
end

lib/sentry/logger_utils.ex

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ defmodule Sentry.LoggerUtils do
1313
@spec build_sentry_options(Logger.level(), keyword() | nil, map(), [atom()] | :all) ::
1414
keyword()
1515
def build_sentry_options(level, sentry_context, meta, allowed_meta) do
16-
default_extra = %{logger_metadata: logger_metadata(meta, allowed_meta), logger_level: level}
16+
default_extra =
17+
Map.merge(
18+
%{logger_metadata: logger_metadata(meta, allowed_meta), logger_level: level},
19+
Map.take(meta, [:domain])
20+
)
1721

1822
(sentry_context || get_sentry_options_from_callers(meta[:callers]) || %{})
1923
|> Map.new()

test/sentry/logger_handler_test.exs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,63 @@ defmodule Sentry.LoggerHandlerTest do
185185
if System.otp_release() >= "26" do
186186
assert [] = event.exception
187187
assert [_thread] = event.threads
188+
assert event.extra.genserver_state == ":no_state"
189+
assert event.extra.last_message =~ "{:run, #Function"
188190
end
189191
end
190192

193+
test "an exit while calling another GenServer is reported nicely",
194+
%{sender_ref: ref, test_genserver: test_genserver} do
195+
# Get a PID and make sure it's done before using it.
196+
{pid, monitor_ref} = spawn_monitor(fn -> :ok end)
197+
assert_receive {:DOWN, ^monitor_ref, _, _, _}
198+
199+
run_and_catch_exit(test_genserver, fn ->
200+
GenServer.call(pid, :ping)
201+
end)
202+
203+
assert_receive {^ref, event}
204+
205+
assert event.exception == []
206+
assert event.extra.domain == [:otp]
207+
assert event.extra.logger_level == :error
208+
assert event.extra.logger_metadata == %{}
209+
assert event.extra.crash_reason =~ "{:noproc, {GenServer, :call"
210+
assert event.fingerprint == ["noproc", "genserver_call", ":ping"]
211+
212+
assert event.message.formatted == """
213+
exited in: GenServer.call(#{inspect(pid)}, :ping, 5000)
214+
** (EXIT) no process: the process is not alive or there's no process currently \
215+
associated with the given name, possibly because its application isn't started\
216+
"""
217+
218+
assert [%{stacktrace: stacktrace}] = event.threads
219+
assert Enum.find(stacktrace.frames, &(&1.function == "GenServer.call/3"))
220+
end
221+
222+
test "a timeout while calling another GenServer is reported nicely",
223+
%{sender_ref: ref, test_genserver: test_genserver} do
224+
{:ok, agent} = Agent.start_link(fn -> nil end)
225+
226+
run_and_catch_exit(test_genserver, fn ->
227+
Agent.get(agent, & &1, 0)
228+
end)
229+
230+
assert_receive {^ref, event}
231+
232+
assert event.exception == []
233+
assert event.extra.domain == [:otp]
234+
assert event.extra.logger_level == :error
235+
assert event.extra.logger_metadata == %{}
236+
assert event.extra.crash_reason =~ "{:timeout, {GenServer, :call"
237+
assert ["timeout", "genserver_call", "{:get" <> _] = event.fingerprint
238+
239+
assert event.message.formatted =~ "exited in: GenServer.call(#{inspect(agent)}, {:get, "
240+
241+
assert [%{stacktrace: stacktrace}] = event.threads
242+
assert Enum.find(stacktrace.frames, &(&1.function == "GenServer.call/3"))
243+
end
244+
191245
@tag handler_config: %{metadata: [:string, :number, :map, :list, :chardata]}
192246
test "includes Logger metadata for keys configured to be included",
193247
%{sender_ref: ref, test_genserver: test_genserver} do
@@ -285,7 +339,7 @@ defmodule Sentry.LoggerHandlerTest do
285339
assert [] = event.exception
286340
assert [thread] = event.threads
287341

288-
assert event.message.formatted =~ "** (stop) exited in: GenServer.call("
342+
assert event.message.formatted =~ "exited in: GenServer.call("
289343
assert event.message.formatted =~ "** (EXIT) time out"
290344
assert length(thread.stacktrace.frames) > 0
291345
end

0 commit comments

Comments
 (0)