Skip to content
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

Support for Traces/Transactions via Opentelemetry #784

Open
wants to merge 39 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e08210c
Support configurable DSN for phoenix test app
solnic Sep 24, 2024
2ffd435
Initial work on OTel-based Transactions
solnic Sep 4, 2024
a7b030b
Move SpanStorage to its own file
solnic Sep 25, 2024
0b84a81
Rename Sentry.Telemetry => Sentry.Opentelemetry
solnic Sep 25, 2024
c001d1b
Introduce SpanRecord to simplify processing
solnic Sep 25, 2024
ddecba0
Move casting trace_id to the SpanRecord struct
solnic Sep 25, 2024
9777500
Move casting of span ids to the SpanRecord struct
solnic Sep 25, 2024
4fa80f2
No need to pass trace_id around anymore
solnic Sep 25, 2024
f4330ec
Handle casting timestamps in the SpanRecord struct
solnic Sep 25, 2024
c2c0bc9
Extract SpanRecord into its own file
solnic Sep 25, 2024
b3dceb6
Remove redundant function
solnic Sep 25, 2024
2b68f86
Move setting platform info to the Client
solnic Sep 25, 2024
64d0344
Extract setting root span context
solnic Sep 25, 2024
e9901da
Improve ecto span/transaction
solnic Sep 25, 2024
b681c9a
Unify top-level ecto transactions and ecto spans
solnic Sep 25, 2024
61baf6a
Return full URL and status info in Phoenix transactions
solnic Sep 25, 2024
e47521a
Fix extracting span record when sentry is an external dep
solnic Sep 25, 2024
ca1606f
Support for liveview traces/spans
solnic Sep 26, 2024
f0cdc22
Remove span records after sending a transaction
solnic Sep 26, 2024
faf9070
Fix formatting
solnic Sep 26, 2024
edcc9a3
Fix formatting
solnic Sep 26, 2024
7e65e12
Fix dialyzer warnings
solnic Sep 27, 2024
b64aff5
More dialyzer fixes
solnic Sep 27, 2024
3b4276a
Fix build for 1.16
solnic Oct 23, 2024
d166232
Remove debugging statement
solnic Oct 23, 2024
fe785d2
Update lib/sentry/opentelemetry/span_processor.ex
solnic Oct 25, 2024
2b6a9d5
WIP - rework SpanStorage to use ETS
solnic Oct 28, 2024
32ea20b
Refactor span storage (#817)
savhappy Dec 6, 2024
4265136
Add tests for Sentry.send_transaction
solnic Dec 9, 2024
09456d8
Opentelemetry => OpenTelemetry
solnic Dec 9, 2024
5dc155c
Use SpanStorage alias in the test
solnic Dec 9, 2024
75f781d
Tests for SpanStorage
solnic Dec 9, 2024
e2ee522
Remove child spans from SpanStorage automatically
solnic Dec 9, 2024
4e8e737
Add sweeping of expired spans
solnic Dec 9, 2024
bf9e35f
Make opentelemetry libs optional deps
solnic Dec 9, 2024
7490f9c
Fix tests under 1.13
solnic Dec 13, 2024
83b0162
Support Transaction in client reports
solnic Dec 13, 2024
ef1b77c
wip - initial work on oban support
solnic Dec 13, 2024
96d50e6
wip - add a UI for testing Oban workers
solnic Dec 13, 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
2 changes: 2 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ 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 :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: "10.7.1"
}
})
end

defp render_exception(%Interfaces.Exception{} = exception) do
exception
|> Map.from_struct()
Expand Down
38 changes: 35 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,16 +48,35 @@ 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(%CheckIn{}), do: "monitor"
def get_data_category(%ClientReport{}), do: "internal"
def get_data_category(%Event{}), do: "error"
# TODO: is this correct?
def get_data_category(%Transaction{}), do: "error"

@doc """
Encodes the envelope into its binary representation.
Expand Down Expand Up @@ -126,4 +147,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
Loading
Loading