Skip to content

Support for Traces/Transactions via Opentelemetry #784

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 53 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
ec16ca8
Support configurable DSN for phoenix test app
solnic Sep 24, 2024
f214b82
Initial work on OTel-based Transactions
solnic Sep 4, 2024
59f0c2b
Move SpanStorage to its own file
solnic Sep 25, 2024
025ec6e
Rename Sentry.Telemetry => Sentry.Opentelemetry
solnic Sep 25, 2024
f7fb086
Introduce SpanRecord to simplify processing
solnic Sep 25, 2024
ab82965
Move casting trace_id to the SpanRecord struct
solnic Sep 25, 2024
e953b26
Move casting of span ids to the SpanRecord struct
solnic Sep 25, 2024
2d97073
No need to pass trace_id around anymore
solnic Sep 25, 2024
91ef16f
Handle casting timestamps in the SpanRecord struct
solnic Sep 25, 2024
4f2e63b
Extract SpanRecord into its own file
solnic Sep 25, 2024
6e19f48
Remove redundant function
solnic Sep 25, 2024
fca71df
Move setting platform info to the Client
solnic Sep 25, 2024
2f62ce7
Extract setting root span context
solnic Sep 25, 2024
fc91ca7
Improve ecto span/transaction
solnic Sep 25, 2024
a79dbc3
Unify top-level ecto transactions and ecto spans
solnic Sep 25, 2024
e9b6ef5
Return full URL and status info in Phoenix transactions
solnic Sep 25, 2024
b8c0c86
Fix extracting span record when sentry is an external dep
solnic Sep 25, 2024
5883053
Support for liveview traces/spans
solnic Sep 26, 2024
3155116
Remove span records after sending a transaction
solnic Sep 26, 2024
d2e3669
Fix formatting
solnic Sep 26, 2024
23f7122
Fix formatting
solnic Sep 26, 2024
83180e6
Fix dialyzer warnings
solnic Sep 27, 2024
770c67e
More dialyzer fixes
solnic Sep 27, 2024
8d9e81d
Fix build for 1.16
solnic Oct 23, 2024
8bab636
Remove debugging statement
solnic Oct 23, 2024
72c7d98
Update lib/sentry/opentelemetry/span_processor.ex
solnic Oct 25, 2024
7061f3c
WIP - rework SpanStorage to use ETS
solnic Oct 28, 2024
631b824
Refactor span storage (#817)
savhappy Dec 6, 2024
3dd1a94
Add tests for Sentry.send_transaction
solnic Dec 9, 2024
aa9f5f5
Opentelemetry => OpenTelemetry
solnic Dec 9, 2024
6301fe5
Use SpanStorage alias in the test
solnic Dec 9, 2024
3e93cfa
Tests for SpanStorage
solnic Dec 9, 2024
b7eb637
Remove child spans from SpanStorage automatically
solnic Dec 9, 2024
70f8e61
Add sweeping of expired spans
solnic Dec 9, 2024
54d1f20
Make opentelemetry libs optional deps
solnic Dec 9, 2024
6788ad5
Fix tests under 1.13
solnic Dec 13, 2024
7b2fc02
Support Transaction in client reports
solnic Dec 13, 2024
4f3ada1
wip - initial work on oban support
solnic Dec 13, 2024
1856faa
wip - add a UI for testing Oban workers
solnic Dec 13, 2024
6c2d80c
Refactor OTel (#835)
danschultzer Dec 18, 2024
b7b1dc6
Update dep specs for opentelemetry_*
solnic Dec 18, 2024
10224eb
[tmp] use opentelemetry_oban from git
solnic Dec 18, 2024
f67446e
Fix warning in get_op_description
solnic Dec 18, 2024
4a175a9
Fix formatting
solnic Dec 18, 2024
d2f88a2
Refine how we set values for Oban jobs
solnic Dec 18, 2024
469b5cd
Extend the test worker UI with auto-scheduling
solnic Dec 18, 2024
9c59d36
Filter out Oban internal traces
solnic Dec 18, 2024
0832054
Oops, fix filtering Oban Stager spans
solnic Dec 18, 2024
01fcd87
Address dialyzer warning (I think)
solnic Dec 18, 2024
4c32d87
Set sdk version dynamically
solnic Dec 19, 2024
9b4d700
Fix client report data category for transactions
solnic Dec 20, 2024
7ad38bb
Add Sentry's Sampler for OTel
solnic Dec 20, 2024
a808377
Remove conditional we longer need
solnic Dec 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ if config_env() == :test do
config :logger, backends: []
end

config :opentelemetry, span_processor: {Sentry.OpenTelemetry.SpanProcessor, []}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@whatyouhide seems like this just doesn't do anything under Elixir 1.13 - do you maybe have any insights here? What would be an equivalent of this for older Elixir?


config :opentelemetry,
sampler: {Sentry.OpenTelemetry.Sampler, [drop: ["Elixir.Oban.Stager process"]]}

config :phoenix, :json_library, Jason
22 changes: 22 additions & 0 deletions lib/sentry.ex
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,28 @@ defmodule Sentry do
end
end

def send_transaction(transaction, options \\ []) do
# TODO: remove on v11.0.0, :included_environments was deprecated in 10.0.0.
included_envs = Config.included_environments()

cond do
Config.test_mode?() ->
Client.send_transaction(transaction, options)

!Config.dsn() ->
# We still validate options even if we're not sending the event. This aims at catching
# configuration issues during development instead of only when deploying to production.
_options = NimbleOptions.validate!(options, Options.send_event_schema())
:ignored

included_envs == :all or to_string(Config.environment_name()) in included_envs ->
Client.send_transaction(transaction, options)

true ->
:ignored
end
end

@doc """
Captures a check-in built with the given `options`.

Expand Down
1 change: 1 addition & 0 deletions lib/sentry/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ defmodule Sentry.Application do
Sentry.Sources,
Sentry.Dedupe,
Sentry.ClientReport.Sender,
Sentry.OpenTelemetry.SpanStorage,
{Sentry.Integrations.CheckInIDMappings,
[
max_expected_check_in_time:
Expand Down
72 changes: 71 additions & 1 deletion lib/sentry/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ defmodule Sentry.Client do
Interfaces,
LoggerUtils,
Transport,
Options
Options,
Transaction
}

require Logger
Expand Down Expand Up @@ -107,6 +108,26 @@ defmodule Sentry.Client do
|> Transport.encode_and_post_envelope(client, request_retries)
end

def send_transaction(%Transaction{} = transaction, opts \\ []) do
# opts = validate_options!(opts)

Comment on lines +112 to +113

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# opts = validate_options!(opts)

result_type = Keyword.get_lazy(opts, :result, &Config.send_result/0)
client = Keyword.get_lazy(opts, :client, &Config.client/0)

request_retries =
Keyword.get_lazy(opts, :request_retries, fn ->
Application.get_env(:sentry, :request_retries, Transport.default_retries())
end)

case encode_and_send(transaction, result_type, client, request_retries) do
{:ok, id} ->
{:ok, id}

{:error, %ClientError{} = error} ->
{:error, error}
end
end

defp sample_event(sample_rate) do
cond do
sample_rate == 1 -> :ok
Expand Down Expand Up @@ -205,6 +226,42 @@ defmodule Sentry.Client do
end
end

defp encode_and_send(
%Transaction{} = transaction,
_result_type = :sync,
client,
request_retries
) do
case Sentry.Test.maybe_collect(transaction) do
:collected ->
{:ok, ""}

:not_collecting ->
send_result =
transaction
|> Envelope.from_transaction()
|> Transport.encode_and_post_envelope(client, request_retries)

send_result
end
end

defp encode_and_send(
%Transaction{} = transaction,
_result_type = :none,
client,
_request_retries
) do
case Sentry.Test.maybe_collect(transaction) do
:collected ->
{:ok, ""}

:not_collecting ->
:ok = Transport.Sender.send_async(client, transaction)
{:ok, ""}
end
end

@spec render_event(Event.t()) :: map()
def render_event(%Event{} = event) do
json_library = Config.json_library()
Expand All @@ -225,6 +282,19 @@ defmodule Sentry.Client do
|> update_if_present(:threads, fn list -> Enum.map(list, &render_thread/1) end)
end

@spec render_transaction(%Transaction{}) :: map()
def render_transaction(%Transaction{} = transaction) do
transaction
|> Transaction.to_map()
|> Map.merge(%{
platform: "elixir",
sdk: %{
name: "sentry.elixir",
version: Application.spec(:sentry, :vsn)
}
})
end

defp render_exception(%Interfaces.Exception{} = exception) do
exception
|> Map.from_struct()
Expand Down
37 changes: 34 additions & 3 deletions lib/sentry/envelope.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ defmodule Sentry.Envelope do
@moduledoc false
# https://develop.sentry.dev/sdk/envelopes/

alias Sentry.{Attachment, CheckIn, ClientReport, Config, Event, UUID}
alias Sentry.{Attachment, CheckIn, ClientReport, Config, Event, UUID, Transaction}

@type t() :: %__MODULE__{
event_id: UUID.t(),
items: [Event.t() | Attachment.t() | CheckIn.t() | ClientReport.t(), ...]
items: [
Event.t() | Attachment.t() | CheckIn.t() | ClientReport.t() | Transaction.t()
]
}

@enforce_keys [:event_id, :items]
Expand Down Expand Up @@ -46,13 +48,31 @@ defmodule Sentry.Envelope do
}
end

@doc """
Creates a new envelope containing a transaction with spans.
"""
@spec from_transaction(Sentry.Transaction.t()) :: t()
def from_transaction(%Transaction{} = transaction) do
%__MODULE__{
event_id: transaction.event_id,
items: [transaction]
}
end

@doc """
Returns the "data category" of the envelope's contents (to be used in client reports and more).
"""
@doc since: "10.8.0"
@spec get_data_category(Attachment.t() | CheckIn.t() | ClientReport.t() | Event.t()) ::
@spec get_data_category(
Attachment.t()
| CheckIn.t()
| ClientReport.t()
| Event.t()
| Transaction.t()
) ::
String.t()
def get_data_category(%Attachment{}), do: "attachment"
def get_data_category(%Transaction{}), do: "transaction"
def get_data_category(%CheckIn{}), do: "monitor"
def get_data_category(%ClientReport{}), do: "internal"
def get_data_category(%Event{}), do: "error"
Expand Down Expand Up @@ -126,4 +146,15 @@ defmodule Sentry.Envelope do
throw(error)
end
end

defp item_to_binary(json_library, %Transaction{} = transaction) do
case transaction |> Sentry.Client.render_transaction() |> json_library.encode() do
{:ok, encoded_transaction} ->
header = ~s({"type": "transaction", "length": #{byte_size(encoded_transaction)}})
[header, ?\n, encoded_transaction, ?\n]

{:error, _reason} = error ->
throw(error)
end
end
end
27 changes: 27 additions & 0 deletions lib/sentry/opentelemetry/sampler.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
defmodule Sentry.OpenTelemetry.Sampler do
@moduledoc false

def setup(config) do
config
end

def description(_) do
"SentrySampler"
end

def should_sample(
_ctx,
_trace_id,
_links,
span_name,
_span_kind,
_attributes,
config
) do
if span_name in config[:drop] do
{:drop, [], []}
else
{:record_and_sample, [], []}
end
end
end
Loading
Loading