Skip to content

Commit d45aca4

Browse files
committed
Initial work on OTel-based Transactions
1 parent 6b15f17 commit d45aca4

File tree

34 files changed

+1080
-37
lines changed

34 files changed

+1080
-37
lines changed

config/config.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ if config_env() == :test do
1515
config :logger, backends: []
1616
end
1717

18+
config :opentelemetry, span_processor: {Sentry.Telemetry.SpanProcessor, []}
19+
1820
config :phoenix, :json_library, Jason

lib/sentry.ex

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,22 @@ defmodule Sentry do
361361
end
362362
end
363363

364+
def send_transaction(transaction, opts \\ []) do
365+
# TODO: remove on v11.0.0, :included_environments was deprecated in 10.0.0.
366+
included_envs = Config.included_environments()
367+
368+
cond do
369+
Config.test_mode?() ->
370+
Client.send_transaction(transaction, opts)
371+
372+
included_envs == :all or to_string(Config.environment_name()) in included_envs ->
373+
Client.send_transaction(transaction, opts)
374+
375+
true ->
376+
:ignored
377+
end
378+
end
379+
364380
@doc """
365381
Captures a check-in built with the given `options`.
366382

lib/sentry/application.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ defmodule Sentry.Application do
2626
{Registry, keys: :unique, name: Sentry.Transport.SenderRegistry},
2727
Sentry.Sources,
2828
Sentry.Dedupe,
29+
Sentry.Telemetry.SpanProcessor.SpanStorage,
2930
{Sentry.Integrations.CheckInIDMappings,
3031
[
3132
max_expected_check_in_time:

lib/sentry/client.ex

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ defmodule Sentry.Client do
1515
Interfaces,
1616
LoggerUtils,
1717
Transport,
18-
Options
18+
Options,
19+
Transaction
1920
}
2021

2122
require Logger
@@ -91,6 +92,29 @@ defmodule Sentry.Client do
9192
end
9293
end
9394

95+
def send_transaction(%Transaction{} = transaction, opts \\ []) do
96+
# opts = validate_options!(opts)
97+
98+
result_type = Keyword.get_lazy(opts, :result, &Config.send_result/0)
99+
client = Keyword.get_lazy(opts, :client, &Config.client/0)
100+
101+
request_retries =
102+
Keyword.get_lazy(opts, :request_retries, fn ->
103+
Application.get_env(:sentry, :request_retries, Transport.default_retries())
104+
end)
105+
106+
case encode_and_send(transaction, result_type, client, request_retries) do
107+
{:ok, id} ->
108+
{:ok, id}
109+
110+
{:error, {status, headers, body}} ->
111+
{:error, ClientError.server_error(status, headers, body)}
112+
113+
{:error, reason} ->
114+
{:error, ClientError.new(reason)}
115+
end
116+
end
117+
94118
defp sample_event(sample_rate) do
95119
cond do
96120
sample_rate == 1 -> :ok
@@ -189,6 +213,42 @@ defmodule Sentry.Client do
189213
end
190214
end
191215

216+
defp encode_and_send(
217+
%Transaction{} = transaction,
218+
_result_type = :sync,
219+
client,
220+
request_retries
221+
) do
222+
case Sentry.Test.maybe_collect(transaction) do
223+
:collected ->
224+
{:ok, ""}
225+
226+
:not_collecting ->
227+
send_result =
228+
transaction
229+
|> Envelope.from_transaction()
230+
|> Transport.encode_and_post_envelope(client, request_retries)
231+
232+
send_result
233+
end
234+
end
235+
236+
defp encode_and_send(
237+
%Transaction{} = transaction,
238+
_result_type = :none,
239+
client,
240+
_request_retries
241+
) do
242+
case Sentry.Test.maybe_collect(transaction) do
243+
:collected ->
244+
{:ok, ""}
245+
246+
:not_collecting ->
247+
:ok = Transport.Sender.send_async(client, transaction)
248+
{:ok, ""}
249+
end
250+
end
251+
192252
@spec render_event(Event.t()) :: map()
193253
def render_event(%Event{} = event) do
194254
json_library = Config.json_library()
@@ -209,6 +269,11 @@ defmodule Sentry.Client do
209269
|> update_if_present(:threads, fn list -> Enum.map(list, &render_thread/1) end)
210270
end
211271

272+
@spec render_transaction(%Transaction{}) :: map()
273+
def render_transaction(%Transaction{} = transaction) do
274+
Transaction.to_map(transaction)
275+
end
276+
212277
defp render_exception(%Interfaces.Exception{} = exception) do
213278
exception
214279
|> Map.from_struct()

lib/sentry/envelope.ex

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule Sentry.Envelope do
22
@moduledoc false
33
# https://develop.sentry.dev/sdk/envelopes/
44

5-
alias Sentry.{Attachment, CheckIn, Config, Event, UUID}
5+
alias Sentry.{Attachment, CheckIn, Config, Event, UUID, Transaction}
66

77
@type t() :: %__MODULE__{
88
event_id: UUID.t(),
@@ -34,6 +34,17 @@ defmodule Sentry.Envelope do
3434
}
3535
end
3636

37+
@doc """
38+
Creates a new envelope containing a transaction with spans.
39+
"""
40+
@spec from_transaction(Sentry.Transaction.t()) :: t()
41+
def from_transaction(%Transaction{} = transaction) do
42+
%__MODULE__{
43+
event_id: transaction.event_id,
44+
items: [transaction]
45+
}
46+
end
47+
3748
@doc """
3849
Encodes the envelope into its binary representation.
3950
@@ -92,4 +103,15 @@ defmodule Sentry.Envelope do
92103
throw(error)
93104
end
94105
end
106+
107+
defp item_to_binary(json_library, %Transaction{} = transaction) do
108+
case transaction |> Sentry.Client.render_transaction() |> json_library.encode() do
109+
{:ok, encoded_transaction} ->
110+
header = ~s({"type": "transaction", "length": #{byte_size(encoded_transaction)}})
111+
[header, ?\n, encoded_transaction, ?\n]
112+
113+
{:error, _reason} = error ->
114+
throw(error)
115+
end
116+
end
95117
end

0 commit comments

Comments
 (0)