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) 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