Skip to content

Commit fd552ad

Browse files
authored
Sanitize the context when tracking errors (#94)
Adds a `ErrorTracker.Filter` behavior that users can supply, it has a single function `sanitize/1` that takes an error context and returns a new error context which will be saved in the DB. Closes #88
1 parent a15639a commit fd552ad

File tree

3 files changed

+106
-1
lines changed

3 files changed

+106
-1
lines changed

lib/error_tracker.ex

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ defmodule ErrorTracker do
118118
{kind, reason} = normalize_exception(exception, stacktrace)
119119
{:ok, stacktrace} = ErrorTracker.Stacktrace.new(stacktrace)
120120
{:ok, error} = Error.new(kind, reason, stacktrace)
121-
context = Map.merge(get_context(), given_context)
121+
context = get_context() |> Map.merge(given_context) |> filter_context_data()
122122

123123
if enabled?() && !ignored?(error, context) do
124124
{_error, occurrence} = upsert_error!(error, stacktrace, context, reason)
@@ -209,6 +209,16 @@ defmodule ErrorTracker do
209209
ignorer && ignorer.ignore?(error, context)
210210
end
211211

212+
defp filter_context_data(context) do
213+
filter_mod = Application.get_env(:error_tracker, :filter)
214+
215+
if filter_mod do
216+
filter_mod.sanitize(context)
217+
else
218+
context
219+
end
220+
end
221+
212222
defp normalize_exception(%struct{} = ex, _stacktrace) when is_exception(ex) do
213223
{to_string(struct), Exception.message(ex)}
214224
end

lib/error_tracker/filter.ex

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
defmodule ErrorTracker.Filter do
2+
@moduledoc """
3+
Behaviour for sanitizing & modifying the error context before it's saved.
4+
5+
defmodule MyApp.ErrorFilter do
6+
@behaviour ErrorTracker.Filter
7+
8+
@impl true
9+
def sanitize(context) do
10+
context # Modify the context object (add or remove fields as much as you need.)
11+
end
12+
end
13+
14+
Once implemented, include it in the ErrorTracker configuration:
15+
16+
config :error_tracker, filter: MyApp.Filter
17+
18+
With this configuration in place, the ErrorTracker will call `MyApp.Filter.sanitize/1` to get a context before
19+
saving error occurrence.
20+
21+
> #### A note on performance {: .warning}
22+
>
23+
> Keep in mind that the `sanitize/1` will be called in the context of the ErrorTracker itself.
24+
> Slow code will have a significant impact in the ErrorTracker performance. Buggy code can bring
25+
> the ErrorTracker process down.
26+
"""
27+
28+
@doc """
29+
This function will be given an error context to inspect/modify before it's saved.
30+
"""
31+
@callback sanitize(context :: map()) :: map()
32+
end

test/error_tracker/filter_test.exs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
defmodule ErrorTracker.FilterTest do
2+
use ErrorTracker.Test.Case
3+
4+
setup context do
5+
if filter = context[:filter] do
6+
previous_setting = Application.get_env(:error_tracker, :filter)
7+
Application.put_env(:error_tracker, :filter, filter)
8+
# Ensure that the application env is restored after each test
9+
on_exit(fn -> Application.put_env(:error_tracker, :filter, previous_setting) end)
10+
end
11+
12+
[]
13+
end
14+
15+
@sensitive_ctx %{
16+
"request" => %{
17+
"headers" => %{
18+
"accept" => "application/json, text/plain, */*",
19+
"authorization" => "Bearer 12341234"
20+
}
21+
}
22+
}
23+
24+
test "without an filter, context objects are saved as they are." do
25+
assert %ErrorTracker.Occurrence{context: ctx} =
26+
report_error(fn -> raise "BOOM" end, @sensitive_ctx)
27+
28+
assert ctx == @sensitive_ctx
29+
end
30+
31+
@tag filter: ErrorTracker.FilterTest.AuthHeaderHider
32+
test "user defined filter should be used to sanitize the context before it's saved." do
33+
assert %ErrorTracker.Occurrence{context: ctx} =
34+
report_error(fn -> raise "BOOM" end, @sensitive_ctx)
35+
36+
assert ctx != @sensitive_ctx
37+
38+
cleaned_header_value =
39+
ctx |> Map.get("request") |> Map.get("headers") |> Map.get("authorization")
40+
41+
assert cleaned_header_value == "REMOVED"
42+
end
43+
end
44+
45+
defmodule ErrorTracker.FilterTest.AuthHeaderHider do
46+
@behaviour ErrorTracker.Filter
47+
48+
def sanitize(context) do
49+
context
50+
|> Enum.map(fn
51+
{"authorization", _} ->
52+
{"authorization", "REMOVED"}
53+
54+
o ->
55+
o
56+
end)
57+
|> Enum.map(fn
58+
{key, val} when is_map(val) -> {key, sanitize(val)}
59+
o -> o
60+
end)
61+
|> Map.new()
62+
end
63+
end

0 commit comments

Comments
 (0)