Skip to content

Commit f7a9264

Browse files
Merge pull request #437 from jbernardo95/multiple-root-source-code-paths
Updates Sentry.Sources to support multiple source code root paths
2 parents 9850fc4 + 52d32cf commit f7a9264

File tree

10 files changed

+184
-20
lines changed

10 files changed

+184
-20
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ config :sentry,
139139
environment_name: Mix.env(),
140140
included_environments: [:prod],
141141
enable_source_code_context: true,
142-
root_source_code_path: File.cwd!()
142+
root_source_code_paths: [File.cwd!()]
143143
```
144144

145145
The `environment_name` and `included_environments` work together to determine
@@ -169,7 +169,7 @@ The full range of options is the following:
169169
| `in_app_module_whitelist` | False | `[]` | |
170170
| `report_deps` | False | True | Will attempt to load Mix dependencies at compile time to report alongside events |
171171
| `enable_source_code_context` | False | False | |
172-
| `root_source_code_path` | Required if `enable_source_code_context` is enabled | | Should generally be set to `File.cwd!()`|
172+
| `root_source_code_paths` | Required if `enable_source_code_context` is enabled | | Should usually be set to `[File.cwd!()]`. For umbrella applications you should list all your applications paths in this list (e.g. `["#{File.cwd!()}/apps/app_1", "#{File.cwd!()}/apps/app_2"]`. |
173173
| `context_lines` | False | 3 | |
174174
| `source_code_exclude_patterns` | False | `[~r"/_build/", ~r"/deps/", ~r"/priv/"]` | |
175175
| `source_code_path_pattern` | False | `"**/*.ex"` | |

config/config.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ config :sentry,
55
environment_name: :dev,
66
tags: %{},
77
enable_source_code_context: true,
8-
root_source_code_path: File.cwd!()
8+
root_source_code_paths: [File.cwd!()]
99

1010
import_config "#{Mix.env()}.exs"

lib/sentry/config.ex

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ defmodule Sentry.Config do
7777
get_config(:enable_source_code_context, default: false, check_dsn: false)
7878
end
7979

80+
@deprecated "Use root_source_code_paths/0 instead"
8081
def root_source_code_path do
8182
path = get_config(:root_source_code_path)
8283

@@ -87,6 +88,38 @@ defmodule Sentry.Config do
8788
end
8889
end
8990

91+
# :root_source_code_path (single path) was replaced by :root_source_code_paths (list of
92+
# paths).
93+
#
94+
# In order for this to not be a breaking change we still accept the old
95+
# :root_source_code_path as a fallback.
96+
#
97+
# We should deprecate this the :root_source_code_path completely in the next major
98+
# release.
99+
def root_source_code_paths do
100+
paths = get_config(:root_source_code_paths)
101+
path = get_config(:root_source_code_path)
102+
103+
cond do
104+
not is_nil(path) and not is_nil(paths) ->
105+
raise ArgumentError, """
106+
:root_source_code_path and :root_source_code_paths can't be configured at the \
107+
same time.
108+
109+
:root_source_code_path is deprecated. Set :root_source_code_paths instead.
110+
"""
111+
112+
not is_nil(paths) ->
113+
paths
114+
115+
not is_nil(path) ->
116+
[path]
117+
118+
true ->
119+
raise ArgumentError.exception(":root_source_code_paths must be configured")
120+
end
121+
end
122+
90123
def source_code_path_pattern do
91124
get_config(:source_code_path_pattern, default: @default_path_pattern, check_dsn: false)
92125
end

lib/sentry/sources.ex

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ defmodule Sentry.Sources do
88
99
### Configuration
1010
There is configuration required to set up this functionality. The options
11-
include `:enable_source_code_context`, `:root_source_code_path`, `:context_lines`,
11+
include `:enable_source_code_context`, `:root_source_code_paths`, `:context_lines`,
1212
`:source_code_exclude_patterns`, and `:source_code_path_pattern`.
1313
1414
* `:enable_source_code_context` - when `true`, enables reporting source code
1515
alongside exceptions.
16-
* `:root_source_code_path` - The path from which to start recursively reading files from.
17-
Should usually be set to `File.cwd!()`.
16+
* `:root_source_code_paths` - List of paths from which to start recursively reading files from.
17+
Should usually be set to `[File.cwd!()]`. For umbrella applications you should list all your
18+
applications paths in this list (e.g. `["#{File.cwd!()}/apps/app_1", "#{File.cwd!()}/apps/app_2"]`.
1819
* `:context_lines` - The number of lines of source code before and after the line that
1920
caused the exception to be included. Defaults to `3`.
2021
* `:source_code_exclude_patterns` - a list of Regex expressions used to exclude file paths that
@@ -28,7 +29,7 @@ defmodule Sentry.Sources do
2829
config :sentry,
2930
dsn: "https://public:[email protected]/1",
3031
enable_source_code_context: true,
31-
root_source_code_path: File.cwd!(),
32+
root_source_code_path: [File.cwd!()],
3233
context_lines: 5
3334
3435
### Source code storage
@@ -64,19 +65,11 @@ defmodule Sentry.Sources do
6465
@type source_map :: %{String.t() => file_map}
6566

6667
def load_files do
67-
root_path = Config.root_source_code_path()
68-
path_pattern = Config.source_code_path_pattern()
69-
exclude_patterns = Config.source_code_exclude_patterns()
70-
71-
Path.join(root_path, path_pattern)
72-
|> Path.wildcard()
73-
|> exclude_files(exclude_patterns)
74-
|> Enum.reduce(%{}, fn path, acc ->
75-
key = Path.relative_to(path, root_path)
76-
value = source_to_lines(File.read!(path))
77-
78-
Map.put(acc, key, value)
79-
end)
68+
Enum.reduce(
69+
Config.root_source_code_paths(),
70+
%{},
71+
&load_files_for_root_path/2
72+
)
8073
end
8174

8275
@doc """
@@ -125,6 +118,37 @@ defmodule Sentry.Sources do
125118
end)
126119
end
127120

121+
defp load_files_for_root_path(root_path, files) do
122+
root_path
123+
|> find_files_for_root_path()
124+
|> Enum.reduce(files, fn path, acc ->
125+
key = Path.relative_to(path, root_path)
126+
127+
if Map.has_key?(acc, key) do
128+
raise RuntimeError, """
129+
Found two source files in different source root paths with the same relative \
130+
path: #{key}
131+
132+
This means that both source files would be reported to Sentry as the same \
133+
file. Please rename one of them to avoid this.
134+
"""
135+
else
136+
value = source_to_lines(File.read!(path))
137+
138+
Map.put(acc, key, value)
139+
end
140+
end)
141+
end
142+
143+
defp find_files_for_root_path(root_path) do
144+
path_pattern = Config.source_code_path_pattern()
145+
exclude_patterns = Config.source_code_exclude_patterns()
146+
147+
Path.join(root_path, path_pattern)
148+
|> Path.wildcard()
149+
|> exclude_files(exclude_patterns)
150+
end
151+
128152
defp exclude_files(file_names, []), do: file_names
129153

130154
defp exclude_files(file_names, [exclude_pattern | rest]) do

test/config_test.exs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,46 @@ defmodule Sentry.ConfigTest do
7272
assert ["test", "dev"] == Config.included_environments()
7373
end
7474
end
75+
76+
describe "root_source_code_paths" do
77+
test "raises error if :root_source_code_path and :root_source_code_paths are set" do
78+
modify_env(:sentry, root_source_code_path: "/test")
79+
modify_env(:sentry, root_source_code_paths: ["/test"])
80+
81+
expected_error_message = """
82+
:root_source_code_path and :root_source_code_paths can't be configured at the \
83+
same time.
84+
85+
:root_source_code_path is deprecated. Set :root_source_code_paths instead.
86+
"""
87+
88+
assert_raise ArgumentError, expected_error_message, fn ->
89+
Config.root_source_code_paths()
90+
end
91+
end
92+
93+
test "raises error if :root_source_code_path and :root_source_code_paths are not set" do
94+
delete_env(:sentry, [:root_source_code_path, :root_source_code_paths])
95+
96+
expected_error_message = ":root_source_code_paths must be configured"
97+
98+
assert_raise ArgumentError, expected_error_message, fn ->
99+
Config.root_source_code_paths()
100+
end
101+
end
102+
103+
test "returns :root_source_code_path if it's set" do
104+
modify_env(:sentry, root_source_code_path: "/test")
105+
modify_env(:sentry, root_source_code_paths: nil)
106+
107+
assert Config.root_source_code_paths() == ["/test"]
108+
end
109+
110+
test "returns :root_source_code_paths if it's set" do
111+
modify_env(:sentry, root_source_code_path: nil)
112+
modify_env(:sentry, root_source_code_paths: ["/test"])
113+
114+
assert Config.root_source_code_paths() == ["/test"]
115+
end
116+
end
75117
end

test/sources_test.exs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,51 @@ defmodule Sentry.SourcesTest do
33
use Plug.Test
44
import Sentry.TestEnvironmentHelper
55

6+
describe "load_files/0" do
7+
test "loads files" do
8+
modify_env(:sentry, root_source_code_paths: [
9+
File.cwd!() <> "/test/support/example-umbrella-app/apps/app_a",
10+
File.cwd!() <> "/test/support/example-umbrella-app/apps/app_b"
11+
])
12+
13+
assert %{
14+
"lib/module_a.ex" => %{
15+
1 => "defmodule ModuleA do",
16+
2 => " def test do",
17+
3 => " \"test a\"",
18+
4 => " end",
19+
5 => "end"
20+
},
21+
"lib/module_b.ex" => %{
22+
1 => "defmodule ModuleB do",
23+
2 => " def test do",
24+
3 => " \"test b\"",
25+
4 => " end",
26+
5 => "end"
27+
}
28+
} = Sentry.Sources.load_files()
29+
end
30+
31+
test "raises error when two files have the same relative path" do
32+
modify_env(:sentry, root_source_code_paths: [
33+
File.cwd!() <> "/test/support/example-umbrella-app-with-conflict/apps/app_a",
34+
File.cwd!() <> "/test/support/example-umbrella-app-with-conflict/apps/app_b"
35+
])
36+
37+
expected_error_message = """
38+
Found two source files in different source root paths with the same relative \
39+
path: lib/module_a.ex
40+
41+
This means that both source files would be reported to Sentry as the same \
42+
file. Please rename one of them to avoid this.
43+
"""
44+
45+
assert_raise RuntimeError, expected_error_message, fn ->
46+
Sentry.Sources.load_files()
47+
end
48+
end
49+
end
50+
651
test "exception makes call to Sentry API" do
752
correct_context = %{
853
"context_line" => " raise RuntimeError, \"Error\"",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
defmodule ModuleA do
2+
def test do
3+
"test a"
4+
end
5+
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
defmodule ModuleB do
2+
def test do
3+
"test b"
4+
end
5+
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
defmodule ModuleA do
2+
def test do
3+
"test a"
4+
end
5+
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
defmodule ModuleB do
2+
def test do
3+
"test b"
4+
end
5+
end

0 commit comments

Comments
 (0)