diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b80c45c5..88a393032 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ### Features +- Expose `:values` in `ExceptionInterface`, so that it can be accessed in `before_send` under `event.exception.values` [#1843](https://github.com/getsentry/sentry-ruby/pull/1843) + +- Add top level `Sentry.close` API [#1844](https://github.com/getsentry/sentry-ruby/pull/1844) + - Cleans up SDK state and sets it to uninitialized + - No-ops all SDK APIs and also disables the transport layer, so nothing will be sent to Sentry after closing the SDK + - Handle exception with large stacktrace without dropping entire item [#1807](https://github.com/getsentry/sentry-ruby/pull/1807) - Capture Rails runner's exceptions before exiting [#1820](https://github.com/getsentry/sentry-ruby/pull/1820) diff --git a/sentry-ruby/lib/sentry-ruby.rb b/sentry-ruby/lib/sentry-ruby.rb index 01cda0623..1bd3c092e 100644 --- a/sentry-ruby/lib/sentry-ruby.rb +++ b/sentry-ruby/lib/sentry-ruby.rb @@ -60,11 +60,11 @@ def exception_locals_tp end # @!attribute [rw] background_worker - # @return [BackgroundWorker] + # @return [BackgroundWorker, nil] attr_accessor :background_worker # @!attribute [r] session_flusher - # @return [SessionFlusher] + # @return [SessionFlusher, nil] attr_reader :session_flusher ##### Patch Registration ##### @@ -213,10 +213,30 @@ def init(&block) exception_locals_tp.enable end - at_exit do - @session_flusher&.kill + at_exit { close } + end + + # Flushes pending events and cleans up SDK state. + # SDK will stop sending events and all top-level APIs will be no-ops after this. + # + # @return [void] + def close + if @background_worker @background_worker.shutdown + @background_worker = nil + end + + if @session_flusher + @session_flusher.kill + @session_flusher = nil + end + + if configuration&.capture_exception_frame_locals + exception_locals_tp.disable end + + @main_hub = nil + Thread.current.thread_variable_set(THREAD_LOCAL, nil) end # Returns true if the SDK is initialized. @@ -287,6 +307,7 @@ def get_current_scope # # @return [void] def clone_hub_to_current_thread + return unless initialized? Thread.current.thread_variable_set(THREAD_LOCAL, get_main_hub.clone) end diff --git a/sentry-ruby/spec/sentry_spec.rb b/sentry-ruby/spec/sentry_spec.rb index 504051438..890ca3693 100644 --- a/sentry-ruby/spec/sentry_spec.rb +++ b/sentry-ruby/spec/sentry_spec.rb @@ -784,4 +784,71 @@ end end + describe ".close" do + context "when closing initialized SDK" do + it "not initialized?" do + expect(described_class.initialized?).to eq(true) + described_class.close + expect(described_class.initialized?).to eq(false) + end + + it "removes main hub" do + expect(described_class.get_main_hub).to be_a(Sentry::Hub) + described_class.close + expect(described_class.get_main_hub).to eq(nil) + end + + it "removes thread local" do + expect(Thread.current.thread_variable_get(described_class::THREAD_LOCAL)).to be_a(Sentry::Hub) + described_class.close + expect(Thread.current.thread_variable_get(described_class::THREAD_LOCAL)).to eq(nil) + + end + + it "calls background worker shutdown" do + expect(described_class.background_worker).to receive(:shutdown) + described_class.close + expect(described_class.background_worker).to eq(nil) + end + + it "kills session flusher" do + expect(described_class.session_flusher).to receive(:kill) + described_class.close + expect(described_class.session_flusher).to eq(nil) + end + + it "disables Tracepoint" do + perform_basic_setup do |config| + config.capture_exception_frame_locals = true + end + + expect(described_class.exception_locals_tp).to receive(:disable).and_call_original + described_class.close + end + end + + it "can reinitialize closed SDK" do + perform_basic_setup + + expect do + described_class.capture_event(event) + end.to change { transport.events.count }.by(1) + + described_class.close + + expect do + described_class.capture_event(event) + end.to change { transport.events.count }.by(0) + + perform_basic_setup + + expect(described_class.initialized?).to eq(true) + + new_transport = described_class.get_current_client.transport + + expect do + described_class.capture_event(event) + end.to change { new_transport.events.count }.by(1) + end + end end