Skip to content

Commit 57e6f65

Browse files
committed
fix: :source_code_exclude_patterns support for OTP-28+
1 parent a5f2d3d commit 57e6f65

5 files changed

Lines changed: 119 additions & 7 deletions

File tree

lib/sentry/config.ex

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -427,11 +427,14 @@ defmodule Sentry.Config do
427427
source_code_exclude_patterns: [
428428
type:
429429
{:list,
430-
{:custom, __MODULE__, :__validate_struct__, [:source_code_exclude_patterns, Regex]}},
431-
type_doc: "list of `t:Regex.t/0`",
430+
{:custom, __MODULE__, :__validate_source_code_exclude_pattern__, []}},
431+
type_doc: "list of `t:Regex.t/0` or `t:String.t/0`",
432432
doc: """
433433
A list of regular expressions used to determine which files to
434-
exclude from source code context.
434+
exclude from source code context. Each element can be either a compiled
435+
`Regex` or a string pattern that will be compiled to a regex at runtime.
436+
Using strings is recommended for OTP 28+ compatibility with releases, as
437+
compiled regexes cannot be serialized in release config files.
435438
"""
436439
],
437440
source_code_map_path: [
@@ -891,4 +894,25 @@ defmodule Sentry.Config do
891894
{:error, "expected #{inspect(key)} to be a #{inspect(mod)} struct, got: #{inspect(term)}"}
892895
end
893896
end
897+
898+
def __validate_source_code_exclude_pattern__(term) when is_struct(term, Regex) do
899+
{:ok, term}
900+
end
901+
902+
def __validate_source_code_exclude_pattern__(term) when is_binary(term) do
903+
case Regex.compile(term) do
904+
{:ok, _regex} ->
905+
# Keep the string - it will be compiled at runtime in Sources
906+
{:ok, term}
907+
908+
{:error, {reason, position}} ->
909+
{:error,
910+
"invalid regex pattern #{inspect(term)}: #{reason} at position #{position}"}
911+
end
912+
end
913+
914+
def __validate_source_code_exclude_pattern__(term) do
915+
{:error,
916+
"expected a Regex or a string pattern, got: #{inspect(term)}"}
917+
end
894918
end

lib/sentry/sources.ex

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,12 @@ defmodule Sentry.Sources do
107107
path_pattern = Keyword.fetch!(config, :source_code_path_pattern)
108108

109109
exclude_patterns =
110-
Keyword.get(
111-
config,
110+
config
111+
|> Keyword.get(
112112
:source_code_exclude_patterns,
113113
[~r"/_build/", ~r"/deps/", ~r"/priv/", ~r"/test/"]
114114
)
115+
|> compile_patterns()
115116

116117
config
117118
|> Keyword.fetch!(:root_source_code_paths)
@@ -207,6 +208,20 @@ defmodule Sentry.Sources do
207208
|> exclude_files(rest)
208209
end
209210

211+
# Compile string patterns to regexes at runtime, pass through already-compiled regexes.
212+
# This supports OTP 28+ where regexes cannot be serialized in release config files.
213+
defp compile_patterns(patterns) when is_list(patterns) do
214+
Enum.map(patterns, &compile_pattern/1)
215+
end
216+
217+
defp compile_patterns(nil), do: nil
218+
219+
defp compile_pattern(pattern) when is_struct(pattern, Regex), do: pattern
220+
221+
defp compile_pattern(pattern) when is_binary(pattern) do
222+
Regex.compile!(pattern)
223+
end
224+
210225
defp source_to_lines(source) do
211226
String.replace_suffix(source, "\n", "")
212227
|> String.split("\n")

test/sentry/config_test.exs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,24 @@ defmodule Sentry.ConfigTest do
9999
config = [source_code_exclude_patterns: [regex]]
100100
assert Config.validate!(config)[:source_code_exclude_patterns] == [regex]
101101

102-
message = ~r/invalid list in :source_code_exclude_patterns option/
102+
config = [source_code_exclude_patterns: ["foo", "bar/baz"]]
103+
assert Config.validate!(config)[:source_code_exclude_patterns] == ["foo", "bar/baz"]
104+
105+
config = [source_code_exclude_patterns: [~r/foo/, "bar"]]
106+
[regex, string] = Config.validate!(config)[:source_code_exclude_patterns]
107+
assert regex.source == "foo"
108+
assert string == "bar"
109+
110+
message = ~r/invalid regex pattern/
111+
112+
assert_raise ArgumentError, message, fn ->
113+
Config.validate!(source_code_exclude_patterns: ["[invalid"])
114+
end
115+
116+
message = ~r/expected a Regex or a string pattern/
103117

104118
assert_raise ArgumentError, message, fn ->
105-
Config.validate!(source_code_exclude_patterns: ["foo"])
119+
Config.validate!(source_code_exclude_patterns: [:atom])
106120
end
107121
end
108122

test/sources_test.exs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,57 @@ defmodule Sentry.SourcesTest do
6464
To fix this, you'll have to rename one of the conflicting paths.
6565
"""
6666
end
67+
68+
test "accepts string patterns for source_code_exclude_patterns (OTP 28+ compatibility)" do
69+
paths = [
70+
File.cwd!() <> "/test/fixtures/example-umbrella-app/apps/app_a",
71+
File.cwd!() <> "/test/fixtures/example-umbrella-app/apps/app_b"
72+
]
73+
74+
assert {:ok, result} =
75+
Sources.load_files(
76+
root_source_code_paths: paths,
77+
source_code_exclude_patterns: ["module_b"]
78+
)
79+
80+
assert Map.has_key?(result, "lib/module_a.ex")
81+
refute Map.has_key?(result, "lib/module_b.ex")
82+
end
83+
84+
test "accepts mixed string and regex patterns for source_code_exclude_patterns" do
85+
paths = [
86+
File.cwd!() <> "/test/fixtures/example-umbrella-app/apps/app_a",
87+
File.cwd!() <> "/test/fixtures/example-umbrella-app/apps/app_b"
88+
]
89+
90+
assert {:ok, result} =
91+
Sources.load_files(
92+
root_source_code_paths: paths,
93+
source_code_exclude_patterns: [~r/module_a/, "module_b"]
94+
)
95+
96+
refute Map.has_key?(result, "lib/module_a.ex")
97+
refute Map.has_key?(result, "lib/module_b.ex")
98+
end
99+
100+
test "string patterns survive term serialization (OTP 28+ release simulation)" do
101+
paths = [
102+
File.cwd!() <> "/test/fixtures/example-umbrella-app/apps/app_a",
103+
File.cwd!() <> "/test/fixtures/example-umbrella-app/apps/app_b"
104+
]
105+
106+
original_config = [
107+
root_source_code_paths: paths,
108+
source_code_exclude_patterns: ["module_b", "/deps/"]
109+
]
110+
111+
serialized = :erlang.term_to_binary(original_config)
112+
deserialized_config = :erlang.binary_to_term(serialized)
113+
114+
assert {:ok, result} = Sources.load_files(deserialized_config)
115+
assert Map.has_key?(result, "lib/module_a.ex")
116+
refute Map.has_key?(result, "lib/module_b.ex")
117+
end
67118
end
68119

69120
describe "load_source_code_map_if_present/0" do

test_integrations/phoenix_app/config/prod.exs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,13 @@ config :swoosh, local: false
1717
# Do not print debug messages in production
1818
config :logger, level: :info
1919

20+
# Configure Sentry for production with source code context
21+
# Using string patterns instead of regexes for OTP 28+/Elixir 1.18+ compatibility
22+
# See: https://github.com/getsentry/sentry-elixir/issues/951
23+
config :sentry,
24+
enable_source_code_context: true,
25+
root_source_code_paths: [File.cwd!()],
26+
source_code_exclude_patterns: ["/_build/", "/deps/", "/priv/", "/test/"]
27+
2028
# Runtime production configuration, including reading
2129
# of environment variables, is done on config/runtime.exs.

0 commit comments

Comments
 (0)