From 7b0bd3b173d657fc796a0da38e4710561eee902a Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 3 May 2022 15:26:38 +0800 Subject: [PATCH 1/2] Fix: Handle exception with large stacktrace without dropping entire item Certain exception type such as `SystemStackError` has long backtrace (thus the stack error) The whole envelope item was dropped due to payload size limit logic This ensures it tries to remove the stacktrace when payload is too large, so that the envelope item won't be dropped = exception still reported --- sentry-ruby/lib/sentry/transport.rb | 20 +++++ sentry-ruby/spec/sentry/transport_spec.rb | 102 +++++++++++++++++----- 2 files changed, 99 insertions(+), 23 deletions(-) diff --git a/sentry-ruby/lib/sentry/transport.rb b/sentry-ruby/lib/sentry/transport.rb index 9524b8d3d..ec59c7489 100644 --- a/sentry-ruby/lib/sentry/transport.rb +++ b/sentry-ruby/lib/sentry/transport.rb @@ -82,6 +82,26 @@ def serialize_envelope(envelope) result = item.to_s end + if result.bytesize > Event::MAX_SERIALIZED_PAYLOAD_SIZE + if single_exceptions = item.payload.dig(:exception, :values) + single_exceptions.each do |single_exception| + traces = single_exception.dig(:stacktrace, :frames) + if traces && traces.size > 0 + single_exception.delete(:stacktrace) + end + end + elsif single_exceptions = item.payload.dig("exception", "values") + single_exceptions.each do |single_exception| + traces = single_exception.dig("stacktrace", "frames") + if traces && traces.size > 0 + single_exception.delete("stacktrace") + end + end + end + + result = item.to_s + end + if result.bytesize > Event::MAX_SERIALIZED_PAYLOAD_SIZE size_breakdown = item.payload.map do |key, value| "#{key}: #{JSON.generate(value).bytesize}" diff --git a/sentry-ruby/spec/sentry/transport_spec.rb b/sentry-ruby/spec/sentry/transport_spec.rb index 7b038fd7f..324286a38 100644 --- a/sentry-ruby/spec/sentry/transport_spec.rb +++ b/sentry-ruby/spec/sentry/transport_spec.rb @@ -100,40 +100,96 @@ end context "oversized event" do - let(:event) { client.event_from_message("foo") } - let(:envelope) { subject.envelope_from_event(event) } + context "due to breadcrumb" do + let(:event) { client.event_from_message("foo") } + let(:envelope) { subject.envelope_from_event(event) } - before do - event.breadcrumbs = Sentry::BreadcrumbBuffer.new(100) - 100.times do |i| - event.breadcrumbs.record Sentry::Breadcrumb.new(category: i.to_s, message: "x" * Sentry::Event::MAX_MESSAGE_SIZE_IN_BYTES) + before do + event.breadcrumbs = Sentry::BreadcrumbBuffer.new(100) + 100.times do |i| + event.breadcrumbs.record Sentry::Breadcrumb.new(category: i.to_s, message: "x" * Sentry::Event::MAX_MESSAGE_SIZE_IN_BYTES) + end + serialized_result = JSON.generate(event.to_hash) + expect(serialized_result.bytesize).to be > Sentry::Event::MAX_SERIALIZED_PAYLOAD_SIZE end - serialized_result = JSON.generate(event.to_hash) - expect(serialized_result.bytesize).to be > Sentry::Event::MAX_SERIALIZED_PAYLOAD_SIZE - end - it "removes breadcrumbs and carry on" do - data, _ = subject.serialize_envelope(envelope) - expect(data.bytesize).to be < Sentry::Event::MAX_SERIALIZED_PAYLOAD_SIZE + it "removes breadcrumbs and carry on" do + data, _ = subject.serialize_envelope(envelope) + expect(data.bytesize).to be < Sentry::Event::MAX_SERIALIZED_PAYLOAD_SIZE + + expect(envelope.items.count).to eq(1) + + event_item = envelope.items.first + expect(event_item.payload[:breadcrumbs]).to be_nil + end - expect(envelope.items.count).to eq(1) + context "if it's still oversized" do + before do + 100.times do |i| + event.contexts["context_#{i}"] = "s" * Sentry::Event::MAX_MESSAGE_SIZE_IN_BYTES + end + end - event_item = envelope.items.first - expect(event_item.payload[:breadcrumbs]).to be_nil + it "rejects the item and logs attributes size breakdown" do + data, _ = subject.serialize_envelope(envelope) + expect(data).to be_nil + expect(io.string).not_to match(/Sending envelope with items \[event\]/) + expect(io.string).to match(/tags: 2, contexts: 820791, extra: 2/) + end + end end - context "if it's still oversized" do + context "due to stacktrace frames" do + let(:event) { client.event_from_exception(SystemStackError.new("stack level too deep")) } + let(:envelope) { subject.envelope_from_event(event) } + + let(:unparsed_app_line) do + "app.rb:12:in `/'" + end + let(:in_app_pattern) do + project_root = "/fake/project_root" + Regexp.new("^(#{project_root}/)?#{Sentry::Backtrace::APP_DIRS_PATTERN}") + end + before do - 100.times do |i| - event.contexts["context_#{i}"] = "s" * Sentry::Event::MAX_MESSAGE_SIZE_IN_BYTES - end + single_exception = event.exception.instance_variable_get(:@values)[0] + new_stacktrace = Sentry::StacktraceInterface.new( + frames: [ + Sentry::StacktraceInterface::Frame.new( + "/fake/path/#{ "x" * Sentry::Event::MAX_SERIALIZED_PAYLOAD_SIZE }", + Sentry::Backtrace::Line.parse(unparsed_app_line, in_app_pattern), + ), + ], + ) + single_exception.instance_variable_set(:@stacktrace, new_stacktrace) + + serialized_result = JSON.generate(event.to_hash) + expect(serialized_result.bytesize).to be > Sentry::Event::MAX_SERIALIZED_PAYLOAD_SIZE end - it "rejects the item and logs attributes size breakdown" do + it "removes stacktrace frames and carry on" do data, _ = subject.serialize_envelope(envelope) - expect(data).to be_nil - expect(io.string).not_to match(/Sending envelope with items \[event\]/) - expect(io.string).to match(/tags: 2, contexts: 820791, extra: 2/) + expect(data.bytesize).to be < Sentry::Event::MAX_SERIALIZED_PAYLOAD_SIZE + + expect(envelope.items.count).to eq(1) + + event_item = envelope.items.first + expect(event_item.payload[:exception][:values][0][:stacktrace]).to be_nil + end + + context "if it's still oversized" do + before do + 100.times do |i| + event.contexts["context_#{i}"] = "s" * Sentry::Event::MAX_MESSAGE_SIZE_IN_BYTES + end + end + + it "rejects the item and logs attributes size breakdown" do + data, _ = subject.serialize_envelope(envelope) + expect(data).to be_nil + expect(io.string).not_to match(/Sending envelope with items \[event\]/) + expect(io.string).to match(/tags: 2, contexts: 820791, extra: 2/) + end end end end From 3a37b01f958f8c1e8f8762369f4cd6f8aeaf443e Mon Sep 17 00:00:00 2001 From: PikachuEXE Date: Tue, 3 May 2022 15:31:53 +0800 Subject: [PATCH 2/2] Update CHANGELOG --- sentry-ruby/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sentry-ruby/CHANGELOG.md b/sentry-ruby/CHANGELOG.md index 74a145f65..38bb8e188 100644 --- a/sentry-ruby/CHANGELOG.md +++ b/sentry-ruby/CHANGELOG.md @@ -2,6 +2,12 @@ Individual gem's changelog has been deprecated. Please check the [project changelog](https://github.com/getsentry/sentry-ruby/blob/master/CHANGELOG.md). +## Unreleased + +### Bug Fixes + +- Fix: Handle exception with large stacktrace without dropping entire item [#1806](https://github.com/getsentry/sentry-ruby/pull/1806) + ## 4.4.2 - Fix NoMethodError when SDK's dsn is nil [#1433](https://github.com/getsentry/sentry-ruby/pull/1433)