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
9 changes: 7 additions & 2 deletions sentry-ruby/lib/sentry/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,13 @@ def send_envelope(envelope)
rescue => e
log_error("Envelope sending failed", e, debug: configuration.debug)

envelope.items.map(&:data_category).each do |data_category|
transport.record_lost_event(:network_error, data_category)
envelope.items.each do |item|
transport.record_lost_event(
:network_error,
item.data_category,
num: item.item_count,
num_bytes: item.lost_event_byte_size
)
end

raise
Expand Down
21 changes: 21 additions & 0 deletions sentry-ruby/lib/sentry/envelope/item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ def self.data_category(type)
end
end

def self.byte_data_category(data_category)
case data_category
when "log_item" then "log_byte"
when "trace_metric" then "trace_metric_byte"
end
end

def initialize(headers, payload)
@headers = headers
@payload = payload
Expand All @@ -33,6 +40,20 @@ def initialize(headers, payload)
@size_limit = SIZE_LIMITS[type]
end

def byte_data_category
self.class.byte_data_category(data_category)
end

def item_count
headers[:item_count] || 1
end

def lost_event_byte_size
return unless byte_data_category

(payload.is_a?(String) ? payload : JSON.generate(payload)).bytesize
end

def to_s
[JSON.generate(@headers), @payload.is_a?(String) ? @payload : JSON.generate(@payload)].join("\n")
end
Expand Down
11 changes: 9 additions & 2 deletions sentry-ruby/lib/sentry/telemetry_event_buffer.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true

require "json"
require "sentry/threaded_periodic_worker"
require "sentry/envelope"

Expand Down Expand Up @@ -58,7 +59,11 @@ def add_item(item)

if size >= @max_items_before_drop
log_debug("[#{self.class}] exceeded max capacity, dropping event")
@client.transport.record_lost_event(:queue_overflow, @data_category)
@client.transport.record_lost_event(
:queue_overflow,
@data_category,
num_bytes: JSON.generate(item.to_h).bytesize
)
else
@pending_items << item
end
Expand Down Expand Up @@ -92,6 +97,7 @@ def send_items
)

discarded_count = 0
discarded_bytes = 0
envelope_items = []

if @before_send
Expand All @@ -102,14 +108,15 @@ def send_items
envelope_items << processed_item.to_h
else
discarded_count += 1
discarded_bytes += JSON.generate(item.to_h).bytesize
end
end
else
envelope_items = @pending_items.map(&:to_h)
end

unless discarded_count.zero?
@client.transport.record_lost_event(:before_send, @data_category, num: discarded_count)
@client.transport.record_lost_event(:before_send, @data_category, num: discarded_count, num_bytes: discarded_bytes)
end

return if envelope_items.empty?
Expand Down
17 changes: 14 additions & 3 deletions sentry-ruby/lib/sentry/transport.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def send_envelope(envelope)
end
rescue Sentry::SizeExceededError
serialized_items&.each do |item|
record_lost_event(:send_error, item.data_category)
record_lost_event(:send_error, item.data_category, num: item.item_count, num_bytes: item.lost_event_byte_size)
end
end

Expand Down Expand Up @@ -167,11 +167,16 @@ def envelope_from_event(event)
envelope
end

def record_lost_event(reason, data_category, num: 1)
def record_lost_event(reason, data_category, num: 1, num_bytes: nil)
return unless @send_client_reports
return unless CLIENT_REPORT_REASONS.include?(reason)

@discarded_events[[reason, data_category]] += num

return unless num_bytes

byte_category = Envelope::Item.byte_data_category(data_category)
@discarded_events[[reason, byte_category]] += num_bytes if byte_category
end

def flush
Expand Down Expand Up @@ -211,7 +216,13 @@ def reject_rate_limited_items(envelope)
envelope.items.reject! do |item|
if is_rate_limited?(item.data_category)
log_debug("[Transport] Envelope item [#{item.type}] not sent: rate limiting")
record_lost_event(:ratelimit_backoff, item.data_category)

record_lost_event(
:ratelimit_backoff,
item.data_category,
num: item.item_count,
num_bytes: item.lost_event_byte_size
)

true
else
Expand Down
46 changes: 46 additions & 0 deletions sentry-ruby/spec/sentry/envelope/item_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,50 @@
end
end
end

describe '.byte_data_category' do
[
['log_item', 'log_byte'],
['trace_metric', 'trace_metric_byte'],
['error', nil],
['transaction', nil],
['default', nil]
].each do |data_category, byte_category|
it "maps data category #{data_category} to byte category #{byte_category.inspect}" do
expect(described_class.byte_data_category(data_category)).to eq(byte_category)
end
end
end

describe '#item_count' do
it "returns the item_count header when present" do
item = described_class.new({ type: "log", item_count: 5 }, { items: [] })
expect(item.item_count).to eq(5)
end

it "defaults to 1 when the header is absent" do
item = described_class.new({ type: "event" }, {})
expect(item.item_count).to eq(1)
end
end

describe '#lost_event_byte_size' do
it "returns the serialized payload byte size for byte-tracked items" do
payload = { items: [{ body: "hello" }] }
item = described_class.new({ type: "log", item_count: 1 }, payload)
expect(item.lost_event_byte_size).to eq(JSON.generate(payload).bytesize)
expect(item.lost_event_byte_size).to be > 0
end

