Skip to content

Commit 913ecff

Browse files
authored
Relax version reqs on opentelemetry deps (#931)
* Fix flaky test * Relax version reqs on opentelemetry deps This enables installing sentry in apps with old opentelemetry libs, that are not compatible with tracing, in which case tracing is simply not loaded and cannot be used as a feature. Closes #928 * Explain why we allow >= 0.0.0 for otel libs * Add jason for older elixirs * Update CHANGELOG * Warn if otel deps are not compatible but tracing is enabled in config
1 parent f83b508 commit 913ecff

File tree

19 files changed

+590
-122
lines changed

19 files changed

+590
-122
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ erl_crash.dump
1414
/priv/sentry.map
1515

1616
test_integrations/phoenix_app/db
17+
18+
test_integrations/*/_build
19+
test_integrations/*/deps

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## Unreleased
2+
3+
#### Various improvements
4+
5+
- Allow any version of opentelemetry deps and verify minimum versions internally - this makes it possible to use `sentry` *with tracing disabled* along with older versions of opentelemetry deps ([#931](https://github.com/getsentry/sentry-elixir/pull/931))
6+
17
## 11.0.2
28

39
### Bug fixes

lib/sentry/config.ex

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,7 @@ defmodule Sentry.Config do
554554
|> normalize_included_environments()
555555
|> normalize_environment()
556556
|> handle_deprecated_before_send()
557+
|> warn_traces_sample_rate_without_dependencies()
557558

558559
{:error, error} ->
559560
raise ArgumentError, """
@@ -701,7 +702,10 @@ defmodule Sentry.Config do
701702
def integrations, do: fetch!(:integrations)
702703

703704
@spec tracing?() :: boolean()
704-
def tracing?, do: not is_nil(fetch!(:traces_sample_rate)) or not is_nil(get(:traces_sampler))
705+
def tracing? do
706+
(Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() and
707+
not is_nil(fetch!(:traces_sample_rate))) or not is_nil(get(:traces_sampler))
708+
end
705709

706710
@spec put_config(atom(), term()) :: :ok
707711
def put_config(key, value) when is_atom(key) do
@@ -762,6 +766,25 @@ defmodule Sentry.Config do
762766
end
763767
end
764768

769+
defp warn_traces_sample_rate_without_dependencies(opts) do
770+
traces_sample_rate = Keyword.get(opts, :traces_sample_rate)
771+
772+
if not is_nil(traces_sample_rate) and
773+
not Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() do
774+
require Logger
775+
776+
Logger.warning("""
777+
Sentry tracing is configured with traces_sample_rate: #{inspect(traces_sample_rate)}, \
778+
but the required OpenTelemetry dependencies are not satisfied. \
779+
Tracing will be disabled. Please ensure you have compatible versions of: \
780+
opentelemetry (>= 1.5.0), opentelemetry_api (>= 1.4.0), \
781+
opentelemetry_exporter (>= 1.0.0), and opentelemetry_semantic_conventions (>= 1.27.0).
782+
""")
783+
end
784+
785+
opts
786+
end
787+
765788
defp normalize_environment(config) do
766789
Keyword.update!(config, :environment_name, &to_string/1)
767790
end

lib/sentry/opentelemetry/sampler.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
if Code.ensure_loaded?(:otel_sampler) do
1+
if Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() do
22
defmodule Sentry.OpenTelemetry.Sampler do
33
@moduledoc false
44

lib/sentry/opentelemetry/span_processor.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
if Code.ensure_loaded?(OpenTelemetry) do
1+
if Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() do
22
defmodule Sentry.OpenTelemetry.SpanProcessor do
33
@moduledoc false
44

lib/sentry/opentelemetry/span_record.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
if Code.ensure_loaded?(OpenTelemetry) do
1+
if Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() do
22
defmodule Sentry.OpenTelemetry.SpanRecord do
33
@moduledoc false
44

Lines changed: 110 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,151 +1,153 @@
1-
defmodule Sentry.OpenTelemetry.SpanStorage do
2-
@moduledoc false
3-
use GenServer
1+
if Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() do
2+
defmodule Sentry.OpenTelemetry.SpanStorage do
3+
@moduledoc false
4+
use GenServer
45

5-
defstruct [:cleanup_interval, :table_name]
6+
defstruct [:cleanup_interval, :table_name]
67

7-
alias Sentry.OpenTelemetry.SpanRecord
8+
alias Sentry.OpenTelemetry.SpanRecord
89

9-
@cleanup_interval :timer.minutes(5)
10+
@cleanup_interval :timer.minutes(5)
1011

11-
@span_ttl 30 * 60
12+
@span_ttl 30 * 60
1213

13-
@spec start_link(keyword()) :: GenServer.on_start()
14-
def start_link(opts) when is_list(opts) do
15-
name = Keyword.get(opts, :name, __MODULE__)
16-
GenServer.start_link(__MODULE__, opts, name: name)
17-
end
14+
@spec start_link(keyword()) :: GenServer.on_start()
15+
def start_link(opts) when is_list(opts) do
16+
name = Keyword.get(opts, :name, __MODULE__)
17+
GenServer.start_link(__MODULE__, opts, name: name)
18+
end
1819

19-
@impl true
20-
def init(opts) do
21-
table_name = Keyword.get(opts, :table_name, default_table_name())
22-
cleanup_interval = Keyword.get(opts, :cleanup_interval, @cleanup_interval)
20+
@impl true
21+
def init(opts) do
22+
table_name = Keyword.get(opts, :table_name, default_table_name())
23+
cleanup_interval = Keyword.get(opts, :cleanup_interval, @cleanup_interval)
2324

24-
_ = :ets.new(table_name, [:named_table, :public, :ordered_set])
25+
_ = :ets.new(table_name, [:named_table, :public, :ordered_set])
2526

26-
schedule_cleanup(cleanup_interval)
27+
schedule_cleanup(cleanup_interval)
2728

28-
{:ok, %__MODULE__{cleanup_interval: cleanup_interval, table_name: table_name}}
29-
end
29+
{:ok, %__MODULE__{cleanup_interval: cleanup_interval, table_name: table_name}}
30+
end
3031

31-
@impl true
32-
def handle_info(:cleanup_stale_spans, state) do
33-
cleanup_stale_spans(state.table_name)
34-
schedule_cleanup(state.cleanup_interval)
32+
@impl true
33+
def handle_info(:cleanup_stale_spans, state) do
34+
cleanup_stale_spans(state.table_name)
35+
schedule_cleanup(state.cleanup_interval)
3536

36-
{:noreply, state}
37-
end
37+
{:noreply, state}
38+
end
3839

39-
@spec store_span(SpanRecord.t(), keyword()) :: true
40-
def store_span(span_data, opts \\ []) do
41-
table_name = Keyword.get(opts, :table_name, default_table_name())
42-
stored_at = System.system_time(:second)
40+
@spec store_span(SpanRecord.t(), keyword()) :: true
41+
def store_span(span_data, opts \\ []) do
42+
table_name = Keyword.get(opts, :table_name, default_table_name())
43+
stored_at = System.system_time(:second)
4344

44-
if span_data.parent_span_id == nil do
45-
:ets.insert(table_name, {{:root_span, span_data.span_id}, span_data, stored_at})
46-
else
47-
key = {:child_span, span_data.parent_span_id, span_data.span_id}
45+
if span_data.parent_span_id == nil do
46+
:ets.insert(table_name, {{:root_span, span_data.span_id}, span_data, stored_at})
47+
else
48+
key = {:child_span, span_data.parent_span_id, span_data.span_id}
4849

49-
:ets.insert(table_name, {key, span_data, stored_at})
50+
:ets.insert(table_name, {key, span_data, stored_at})
51+
end
5052
end
51-
end
5253

53-
@spec get_root_span(String.t(), keyword()) :: SpanRecord.t() | nil
54-
def get_root_span(span_id, opts \\ []) do
55-
table_name = Keyword.get(opts, :table_name, default_table_name())
54+
@spec get_root_span(String.t(), keyword()) :: SpanRecord.t() | nil
55+
def get_root_span(span_id, opts \\ []) do
56+
table_name = Keyword.get(opts, :table_name, default_table_name())
5657

57-
case :ets.lookup(table_name, {:root_span, span_id}) do
58-
[{{:root_span, ^span_id}, span, _stored_at}] -> span
59-
[] -> nil
58+
case :ets.lookup(table_name, {:root_span, span_id}) do
59+
[{{:root_span, ^span_id}, span, _stored_at}] -> span
60+
[] -> nil
61+
end
6062
end
61-
end
6263

63-
@spec get_child_spans(String.t(), keyword()) :: [SpanRecord.t()]
64-
def get_child_spans(parent_span_id, opts \\ []) do
65-
table_name = Keyword.get(opts, :table_name, default_table_name())
64+
@spec get_child_spans(String.t(), keyword()) :: [SpanRecord.t()]
65+
def get_child_spans(parent_span_id, opts \\ []) do
66+
table_name = Keyword.get(opts, :table_name, default_table_name())
6667

67-
get_all_descendants(parent_span_id, table_name)
68-
end
68+
get_all_descendants(parent_span_id, table_name)
69+
end
6970

70-
defp get_all_descendants(parent_span_id, table_name) do
71-
direct_children =
72-
:ets.match_object(table_name, {{:child_span, parent_span_id, :_}, :_, :_})
73-
|> Enum.map(fn {_key, span_data, _stored_at} -> span_data end)
71+
defp get_all_descendants(parent_span_id, table_name) do
72+
direct_children =
73+
:ets.match_object(table_name, {{:child_span, parent_span_id, :_}, :_, :_})
74+
|> Enum.map(fn {_key, span_data, _stored_at} -> span_data end)
7475

75-
nested_descendants =
76-
Enum.flat_map(direct_children, fn child ->
77-
get_all_descendants(child.span_id, table_name)
78-
end)
76+
nested_descendants =
77+
Enum.flat_map(direct_children, fn child ->
78+
get_all_descendants(child.span_id, table_name)
79+
end)
7980

80-
(direct_children ++ nested_descendants)
81-
|> Enum.sort_by(& &1.start_time)
82-
end
81+
(direct_children ++ nested_descendants)
82+
|> Enum.sort_by(& &1.start_time)
83+
end
8384

84-
@spec update_span(SpanRecord.t(), keyword()) :: :ok
85-
def update_span(%{parent_span_id: parent_span_id} = span_data, opts \\ []) do
86-
table_name = Keyword.get(opts, :table_name, default_table_name())
87-
stored_at = System.system_time(:second)
85+
@spec update_span(SpanRecord.t(), keyword()) :: :ok
86+
def update_span(%{parent_span_id: parent_span_id} = span_data, opts \\ []) do
87+
table_name = Keyword.get(opts, :table_name, default_table_name())
88+
stored_at = System.system_time(:second)
8889

89-
key =
90-
if parent_span_id == nil do
91-
{:root_span, span_data.span_id}
92-
else
93-
{:child_span, parent_span_id, span_data.span_id}
94-
end
90+
key =
91+
if parent_span_id == nil do
92+
{:root_span, span_data.span_id}
93+
else
94+
{:child_span, parent_span_id, span_data.span_id}
95+
end
9596

96-
:ets.update_element(table_name, key, [{2, span_data}, {3, stored_at}])
97+
:ets.update_element(table_name, key, [{2, span_data}, {3, stored_at}])
9798

98-
:ok
99-
end
99+
:ok
100+
end
100101

101-
@spec remove_root_span(String.t(), keyword()) :: :ok
102-
def remove_root_span(span_id, opts \\ []) do
103-
table_name = Keyword.get(opts, :table_name, default_table_name())
104-
key = {:root_span, span_id}
102+
@spec remove_root_span(String.t(), keyword()) :: :ok
103+
def remove_root_span(span_id, opts \\ []) do
104+
table_name = Keyword.get(opts, :table_name, default_table_name())
105+
key = {:root_span, span_id}
105106

106-
:ets.select_delete(table_name, [{{key, :_, :_}, [], [true]}])
107-
remove_child_spans(span_id, table_name: table_name)
107+
:ets.select_delete(table_name, [{{key, :_, :_}, [], [true]}])
108+
remove_child_spans(span_id, table_name: table_name)
108109

109-
:ok
110-
end
110+
:ok
111+
end
111112

112-
@spec remove_child_spans(String.t(), keyword()) :: :ok
113-
def remove_child_spans(parent_span_id, opts) do
114-
table_name = Keyword.get(opts, :table_name, default_table_name())
113+
@spec remove_child_spans(String.t(), keyword()) :: :ok
114+
def remove_child_spans(parent_span_id, opts) do
115+
table_name = Keyword.get(opts, :table_name, default_table_name())
115116

116-
:ets.select_delete(table_name, [
117-
{{{:child_span, parent_span_id, :_}, :_, :_}, [], [true]}
118-
])
117+
:ets.select_delete(table_name, [
118+
{{{:child_span, parent_span_id, :_}, :_, :_}, [], [true]}
119+
])
119120

120-
:ok
121-
end
121+
:ok
122+
end
122123

123-
defp schedule_cleanup(interval) do
124-
Process.send_after(self(), :cleanup_stale_spans, interval)
125-
end
124+
defp schedule_cleanup(interval) do
125+
Process.send_after(self(), :cleanup_stale_spans, interval)
126+
end
126127

127-
defp cleanup_stale_spans(table_name) do
128-
now = System.system_time(:second)
129-
cutoff_time = now - @span_ttl
128+
defp cleanup_stale_spans(table_name) do
129+
now = System.system_time(:second)
130+
cutoff_time = now - @span_ttl
130131

131-
root_match_spec = [
132-
{{{:root_span, :"$1"}, :_, :"$2"}, [{:<, :"$2", cutoff_time}], [:"$1"]}
133-
]
132+
root_match_spec = [
133+
{{{:root_span, :"$1"}, :_, :"$2"}, [{:<, :"$2", cutoff_time}], [:"$1"]}
134+
]
134135

135-
expired_root_spans = :ets.select(table_name, root_match_spec)
136+
expired_root_spans = :ets.select(table_name, root_match_spec)
136137

137-
Enum.each(expired_root_spans, fn span_id ->
138-
remove_root_span(span_id, table_name: table_name)
139-
end)
138+
Enum.each(expired_root_spans, fn span_id ->
139+
remove_root_span(span_id, table_name: table_name)
140+
end)
140141

141-
child_match_spec = [
142-
{{{:child_span, :_, :_}, :_, :"$1"}, [{:<, :"$1", cutoff_time}], [true]}
143-
]
142+
child_match_spec = [
143+
{{{:child_span, :_, :_}, :_, :"$1"}, [{:<, :"$1", cutoff_time}], [true]}
144+
]
144145

145-
:ets.select_delete(table_name, child_match_spec)
146-
end
146+
:ets.select_delete(table_name, child_match_spec)
147+
end
147148

148-
defp default_table_name do
149-
Module.concat(__MODULE__, ETSTable)
149+
defp default_table_name do
150+
Module.concat(__MODULE__, ETSTable)
151+
end
150152
end
151153
end

0 commit comments

Comments
 (0)