diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f534309d..4224b0688 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - Include otel as custom sampling context ([2683](https://github.com/getsentry/sentry-ruby/pull/2683)) +### Fixes + +- Prevent logging from crashing main thread ([2795](https://github.com/getsentry/sentry-ruby/pull/2795)) + ## 6.1.2 ### Fixes diff --git a/sentry-ruby/lib/sentry/log_event_buffer.rb b/sentry-ruby/lib/sentry/log_event_buffer.rb index 8134da678..670fb4e2f 100644 --- a/sentry-ruby/lib/sentry/log_event_buffer.rb +++ b/sentry-ruby/lib/sentry/log_event_buffer.rb @@ -65,11 +65,18 @@ def size @pending_events.size end + def clear! + @pending_events.clear + end + private def send_events @client.send_logs(@pending_events) - @pending_events.clear + rescue => e + log_debug("[LogEventBuffer] Failed to send logs: #{e.message}") + ensure + clear! end end end diff --git a/sentry-ruby/spec/sentry/log_event_buffer_spec.rb b/sentry-ruby/spec/sentry/log_event_buffer_spec.rb index d1db43f5a..e97a6941a 100644 --- a/sentry-ruby/spec/sentry/log_event_buffer_spec.rb +++ b/sentry-ruby/spec/sentry/log_event_buffer_spec.rb @@ -74,4 +74,61 @@ expect(log_event_buffer).to be_empty end end + + describe "error handling" do + let(:max_log_events) { 3 } + + let(:error) { Errno::ECONNREFUSED.new("Connection refused") } + + context "when send_logs raises an exception" do + before do + allow(client).to receive(:send_logs).and_raise(error) + end + + it "does not propagate exception from add_event when buffer is full" do + expect { + 3.times { log_event_buffer.add_event(log_event) } + }.not_to raise_error + end + + it "does not propagate exception from flush" do + 2.times { log_event_buffer.add_event(log_event) } + + expect { + log_event_buffer.flush + }.not_to raise_error + end + + it "logs the error to sdk_logger" do + 3.times { log_event_buffer.add_event(log_event) } + + expect(string_io.string).to include("Failed to send logs") + end + + it "clears the buffer after a failed send to avoid memory buildup" do + 3.times { log_event_buffer.add_event(log_event) } + + expect(log_event_buffer).to be_empty + end + end + + context "when background thread encounters an error" do + let(:max_log_events) { 100 } + + before do + allow(client).to receive(:send_logs).and_raise(error) + end + + it "keeps the background thread alive after an error" do + log_event_buffer.add_event(log_event) + log_event_buffer.start + + thread = log_event_buffer.instance_variable_get(:@thread) + + expect(thread).to be_alive + expect { log_event_buffer.flush }.not_to raise_error + expect(thread).to be_alive + end + end + end end