it "uses the payload as-is when it is already serialized" do
payload = JSON.generate({ items: [{ body: "hello" }] })
item = described_class.new({ type: "log", item_count: 1 }, payload)
expect(item.lost_event_byte_size).to eq(payload.bytesize)
end

it "returns nil for items without a byte category" do
item = described_class.new({ type: "event" }, { foo: "bar" })
expect(item.lost_event_byte_size).to be_nil
end
end
end
2 changes: 1 addition & 1 deletion sentry-ruby/spec/sentry/metrics_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@

expect(sentry_metrics.count).to eq(1)
expect(sentry_metrics.first[:name]).to eq("test.allowed")
expect(Sentry.get_current_client.transport).to have_recorded_lost_event(:before_send, 'trace_metric', num: 2)
expect(Sentry.get_current_client.transport).to have_recorded_lost_event(:before_send, 'trace_metric', num: 2, num_bytes: a_value > 0)
end
end
end
Expand Down
1 change: 1 addition & 0 deletions sentry-ruby/spec/sentry/structured_logger_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@
expect(sentry_logs.size).to be(1)

expect(transport.discarded_events).to include([:before_send, "log_item"] => 2)
expect(transport.discarded_events[[:before_send, "log_byte"]]).to be > 0
end
end
end
Expand Down
77 changes: 77 additions & 0 deletions sentry-ruby/spec/sentry/transport_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,83 @@
end
end

context "byte-count client outcomes" do
let(:log_events) do
3.times.map do
Sentry::LogEvent.new(level: :info, body: "User has logged in!")
end
end

let(:metric_events) do
3.times.map do
Sentry::MetricEvent.new(name: "my.metric", type: "counter", value: 1)
end
end

let(:log_envelope) do
envelope = Sentry::Envelope.new
envelope.add_item(
{ type: "log", item_count: log_events.size, content_type: "application/vnd.sentry.items.log+json" },
{ items: log_events.map(&:to_h) }
)
envelope
end

let(:metric_envelope) do
envelope = Sentry::Envelope.new
envelope.add_item(
{ type: "trace_metric", item_count: metric_events.size, content_type: "application/vnd.sentry.items.trace-metric+json" },
{ items: metric_events.map(&:to_h) }
)
envelope
end

describe "#record_lost_event" do
it "fans out into the paired byte category" do
subject.record_lost_event(:ratelimit_backoff, "log_item", num: 3, num_bytes: 1243)

expect(subject.discarded_events[[:ratelimit_backoff, "log_item"]]).to eq(3)
expect(subject.discarded_events[[:ratelimit_backoff, "log_byte"]]).to eq(1243)
end

it "does not record a byte category for non-byte-tracked categories" do
subject.record_lost_event(:ratelimit_backoff, "error", num: 1, num_bytes: 1243)

expect(subject.discarded_events.keys).to contain_exactly([:ratelimit_backoff, "error"])
end

it "does not record bytes when num_bytes is nil" do
subject.record_lost_event(:ratelimit_backoff, "log_item")

expect(subject.discarded_events.keys).to contain_exactly([:ratelimit_backoff, "log_item"])
end
end

context "when a batched log item is rate limited" do
before { subject.rate_limits.merge!("log_item" => Time.now + 60) }

it "records log_item count and log_byte size" do
log_item = log_envelope.items.first
subject.send_envelope(log_envelope)

expect(subject.discarded_events[[:ratelimit_backoff, "log_item"]]).to eq(3)
expect(subject.discarded_events[[:ratelimit_backoff, "log_byte"]]).to eq(JSON.generate(log_item.payload).bytesize)
end
end

context "when a batched trace metric item is rate limited" do
before { subject.rate_limits.merge!("trace_metric" => Time.now + 60) }

it "records trace_metric count and trace_metric_byte size" do
metric_item = metric_envelope.items.first
subject.send_envelope(metric_envelope)

expect(subject.discarded_events[[:ratelimit_backoff, "trace_metric"]]).to eq(3)
expect(subject.discarded_events[[:ratelimit_backoff, "trace_metric_byte"]]).to eq(JSON.generate(metric_item.payload).bytesize)
end
end
end

context "log events" do
let(:log_events) do
5.times.map do |i|
Expand Down
9 changes: 8 additions & 1 deletion sentry-ruby/spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,16 @@
reset_sentry_globals!
end

RSpec::Matchers.define :have_recorded_lost_event do |reason, data_category, num: 1|
RSpec::Matchers.define :have_recorded_lost_event do |reason, data_category, num: 1, num_bytes: nil|
match do |transport|
expect(transport.discarded_events[[reason, data_category]]).to eq(num)

next true unless num_bytes

byte_category = Sentry::Envelope::Item.byte_data_category(data_category)
expect(transport.discarded_events[[reason, byte_category]]).to match(num_bytes)

true
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
it "records lost event when dropping due to queue overflow" do
max_items_before_drop.times { subject.add_item(event) }

expect(client.transport).to receive(:record_lost_event).with(:queue_overflow, subject.data_category)
expect(client.transport).to receive(:record_lost_event).with(:queue_overflow, subject.data_category, num_bytes: a_value > 0)

subject.add_item(event)
end
Expand Down
Loading