-
-
Notifications
You must be signed in to change notification settings - Fork 185
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor and improve docs for client reports (#810)
Co-authored-by: Savannah Manning <[email protected]>
- Loading branch information
1 parent
823ad4e
commit d6798d5
Showing
10 changed files
with
181 additions
and
157 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
defmodule Sentry.ClientReport.Sender do | ||
@moduledoc false | ||
|
||
# This module is responsible for storing client reports and periodically "flushing" | ||
# them to Sentry. | ||
|
||
use GenServer | ||
|
||
alias Sentry.{Client, ClientReport, Config, Envelope} | ||
|
||
@send_interval 30_000 | ||
|
||
@client_report_reasons ClientReport.reasons() | ||
|
||
@spec start_link([]) :: GenServer.on_start() | ||
def start_link(opts \\ []) do | ||
GenServer.start_link(__MODULE__, nil, name: Keyword.get(opts, :name, __MODULE__)) | ||
end | ||
|
||
@spec record_discarded_events(atom(), [item], GenServer.server()) :: :ok | ||
when item: | ||
Sentry.Attachment.t() | ||
| Sentry.CheckIn.t() | ||
| ClientReport.t() | ||
| Sentry.Event.t() | ||
def record_discarded_events(reason, event_items, genserver \\ __MODULE__) | ||
when is_list(event_items) do | ||
# We silently ignore events whose reasons aren't valid because we have to add it to the allowlist in Snuba | ||
# https://develop.sentry.dev/sdk/client-reports/ | ||
if Enum.member?(@client_report_reasons, reason) do | ||
Enum.each( | ||
event_items, | ||
fn item -> | ||
GenServer.cast( | ||
genserver, | ||
{:record_discarded_events, reason, Envelope.get_data_category(item)} | ||
) | ||
end | ||
) | ||
end | ||
end | ||
|
||
## Callbacks | ||
|
||
@impl true | ||
def init(nil) do | ||
schedule_report() | ||
{:ok, _state = %{}} | ||
end | ||
|
||
@impl true | ||
def handle_cast({:record_discarded_events, reason, category}, discarded_events) do | ||
{:noreply, Map.update(discarded_events, {reason, category}, 1, &(&1 + 1))} | ||
end | ||
|
||
@impl true | ||
def handle_info(:send_report, state) do | ||
_ = | ||
if map_size(state) != 0 and Config.dsn() != nil and Config.send_client_reports?() do | ||
client_report = | ||
%ClientReport{ | ||
timestamp: | ||
DateTime.utc_now() | ||
|> DateTime.truncate(:second) | ||
|> DateTime.to_iso8601() | ||
|> String.trim_trailing("Z"), | ||
discarded_events: | ||
Enum.map(state, fn {{reason, category}, quantity} -> | ||
%{ | ||
reason: reason, | ||
category: category, | ||
quantity: quantity | ||
} | ||
end) | ||
} | ||
|
||
Client.send_client_report(client_report) | ||
end | ||
|
||
schedule_report() | ||
{:noreply, %{}} | ||
end | ||
|
||
defp schedule_report do | ||
Process.send_after(self(), :send_report, @send_interval) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
defmodule Sentry.ClientReportTest do | ||
use Sentry.Case, async: false | ||
|
||
import Sentry.TestHelpers | ||
|
||
alias Sentry.ClientReport.Sender | ||
alias Sentry.Event | ||
|
||
setup do | ||
original_retries = | ||
Application.get_env(:sentry, :request_retries, Sentry.Transport.default_retries()) | ||
|
||
on_exit(fn -> Application.put_env(:sentry, :request_retries, original_retries) end) | ||
|
||
Application.put_env(:sentry, :request_retries, []) | ||
|
||
bypass = Bypass.open() | ||
put_test_config(dsn: "http://public:secret@localhost:#{bypass.port}/1") | ||
%{bypass: bypass} | ||
end | ||
|
||
describe "record_discarded_events/2 + flushing" do | ||
test "succefully records the discarded event to the client report", %{bypass: bypass} do | ||
start_supervised!({Sender, name: :test_client_report}) | ||
|
||
events = [ | ||
%Event{ | ||
event_id: Sentry.UUID.uuid4_hex(), | ||
timestamp: "2024-10-12T13:21:13" | ||
} | ||
] | ||
|
||
assert :ok = Sender.record_discarded_events(:before_send, events, :test_client_report) | ||
|
||
assert :sys.get_state(:test_client_report) == %{{:before_send, "error"} => 1} | ||
|
||
assert :ok = Sender.record_discarded_events(:before_send, events, :test_client_report) | ||
|
||
assert :sys.get_state(:test_client_report) == %{{:before_send, "error"} => 2} | ||
|
||
assert :ok = Sender.record_discarded_events(:event_processor, events, :test_client_report) | ||
assert :ok = Sender.record_discarded_events(:network_error, events, :test_client_report) | ||
|
||
assert :sys.get_state(:test_client_report) == %{ | ||
{:before_send, "error"} => 2, | ||
{:event_processor, "error"} => 1, | ||
{:network_error, "error"} => 1 | ||
} | ||
|
||
send(Process.whereis(:test_client_report), :send_report) | ||
|
||
Bypass.expect(bypass, fn conn -> | ||
{:ok, body, conn} = Plug.Conn.read_body(conn) | ||
|
||
assert [{%{"type" => "client_report", "length" => _}, client_report}] = | ||
decode_envelope!(body) | ||
|
||
assert client_report["discarded_events"] == [ | ||
%{"reason" => "before_send", "category" => "error", "quantity" => 2}, | ||
%{"reason" => "event_processor", "category" => "error", "quantity" => 1}, | ||
%{"reason" => "network_error", "category" => "error", "quantity" => 1} | ||
] | ||
|
||
assert client_report["timestamp"] =~ ~r/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/ | ||
|
||
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>) | ||
end) | ||
|
||
assert :sys.get_state(:test_client_report) == %{} | ||
end | ||
end | ||
end |
Oops, something went wrong.