Skip to content
This repository was archived by the owner on Dec 8, 2020. It is now read-only.

Commit 3b3c3d7

Browse files
authored
Be more defensive with stacktrace parsing - v1.X (#131)
1 parent bf407db commit 3b3c3d7

File tree

2 files changed

+65
-22
lines changed

2 files changed

+65
-22
lines changed

lib/timber/events/exception_event.ex

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ defmodule Timber.Events.ExceptionEvent do
4747
|> Enum.map(&({&1, String.trim(&1)}))
4848

4949
case do_new({nil, "", []}, lines) do
50-
{name, message, backtrace} when is_binary(name) and length(backtrace) > 0 ->
51-
name = String.slice(name, 0..(@name_limit - 1))
52-
message = String.slice(message, 0..(@message_limit - 1))
50+
{:ok, {name, message, backtrace}} when is_binary(name) and length(backtrace) > 0 ->
51+
name = Timber.Utils.Logger.truncate(name, @name_limit)
52+
message = Timber.Utils.Logger.truncate(message, @message_limit)
5353

5454
{:ok, %__MODULE__{name: name, message: message, backtrace: backtrace}}
5555

@@ -66,8 +66,12 @@ defmodule Timber.Events.ExceptionEvent do
6666
# ** (RuntimeError) my message
6767
defp do_new({nil, _message, [] = backtrace}, [{_raw_line, ("** (" <> line_suffix)} | lines]) do
6868
# Using split since it is more performance with binary scanning
69-
[name, message] = String.split(line_suffix, ")", parts: 2)
70-
do_new({name, message, backtrace}, lines)
69+
case String.split(line_suffix, ")", parts: 2) do
70+
[name, message] ->
71+
do_new({name, message, backtrace}, lines)
72+
73+
_ -> {:error, :malformed_error_message}
74+
end
7175
end
7276

7377
# Ignore other leading messages
@@ -76,29 +80,46 @@ defmodule Timber.Events.ExceptionEvent do
7680
# (odin_client_api) web/controllers/page_controller.ex:5: Odin.ClientAPI.PageController.index/2
7781
defp do_new({name, message, backtrace}, [{_raw_line, ("(" <> line_suffix)} | lines]) when not is_nil(name) and not is_nil(message) do
7882
# Using split since it is more performance with binary scanning
79-
[app_name, line_suffix] = String.split(line_suffix, ")", parts: 2)
80-
[file, line_suffix] = String.split(line_suffix, ":", parts: 2)
81-
[line_number, function] = String.split(line_suffix, ":", parts: 2)
82-
83-
app_name = String.slice(app_name, 0..(@app_name_limit - 1))
84-
function = String.slice(function, 0..(@function_limit - 1))
85-
file = String.slice(file, 0..(@file_limit - 1))
86-
87-
line = %{
88-
app_name: app_name,
89-
function: String.trim(function),
90-
file: String.trim(file),
91-
line: parse_line_number(line_number)
92-
}
93-
do_new({name, message, [line | backtrace]}, lines)
83+
with [app_name, line_suffix] <- String.split(line_suffix, ") ", parts: 2),
84+
[file, line_suffix] <- String.split(line_suffix, ":", parts: 2),
85+
[line_number, function] <- String.split(line_suffix, ":", parts: 2)
86+
do
87+
app_name = Timber.Utils.Logger.truncate(app_name, @app_name_limit)
88+
89+
function =
90+
function
91+
|> String.trim()
92+
|> Timber.Utils.Logger.truncate(@function_limit)
93+
94+
file =
95+
file
96+
|> String.trim()
97+
|> Timber.Utils.Logger.truncate(@file_limit)
98+
99+
if function != "" && file != "" do
100+
line = %{
101+
app_name: app_name,
102+
function: String.trim(function),
103+
file: String.trim(file),
104+
line: parse_line_number(line_number)
105+
}
106+
do_new({name, message, [line | backtrace]}, lines)
107+
else
108+
{:error, :malformed_stacktrace_line}
109+
end
110+
111+
else
112+
_ ->
113+
{:error, :malformed_stacktrace_line}
114+
end
94115
end
95116

96117
# Ignore lines we don't recognize.
97118
defp do_new(acc, [_line | lines]), do: do_new(acc, lines)
98119

99120
# Finish the iteration, reversing the backtrace for performance reasons.
100121
defp do_new({name, message, backtrace}, []) do
101-
{name, String.trim(message), Enum.reverse(backtrace)}
122+
{:ok, {name, String.trim(message), Enum.reverse(backtrace)}}
102123
end
103124

104125
defp parse_line_number(line_str) do
@@ -113,4 +134,4 @@ defmodule Timber.Events.ExceptionEvent do
113134
"""
114135
@spec message(t) :: IO.chardata
115136
def message(%__MODULE__{name: name, message: message}), do: [?(, name, ?), ?\s, message]
116-
end
137+
end

test/lib/timber/events/exception_event_test.exs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,28 @@ defmodule Timber.Events.ExceptionEventTest do
4141
}
4242
end
4343

44+
test "native functions" do
45+
log_message =
46+
"""
47+
** (exit) an exception was raised:
48+
** (ArgumentError) argument error
49+
(stdlib) :ets.lookup(:noproc, 111)
50+
"""
51+
result = ExceptionEvent.new(log_message)
52+
assert result == {:error, :could_not_parse_message}
53+
end
54+
55+
test "malformed stacktrace" do
56+
log_message =
57+
"""
58+
** (exit) an exception was raised:
59+
** (RuntimeError) boom
60+
(my_app) malformed
61+
"""
62+
result = ExceptionEvent.new(log_message)
63+
assert result == {:error, :could_not_parse_message}
64+
end
65+
4466
test "malformed message" do
4567
{:error, :could_not_parse_message} = ExceptionEvent.new("testing")
4668
end

0 commit comments

Comments
 (0)