Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 1 addition & 3 deletions lib/sentry/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,7 @@ defmodule Sentry.Client do
end

defp encode_and_send(%Event{} = event, _result_type = :sync, request_retries) do
envelope = Envelope.new([event])

send_result = Transport.post_envelope(envelope, request_retries)
send_result = [event] |> Envelope.new() |> Transport.post_envelope(request_retries)

if match?({:ok, _}, send_result) do
Sentry.put_last_event_id_and_source(event.event_id, event.source)
Expand Down
73 changes: 28 additions & 45 deletions lib/sentry/envelope.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ defmodule Sentry.Envelope do
defstruct [:event_id, :items]

@doc """
Creates a new envelope containing the given events.
Creates a new envelope containing the given event.

Envelopes can only have a single element of type "event", so that's why we
restrict on a single-element list.
"""
@spec new([Event.t(), ...]) :: t()
def new([%Event{event_id: event_id} | _rest] = events) do
def new([%Event{event_id: event_id}] = events) do
%__MODULE__{
event_id: event_id,
items: events
Expand All @@ -25,36 +28,33 @@ defmodule Sentry.Envelope do

@doc """
Encodes the envelope into its binary representation.

For now, we support only envelopes with a single event in them.
"""
@spec to_binary(t()) :: {:ok, binary()} | {:error, any()}
def to_binary(%__MODULE__{} = envelope) do
def to_binary(%__MODULE__{items: [%Event{} = event]} = envelope) do
json_library = Config.json_library()

encoded_items =
Enum.map(envelope.items, fn item ->
case encode_item(item, json_library) do
{:ok, encoded_item} ->
type =
if is_struct(item, Event) do
"event"
else
raise "unexpected item in envelope: #{inspect(item)}"
end

[
~s({"type": "#{type}", "length": #{byte_size(encoded_item)}}\n),
encoded_item,
?\n
]

{:error, _reason} = error ->
throw(error)
end
end)

{:ok, IO.iodata_to_binary([encode_headers(envelope) | encoded_items])}
catch
{:error, reason} -> {:error, reason}
headers_iodata =
case envelope.event_id do
nil -> "{{}}\n"
event_id -> ~s({"event_id":"#{event_id}"}\n)
end

case event |> Sentry.Client.render_event() |> json_library.encode() do
{:ok, encoded_event} ->
body = [
headers_iodata,
~s({"type": "event", "length": #{byte_size(encoded_event)}}\n),
encoded_event,
?\n
]

{:ok, IO.iodata_to_binary(body)}

{:error, _reason} = error ->
error
end
end

@doc """
Expand All @@ -79,23 +79,6 @@ defmodule Sentry.Envelope do
end
end

#
# Encoding
#

defp encode_headers(%__MODULE__{} = envelope) do
case envelope.event_id do
nil -> "{{}}\n"
event_id -> ~s({"event_id":"#{event_id}"}\n)
end
end

defp encode_item(%Event{} = event, json_library) do
event
|> Sentry.Client.render_event()
|> json_library.encode()
end

#
# Decoding
#
Expand Down
40 changes: 5 additions & 35 deletions lib/sentry/transport/sender.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ defmodule Sentry.Transport.Sender do

@registry Sentry.Transport.SenderRegistry

@async_queue_max_size 10
@async_queue_timeout 500

# This behaviour is only present for mocks in tests.
defmodule Behaviour do
@moduledoc false
Expand Down Expand Up @@ -39,54 +36,27 @@ defmodule Sentry.Transport.Sender do

## State

defstruct async_queue: :queue.new()
defstruct []

## Callbacks

@impl GenServer
def init([]) do
Process.send_after(self(), :flush_async_queue, @async_queue_timeout)
{:ok, %__MODULE__{}}
end

@impl GenServer
def handle_cast({:send, %Event{} = event}, %__MODULE__{} = state) do
state = update_in(state.async_queue, &:queue.in(event, &1))

state =
if :queue.len(state.async_queue) >= @async_queue_max_size do
flush_async_queue(state)
else
state
end
[event]
|> Envelope.new()
|> Transport.post_envelope()
|> maybe_log_send_result([event])

{:noreply, state}
end

@impl GenServer
def handle_info(:flush_async_queue, %__MODULE__{} = state) do
state = flush_async_queue(state)
Process.send_after(self(), :flush_async_queue, @async_queue_timeout)
{:noreply, state}
end

## Helpers

defp flush_async_queue(%__MODULE__{async_queue: events_queue} = state) do
if :queue.is_empty(events_queue) do
state
else
events = :queue.to_list(events_queue)

events
|> Envelope.new()
|> Transport.post_envelope()
|> maybe_log_send_result(events)

%__MODULE__{state | async_queue: :queue.new()}
end
end

defp maybe_log_send_result(send_result, events) do
if Enum.any?(events, &(&1.source == :logger)) do
:ok
Expand Down
4 changes: 2 additions & 2 deletions test/envelope_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ defmodule Sentry.EnvelopeTest do

{:ok, raw_envelope} =
[event]
|> Sentry.Envelope.new()
|> Sentry.Envelope.to_binary()
|> Envelope.new()
|> Envelope.to_binary()

{:ok, envelope} = Envelope.from_binary(raw_envelope)

Expand Down
4 changes: 1 addition & 3 deletions test/sentry/transport_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ defmodule Sentry.TransportTest do
test "sends a POST request with the right headers and payload", %{bypass: bypass} do
envelope =
Envelope.new([
Event.create_event(message: "Hello 1"),
Event.create_event(message: "Hello 2"),
Event.create_event(message: "Hello 3")
Event.create_event(message: "Hello 1")
])

Bypass.expect_once(bypass, fn conn ->
Expand Down