Packages
sentry
11.0.4
13.3.0
13.2.0
13.1.0
13.0.1
13.0.0
12.0.3
12.0.2
12.0.1
12.0.0
11.0.4
11.0.3
11.0.2
11.0.1
11.0.0
10.10.0
10.9.0
10.8.1
10.8.0
10.7.1
10.7.0
10.6.2
10.6.1
10.6.0
10.5.0
10.4.0
10.3.0
10.2.1
10.2.0
10.2.0-rc.2
10.2.0-rc.1
10.1.0
10.0.3
10.0.2
10.0.1
10.0.0
9.1.0
9.0.0
8.1.0
8.0.6
8.0.5
8.0.4
8.0.3
8.0.2
8.0.1
8.0.0
8.0.0-rc.2
8.0.0-rc.1
8.0.0-rc.0
retired
7.2.5
7.2.4
7.2.3
7.2.2
7.2.1
7.2.0
7.1.0
7.0.6
7.0.5
7.0.4
7.0.3
7.0.2
7.0.1
7.0.0
6.4.2
6.4.1
6.4.0
6.3.0
6.2.1
6.2.0
6.1.0
6.0.5
6.0.4
6.0.3
6.0.2
6.0.1
6.0.0
5.0.1
5.0.0
4.0.3
4.0.2
4.0.1
4.0.0
3.0.0
2.2.0
2.1.0
2.0.2
2.0.1
2.0.0
1.1.2
1.1.1
1.1.0
1.0.0
0.3.2
0.3.1
0.3.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
The Official Elixir client for Sentry
Current section
Files
Jump to
Current section
Files
lib/sentry/opentelemetry/span_storage.ex
if Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() do
defmodule Sentry.OpenTelemetry.SpanStorage do
@moduledoc false
use GenServer
defstruct [:cleanup_interval, :table_name]
alias Sentry.OpenTelemetry.SpanRecord
@cleanup_interval :timer.minutes(5)
@span_ttl 30 * 60
@spec start_link(keyword()) :: GenServer.on_start()
def start_link(opts) when is_list(opts) do
name = Keyword.get(opts, :name, __MODULE__)
GenServer.start_link(__MODULE__, opts, name: name)
end
@impl true
def init(opts) do
table_name = Keyword.get(opts, :table_name, default_table_name())
cleanup_interval = Keyword.get(opts, :cleanup_interval, @cleanup_interval)
_ = :ets.new(table_name, [:named_table, :public, :ordered_set])
schedule_cleanup(cleanup_interval)
{:ok, %__MODULE__{cleanup_interval: cleanup_interval, table_name: table_name}}
end
@impl true
def handle_info(:cleanup_stale_spans, state) do
cleanup_stale_spans(state.table_name)
schedule_cleanup(state.cleanup_interval)
{:noreply, state}
end
@spec store_span(SpanRecord.t(), keyword()) :: true
def store_span(span_data, opts \\ []) do
table_name = Keyword.get(opts, :table_name, default_table_name())
stored_at = System.system_time(:second)
if span_data.parent_span_id == nil do
:ets.insert(table_name, {{:root_span, span_data.span_id}, span_data, stored_at})
else
key = {:child_span, span_data.parent_span_id, span_data.span_id}
:ets.insert(table_name, {key, span_data, stored_at})
end
end
@spec get_root_span(String.t(), keyword()) :: SpanRecord.t() | nil
def get_root_span(span_id, opts \\ []) do
table_name = Keyword.get(opts, :table_name, default_table_name())
case :ets.lookup(table_name, {:root_span, span_id}) do
[{{:root_span, ^span_id}, span, _stored_at}] -> span
[] -> nil
end
end
@spec get_child_spans(String.t(), keyword()) :: [SpanRecord.t()]
def get_child_spans(parent_span_id, opts \\ []) do
table_name = Keyword.get(opts, :table_name, default_table_name())
get_all_descendants(parent_span_id, table_name)
end
defp get_all_descendants(parent_span_id, table_name) do
direct_children =
:ets.match_object(table_name, {{:child_span, parent_span_id, :_}, :_, :_})
|> Enum.map(fn {_key, span_data, _stored_at} -> span_data end)
nested_descendants =
Enum.flat_map(direct_children, fn child ->
get_all_descendants(child.span_id, table_name)
end)
(direct_children ++ nested_descendants)
|> Enum.sort_by(& &1.start_time)
end
@spec update_span(SpanRecord.t(), keyword()) :: :ok
def update_span(%{parent_span_id: parent_span_id} = span_data, opts \\ []) do
table_name = Keyword.get(opts, :table_name, default_table_name())
stored_at = System.system_time(:second)
key =
if parent_span_id == nil do
{:root_span, span_data.span_id}
else
{:child_span, parent_span_id, span_data.span_id}
end
:ets.update_element(table_name, key, [{2, span_data}, {3, stored_at}])
:ok
end
@spec remove_root_span(String.t(), keyword()) :: :ok
def remove_root_span(span_id, opts \\ []) do
table_name = Keyword.get(opts, :table_name, default_table_name())
key = {:root_span, span_id}
:ets.select_delete(table_name, [{{key, :_, :_}, [], [true]}])
remove_child_spans(span_id, table_name: table_name)
:ok
end
@spec remove_child_spans(String.t(), keyword()) :: :ok
def remove_child_spans(parent_span_id, opts) do
table_name = Keyword.get(opts, :table_name, default_table_name())
:ets.select_delete(table_name, [
{{{:child_span, parent_span_id, :_}, :_, :_}, [], [true]}
])
:ok
end
defp schedule_cleanup(interval) do
Process.send_after(self(), :cleanup_stale_spans, interval)
end
defp cleanup_stale_spans(table_name) do
now = System.system_time(:second)
cutoff_time = now - @span_ttl
root_match_spec = [
{{{:root_span, :"$1"}, :_, :"$2"}, [{:<, :"$2", cutoff_time}], [:"$1"]}
]
expired_root_spans = :ets.select(table_name, root_match_spec)
Enum.each(expired_root_spans, fn span_id ->
remove_root_span(span_id, table_name: table_name)
end)
child_match_spec = [
{{{:child_span, :_, :_}, :_, :"$1"}, [{:<, :"$1", cutoff_time}], [true]}
]
:ets.select_delete(table_name, child_match_spec)
end
defp default_table_name do
Module.concat(__MODULE__, ETSTable)
end
end
end