-
-
Notifications
You must be signed in to change notification settings - Fork 189
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
Add support for client reports #801
Conversation
@savhappy can you lmk when this will be ready for review? |
@whatyouhide yep yep I'll tag you here when ready |
f7abce9
to
eaf55a1
Compare
@whatyouhide ready for an initial review. I've only added a single instance of recording an event but will add in more after discussion |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a first pass of comments, some are nits, some are things to address, and a couple are blocking. Let me know if you wanna go through them together 🙃
lib/sentry/client_report.ex
Outdated
def add_discarded_event({reason, category}) do | ||
if Enum.member?(@client_report_reasons, reason) do | ||
GenServer.cast(__MODULE__, {:add_discarded_event, {reason, category}}) | ||
end | ||
|
||
:ok | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you choose to silently ignore events whose reason
is not a valid one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to do this for two reasons -> From what I can tell, the Ruby SDK ignores invalid reasons
def record_lost_event(reason, data_category, num: 1)
return unless @send_client_reports
return unless CLIENT_REPORT_REASONS.include?(reason)
@discarded_events[[reason, data_category]] += num
end
and the documentation says
In case a reason needs to be added, it also has to be added to the allowlist in [snuba](https://github.com/getsentry/snuba/blob/4e7cfdddcf7b93eacb762bc74ca2461cec9464e5/snuba/datasets/outcomes_processor.py#L24-L34).
So we choose to do nothing if it's invalid unless we want to add them to this repo mentioned. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, sounds good. Let's silently ignore it for now, BUT leave a comment with basically what you just said to me, but in the code 🙃
Thanks @whatyouhide for this! Updated with these comments except the blocking tests. Will add those shortly. Hold off on more feedback till I get the next round out. I need to add |
Question @whatyouhide : I need to add in the appropriate data_category to the payload. We add the type only in the headers but other SDK's have a type field. The mappings for types would look like this defmodule Sentry.DataCategory do
@spec data_category_mappings(String.t()) :: String.t()
def data_category_mappings(type) do
case type do
"session" -> "session"
"sessions" -> "session"
"attachment" -> "attachment"
"transaction" -> "transaction"
"profile" -> "profile"
"span" -> "span"
"check_in" -> "monitor"
"statsd" -> "metric_bucket"
"metric_meta" -> "metric_bucket"
"event" -> "error"
"client_report" -> "internal"
_ -> "default"
end
end
end But we only have the types |
@savhappy can you elaborate on the issue here? Where do we need to add |
Yep! @whatyouhide The data categories are used to classify the type of data being ingested (ingest side types for collecting statistics). It's defined in the Client Report docs as -> We need to add the data_category to the payload ->
So When I send {reason, category} over to the genserver. I need to ensure that I'm sending over the correct data category to Sentry. In Transport module -> def record_discarded_event(reason, category) do
# add logic for data category here
# call to client report genserver
ClientReport.add_discarded_event({reason, category})
# end
:ok
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left one more round of comments, this is shaping up! 🎉
lib/sentry/client_report.ex
Outdated
@spec start_link([]) :: GenServer.on_start() | ||
def start_link([]) do | ||
# check config to see if send_client_report is true | ||
GenServer.start_link(__MODULE__, %__MODULE__{}, name: __MODULE__) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using %__MODULE__{}
as the state is confusing IMO because you're conflating two responsibilities in the same place: now %__MODULE__{}
is both the data definition of the client report, as well as the GenServer's internal state.
Instead, I'll go against my own advice and say: just use the map of discarded events as the GenServer's state, and only add the timestamp when sending the client report. Makes sense?
lib/sentry/client_report.ex
Outdated
@send_interval 5000 | ||
|
||
@spec start_link([]) :: GenServer.on_start() | ||
def start_link([]) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you choose to go with Sentry.ClientReport
containing both the struct as well as the GenServer, you'll need to make all these functions private (@doc false
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated but double check that this is what you meant ->
@doc false
@spec start_link([]) :: GenServer.on_start()
def start_link([]) do
# check config to see if send_client_report is true
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
end
lib/sentry/client_report.ex
Outdated
|
||
@spec start_link([]) :: GenServer.on_start() | ||
def start_link([]) do | ||
# check config to see if send_client_report is true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a TODO?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. If the user chooses to flip to false then we don't report but I'm going to add this here ->
if Config.dsn() != nil && Config.send_client_reports?() do
Client.send_client_report(client_report)
end
in the :send_report handle_info function
lib/sentry/client_report.ex
Outdated
@impl true | ||
def handle_cast({:add_discarded_event, {reason, category}}, client_report) do | ||
if map_size(client_report.discarded_events) == 0 do | ||
{:noreply, %{client_report | discarded_events: %{{reason, category} => 1}}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is not needed if you use Map.update/4
the way you do below. If the map is empty, you'll init {reason, category}
to the third arg of Map.update/4
(1
, correctly). Plz remove the if
🙃
lib/sentry/client_report.ex
Outdated
|
||
@impl true | ||
def handle_info(:send_report, state) do | ||
if map_size(state.discarded_events) == 0 do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opposite: if map is empty, then don't report?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops! Yes, thx!
lib/sentry/client_report.ex
Outdated
end | ||
|
||
@spec add_discarded_event({reason(), String.t()}) :: :ok | ||
def add_discarded_event({reason, category}) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use a tuple here, it's not needed. Just pass two args, (reason, category)
and call the GenServer cast {:add_discarded_event, reason, category}
.
lib/sentry/transport.ex
Outdated
@@ -40,6 +40,11 @@ defmodule Sentry.Transport do | |||
result | |||
end | |||
|
|||
def record_discarded_event(reason, category) do | |||
ClientReport.add_discarded_event({reason, DataCategory.data_category_mapping(category)}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why go through the extra data_category_mapping/1
step here rather than just do the mapping in ClientReport
, without introducing a new Sentry.DataCategory
module?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we resolved this in our pairing. Updates made so lemme know thoughts
e98dc43
to
2503547
Compare
lib/sentry.ex
Outdated
@@ -411,6 +412,7 @@ defmodule Sentry do | |||
|> CheckIn.new() | |||
|> Client.send_check_in(options) | |||
else | |||
# add record dropped event here? Dropped due to.... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ask on Discord because I don't think we need to report these here but maybe other SDKs do. Remove this comment in the meantime so that we can start shipping this PR.
lib/sentry/client.ex
Outdated
:unsampled | ||
|
||
:excluded -> | ||
# do we need to add this if an event is dropped due to it being a duplicate?? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ask in Discord
lib/sentry/envelope.ex
Outdated
@@ -34,6 +34,41 @@ defmodule Sentry.Envelope do | |||
} | |||
end | |||
|
|||
@doc """ | |||
Creates a new envelope containing the client report. | |||
since: "10.8.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@doc since
is an ExDoc feature. I literally meant:
@doc since: "10.8.0"
after the @doc
string. There are tons of examples in this repo 🙃
lib/sentry/envelope.ex
Outdated
if is_binary(type) do | ||
type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When will this ever be a string? We talked about functions having a small "type" surface but this function is still accepting binary | Attachment.t | ...
. Can we make it so this only accepts a struct OR a binary?
Also, can you add typespecs and tests for this?
lib/sentry/envelope.ex
Outdated
_ -> | ||
"default" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is dangerous because I can call get_data_category(:completely_banana_value)
. Make sure the input is either a string or a struct (per comm
ent above)
lib/sentry/transport.ex
Outdated
_ = | ||
if is_list(event_item) do | ||
Enum.map(event_item, fn event -> | ||
ClientReport.add_discarded_event({reason, Envelope.get_data_category(event)}) | ||
end) | ||
else | ||
ClientReport.add_discarded_event({reason, Envelope.get_data_category(event_item)}) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
List.wrap/1
Enum.map/2
wastes memory here, useEnum.each/2
since we ignore the result.
_ = | |
if is_list(event_item) do | |
Enum.map(event_item, fn event -> | |
ClientReport.add_discarded_event({reason, Envelope.get_data_category(event)}) | |
end) | |
else | |
ClientReport.add_discarded_event({reason, Envelope.get_data_category(event_item)}) | |
end | |
_ = | |
event_item | |
|> List.wrap() | |
|> Enum.each(&ClientReport.add_discarded_event({reason, Envelope.get_data_category(&1)})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are no Sentry.Transport
tests for the new functionality
lib/sentry/client_report.ex
Outdated
@spec add_discarded_event(reason(), String.t()) :: :ok | ||
def add_discarded_event(reason, category) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is internal for now so @doc false
lib/sentry/envelope.ex
Outdated
|
||
@spec get_data_category(Attachment.t() | CheckIn.t() | ClientReport.t() | Event.t()) :: | ||
String.t() | ||
def get_data_category(type) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you wanna raise, do it like this so that invalid input doesn't even get in the function:
def get_data_category(type) do | |
def get_data_category(%mod{} = type) when mod in [Attachment, CheckIn, ClientReport, Event] do |
88c78a6
to
fa43765
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're ready for docs for this and to
test/envelope_test.exs
Outdated
|
||
client_report = %ClientReport{ | ||
timestamp: "2024-10-12T13:21:13", | ||
discarded_events: [%{reason: :event_processor, category: "error"}] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this need a quantity as well?
@whatyouhide should we create a ticket that reminds us to add in spans to the client report when supported? |
No description provided.