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

Consolidate client report and rate limit handling with data categories #2294

Merged
merged 2 commits into from Apr 11, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/sentry_delayed_job_test.yml
Expand Up @@ -67,4 +67,4 @@ jobs:
if: ${{ matrix.options.codecov }}
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }
token: ${{ secrets.CODECOV_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/sentry_opentelemetry_test.yml
Expand Up @@ -59,4 +59,4 @@ jobs:
if: ${{ matrix.options.codecov }}
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }
token: ${{ secrets.CODECOV_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/sentry_rails_test.yml
Expand Up @@ -83,4 +83,4 @@ jobs:
if: ${{ matrix.options.codecov }}
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }
token: ${{ secrets.CODECOV_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/sentry_resque_test.yml
Expand Up @@ -69,4 +69,4 @@ jobs:
if: ${{ matrix.options.codecov }}
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }
token: ${{ secrets.CODECOV_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/sentry_ruby_test.yml
Expand Up @@ -75,4 +75,4 @@ jobs:
if: ${{ matrix.options.codecov }}
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }
token: ${{ secrets.CODECOV_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/sentry_sidekiq_test.yml
Expand Up @@ -71,4 +71,4 @@ jobs:
if: ${{ matrix.options.codecov }}
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }
token: ${{ secrets.CODECOV_TOKEN }}
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -3,6 +3,7 @@
### Features

- Update key, unit and tags sanitization logic for metrics [#2292](https://github.com/getsentry/sentry-ruby/pull/2292)
- Consolidate client report and rate limit handling with data categories [#2294](https://github.com/getsentry/sentry-ruby/pull/2294)

### Bug Fixes

Expand Down
14 changes: 8 additions & 6 deletions sentry-ruby/lib/sentry/client.rb
Expand Up @@ -49,24 +49,25 @@
return unless configuration.sending_allowed?

if event.is_a?(ErrorEvent) && !configuration.sample_allowed?
transport.record_lost_event(:sample_rate, 'event')
transport.record_lost_event(:sample_rate, 'error')

Check warning on line 52 in sentry-ruby/lib/sentry/client.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/client.rb#L52

Added line #L52 was not covered by tests
return
end

event_type = event.is_a?(Event) ? event.type : event["type"]
data_category = Envelope::Item.data_category(event_type)
event = scope.apply_to_event(event, hint)

if event.nil?
log_debug("Discarded event because one of the event processors returned nil")
transport.record_lost_event(:event_processor, event_type)
transport.record_lost_event(:event_processor, data_category)

Check warning on line 62 in sentry-ruby/lib/sentry/client.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/client.rb#L62

Added line #L62 was not covered by tests
return
end

if async_block = configuration.async
dispatch_async_event(async_block, event, hint)
elsif configuration.background_worker_threads != 0 && hint.fetch(:background, true)
queued = dispatch_background_event(event, hint)
transport.record_lost_event(:queue_overflow, event_type) unless queued
transport.record_lost_event(:queue_overflow, data_category) unless queued

Check warning on line 70 in sentry-ruby/lib/sentry/client.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/client.rb#L70

Added line #L70 was not covered by tests
else
send_event(event, hint)
end
Expand Down Expand Up @@ -166,13 +167,14 @@
# @!macro send_event
def send_event(event, hint = nil)
event_type = event.is_a?(Event) ? event.type : event["type"]
data_category = Envelope::Item.data_category(event_type)

if event_type != TransactionEvent::TYPE && configuration.before_send
event = configuration.before_send.call(event, hint)

if event.nil?
log_debug("Discarded event because before_send returned nil")
transport.record_lost_event(:before_send, 'event')
transport.record_lost_event(:before_send, data_category)

Check warning on line 177 in sentry-ruby/lib/sentry/client.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/client.rb#L177

Added line #L177 was not covered by tests
return
end
end
Expand All @@ -182,7 +184,7 @@

if event.nil?
log_debug("Discarded event because before_send_transaction returned nil")
transport.record_lost_event(:before_send, 'transaction')
transport.record_lost_event(:before_send, data_category)

Check warning on line 187 in sentry-ruby/lib/sentry/client.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/client.rb#L187

Added line #L187 was not covered by tests
return
end
end
Expand All @@ -193,7 +195,7 @@
event
rescue => e
log_error("Event sending failed", e, debug: configuration.debug)
transport.record_lost_event(:network_error, event_type)
transport.record_lost_event(:network_error, data_category)

Check warning on line 198 in sentry-ruby/lib/sentry/client.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/client.rb#L198

Added line #L198 was not covered by tests
raise
end

Expand Down
17 changes: 17 additions & 0 deletions sentry-ruby/lib/sentry/envelope.rb
Expand Up @@ -18,6 +18,23 @@
@headers[:type] || 'event'
end

# rate limits and client reports use the data_category rather than envelope item type
def self.data_category(type)
case type
when 'session', 'attachment', 'transaction', 'profile' then type
when 'sessions' then 'session'
when 'check_in' then 'monitor'
when 'statsd', 'metric_meta' then 'metric_bucket'
when 'event' then 'error'
when 'client_report' then 'internal'
else 'default'

Check warning on line 30 in sentry-ruby/lib/sentry/envelope.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/envelope.rb#L25-L30

Added lines #L25 - L30 were not covered by tests
end
end

def data_category
self.class.data_category(type)

Check warning on line 35 in sentry-ruby/lib/sentry/envelope.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/envelope.rb#L35

Added line #L35 was not covered by tests
end

def to_s
[JSON.generate(@headers), @payload.is_a?(String) ? @payload : JSON.generate(@payload)].join("\n")
end
Expand Down
27 changes: 7 additions & 20 deletions sentry-ruby/lib/sentry/transport.rb
Expand Up @@ -88,18 +88,9 @@
[data, serialized_items]
end

def is_rate_limited?(item_type)
def is_rate_limited?(data_category)
# check category-specific limit
category_delay =
case item_type
when "transaction"
@rate_limits["transaction"]
when "sessions"
@rate_limits["session"]
else
@rate_limits["error"]
end

category_delay = @rate_limits[data_category]

Check warning on line 93 in sentry-ruby/lib/sentry/transport.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/transport.rb#L93

Added line #L93 was not covered by tests
# check universal limit if not category limit
universal_delay = @rate_limits[nil]

Expand Down Expand Up @@ -160,11 +151,11 @@
envelope
end

def record_lost_event(reason, item_type)
def record_lost_event(reason, data_category)
return unless @send_client_reports
return unless CLIENT_REPORT_REASONS.include?(reason)

@discarded_events[[reason, item_type]] += 1
@discarded_events[[reason, data_category]] += 1
end

def flush
Expand All @@ -184,11 +175,7 @@
return nil if @discarded_events.empty?

discarded_events_hash = @discarded_events.map do |key, val|
reason, type = key

# 'event' has to be mapped to 'error'
category = type == 'event' ? 'error' : type

reason, category = key

Check warning on line 178 in sentry-ruby/lib/sentry/transport.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/transport.rb#L178

Added line #L178 was not covered by tests
{ reason: reason, category: category, quantity: val }
end

Expand All @@ -206,9 +193,9 @@

def reject_rate_limited_items(envelope)
envelope.items.reject! do |item|
if is_rate_limited?(item.type)
if is_rate_limited?(item.data_category)

Check warning on line 196 in sentry-ruby/lib/sentry/transport.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/transport.rb#L196

Added line #L196 was not covered by tests
log_debug("[Transport] Envelope item [#{item.type}] not sent: rate limiting")
record_lost_event(:ratelimit_backoff, item.type)
record_lost_event(:ratelimit_backoff, item.data_category)

Check warning on line 198 in sentry-ruby/lib/sentry/transport.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/transport.rb#L198

Added line #L198 was not covered by tests

true
else
Expand Down
12 changes: 6 additions & 6 deletions sentry-ruby/spec/sentry/client/event_sending_spec.rb
Expand Up @@ -35,7 +35,7 @@
it "doesn't send the event when it's not sampled" do
allow(Random).to receive(:rand).and_return(0.51)
subject.capture_event(event, scope)
expect(subject.transport).to have_recorded_lost_event(:sample_rate, 'event')
expect(subject.transport).to have_recorded_lost_event(:sample_rate, 'error')
expect(subject.transport.events.count).to eq(0)
end
end
Expand Down Expand Up @@ -171,7 +171,7 @@
allow(Sentry.background_worker).to receive(:perform).and_return(false)

subject.capture_event(event, scope)
expect(subject.transport).to have_recorded_lost_event(:queue_overflow, 'event')
expect(subject.transport).to have_recorded_lost_event(:queue_overflow, 'error')

expect(subject.transport.events.count).to eq(0)
sleep(0.2)
Expand Down Expand Up @@ -319,7 +319,7 @@
it "discards the event and logs a info" do
expect(subject.capture_event(event, scope)).to be_nil

expect(subject.transport).to have_recorded_lost_event(:event_processor, 'event')
expect(subject.transport).to have_recorded_lost_event(:event_processor, 'error')
expect(string_io.string).to match(/Discarded event because one of the event processors returned nil/)
end
end
Expand Down Expand Up @@ -360,7 +360,7 @@
it "swallows and logs Sentry::ExternalError (caused by transport's networking error)" do
expect(subject.capture_event(event, scope)).to be_nil

expect(subject.transport).to have_recorded_lost_event(:network_error, 'event')
expect(subject.transport).to have_recorded_lost_event(:network_error, 'error')
expect(string_io.string).to match(/Event sending failed: Failed to open TCP connection/)
expect(string_io.string).to match(/Event capturing failed: Failed to open TCP connection/)
end
Expand All @@ -383,7 +383,7 @@
expect(subject.capture_event(event, scope)).to be_a(Sentry::ErrorEvent)
sleep(0.2)

expect(subject.transport).to have_recorded_lost_event(:network_error, 'event')
expect(subject.transport).to have_recorded_lost_event(:network_error, 'error')
expect(string_io.string).to match(/Event sending failed: Failed to open TCP connection/)
end

Expand Down Expand Up @@ -471,7 +471,7 @@

it "records lost event" do
subject.send_event(event)
expect(subject.transport).to have_recorded_lost_event(:before_send, 'event')
expect(subject.transport).to have_recorded_lost_event(:before_send, 'error')
end
end

Expand Down
23 changes: 23 additions & 0 deletions sentry-ruby/spec/sentry/envelope_spec.rb
@@ -0,0 +1,23 @@
require "spec_helper"

RSpec.describe Sentry::Envelope::Item do
describe '.data_category' do
[
['session', 'session'],
['sessions', 'session'],
['attachment', 'attachment'],
['transaction', 'transaction'],
['profile', 'profile'],
['check_in', 'monitor'],
['statsd', 'metric_bucket'],
['metric_meta', 'metric_bucket'],
['event', 'error'],
['client_report', 'internal'],
['unknown', 'default']
].each do |item_type, data_category|
it "maps item type #{item_type} to data category #{data_category}" do
expect(described_class.data_category(item_type)).to eq(data_category)
end
end
end
end
Expand Up @@ -26,40 +26,40 @@
"transaction" => Time.now + 60,
"session" => Time.now + 60)

expect(subject.is_rate_limited?("event")).to eq(true)
expect(subject.is_rate_limited?("error")).to eq(true)
expect(subject.is_rate_limited?("transaction")).to eq(true)
expect(subject.is_rate_limited?("sessions")).to eq(true)
expect(subject.is_rate_limited?("session")).to eq(true)
end

it "returns false for passed limited category" do
subject.rate_limits.merge!("error" => Time.now - 10,
"transaction" => Time.now - 10,
"session" => Time.now - 10)

expect(subject.is_rate_limited?("event")).to eq(false)
expect(subject.is_rate_limited?("error")).to eq(false)
expect(subject.is_rate_limited?("transaction")).to eq(false)
expect(subject.is_rate_limited?("sessions")).to eq(false)
expect(subject.is_rate_limited?("session")).to eq(false)
end

it "returns false for not listed category" do
subject.rate_limits.merge!("transaction" => Time.now + 10)

expect(subject.is_rate_limited?("event")).to eq(false)
expect(subject.is_rate_limited?("sessions")).to eq(false)
expect(subject.is_rate_limited?("error")).to eq(false)
expect(subject.is_rate_limited?("session")).to eq(false)
end
end

context "with only universal limits" do
it "returns true when still limited" do
subject.rate_limits.merge!(nil => Time.now + 60)

expect(subject.is_rate_limited?("event")).to eq(true)
expect(subject.is_rate_limited?("error")).to eq(true)
end

it "returns false when passed limit" do
subject.rate_limits.merge!(nil => Time.now - 10)

expect(subject.is_rate_limited?("event")).to eq(false)
expect(subject.is_rate_limited?("error")).to eq(false)
end
end

Expand All @@ -70,14 +70,14 @@
nil => Time.now - 10
)

expect(subject.is_rate_limited?("event")).to eq(true)
expect(subject.is_rate_limited?("error")).to eq(true)

subject.rate_limits.merge!(
"error" => Time.now - 60,
nil => Time.now + 10
)

expect(subject.is_rate_limited?("event")).to eq(true)
expect(subject.is_rate_limited?("error")).to eq(true)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion sentry-ruby/spec/sentry/transport_spec.rb
Expand Up @@ -494,7 +494,7 @@

it "records lost event" do
subject.send_event(event)
expect(subject).to have_recorded_lost_event(:ratelimit_backoff, 'event')
expect(subject).to have_recorded_lost_event(:ratelimit_backoff, 'error')
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions sentry-ruby/spec/spec_helper.rb
Expand Up @@ -50,9 +50,9 @@
skip("skip rack related tests") unless defined?(Rack)
end

RSpec::Matchers.define :have_recorded_lost_event do |reason, type|
RSpec::Matchers.define :have_recorded_lost_event do |reason, data_category|
match do |transport|
expect(transport.discarded_events[[reason, type]]).to be > 0
expect(transport.discarded_events[[reason, data_category]]).to be > 0
end
end
end
Expand Down