Skip to content

Commit

Permalink
Merge branch 'pause-resume-sessions' into next
Browse files Browse the repository at this point in the history
  • Loading branch information
imjoehaines committed Oct 5, 2021
2 parents 6bc099f + af1b904 commit 9595bbe
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 8 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Changelog
| [#701](https://github.com/bugsnag/bugsnag-ruby/pull/701)
* Add `Configuration#redacted_keys`. This is like `meta_data_filters` but matches strings with case-insensitive equality, rather than matching based on inclusion
| [#703](https://github.com/bugsnag/bugsnag-ruby/pull/703)
* Allow pausing and resuming sessions, giving more control over the stability score
| [#704](https://github.com/bugsnag/bugsnag-ruby/pull/704)

### Deprecated

Expand Down
27 changes: 25 additions & 2 deletions lib/bugsnag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,36 @@ def session_tracker
end

##
# Starts a session.
# Starts a new session, which allows Bugsnag to track error rates across
# releases
#
# Allows Bugsnag to track error rates across releases.
# @return [void]
def start_session
session_tracker.start_session
end

##
# Stop any events being attributed to the current session until it is
# resumed or a new session is started
#
# @see resume_session
#
# @return [void]
def pause_session
session_tracker.pause_session
end

##
# Resume the current session if it was previously paused. If there is no
# current session, a new session will be started
#
# @see pause_session
#
# @return [Boolean] true if a paused session was resumed
def resume_session
session_tracker.resume_session
end

##
# Allow access to "before notify" callbacks as an array.
#
Expand Down
2 changes: 1 addition & 1 deletion lib/bugsnag/middleware/session_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def initialize(bugsnag)
def call(report)
session = Bugsnag::SessionTracker.get_current_session

if session
if session && !session[:paused?]
if report.unhandled
session[:events][:unhandled] += 1
else
Expand Down
54 changes: 49 additions & 5 deletions lib/bugsnag/session_tracker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,20 @@ def initialize
# Starts a new session, storing it on the current thread.
#
# This allows Bugsnag to track error rates for a release.
#
# @return [void]
def start_session
return unless Bugsnag.configuration.enable_sessions && Bugsnag.configuration.should_notify_release_stage?

start_delivery_thread
start_time = Time.now().utc().strftime('%Y-%m-%dT%H:%M:00')
new_session = {
:id => SecureRandom.uuid,
:startedAt => start_time,
:events => {
:handled => 0,
:unhandled => 0
id: SecureRandom.uuid,
startedAt: start_time,
paused?: false,
events: {
handled: 0,
unhandled: 0
}
}
SessionTracker.set_current_session(new_session)
Expand All @@ -53,6 +56,47 @@ def start_session

alias_method :create_session, :start_session

##
# Stop any events being attributed to the current session until it is
# resumed or a new session is started
#
# @see resume_session
#
# @return [void]
def pause_session
current_session = SessionTracker.get_current_session

return unless current_session

current_session[:paused?] = true
end

##
# Resume the current session if it was previously paused. If there is no
# current session, a new session will be started
#
# @see pause_session
#
# @return [Boolean] true if a paused session was resumed
def resume_session
current_session = SessionTracker.get_current_session

if current_session
# if the session is paused then resume it, otherwise we don't need to
# do anything
if current_session[:paused?]
current_session[:paused?] = false

return true
end
else
# if there's no current session, start a new one
start_session
end

false
end

##
# Delivers the current session_counts lists to the session endpoint.
def send_sessions
Expand Down
59 changes: 59 additions & 0 deletions spec/bugsnag_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,65 @@ module Kernel
})
end

it "does not attach session information when the session is paused" do
Bugsnag.configure do |config|
config.auto_capture_sessions = true
end

Bugsnag.start_session
Bugsnag.pause_session

Bugsnag.notify(BugsnagTestException.new("It crashed"), true)

expect(Bugsnag).to(have_sent_notification { |payload, headers|
expect(payload["events"][0]["session"]).to be(nil)

expect(Bugsnag::SessionTracker.get_current_session[:events]).to eq({
handled: 0,
unhandled: 0,
})
})
end

it "attaches session information when the session is resumed" do
Bugsnag.configure do |config|
config.auto_capture_sessions = true
end

Bugsnag.start_session

Bugsnag.notify(BugsnagTestException.new("one handled"))

Bugsnag.pause_session

Bugsnag.notify(BugsnagTestException.new("this unhandled error is not counted"), true)
Bugsnag.notify(BugsnagTestException.new("this handled error is not counted"))

# reset WebMock's stored requests so we only assert against the last one
# as "have_sent_notification" doesn't support finding a specific request
WebMock::RequestRegistry.instance.reset!

Bugsnag.resume_session

Bugsnag.notify(BugsnagTestException.new("one unhandled"), true)

expect(Bugsnag).to(have_sent_notification { |payload, headers|
session = payload["events"][0]["session"]

expect(session["id"]).to match(session_id_regex)
expect(session["startedAt"]).to match(session_timestamp_regex)
expect(session["events"]).to eq({
"handled" => 1,
"unhandled" => 1,
})

expect(Bugsnag::SessionTracker.get_current_session[:events]).to eq({
handled: 1,
unhandled: 1,
})
})
end

it "allows changing an event from handled to unhandled" do
Bugsnag.configure do |config|
config.auto_capture_sessions = true
Expand Down
73 changes: 73 additions & 0 deletions spec/session_tracker_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,77 @@
expect(device["hostname"]).to eq(Bugsnag.configuration.hostname)
expect(device["runtimeVersions"]["ruby"]).to eq(Bugsnag.configuration.runtime_versions["ruby"])
end

context "#pause_session" do
it "does nothing if there is no current session" do
Bugsnag.pause_session

expect(Bugsnag::SessionTracker.get_current_session).to be(nil)
end

it "marks the current session as paused if one exists" do
Bugsnag.start_session

expect(Bugsnag::SessionTracker.get_current_session[:paused?]).to be(false)

Bugsnag.pause_session

expect(Bugsnag::SessionTracker.get_current_session[:paused?]).to be(true)
end

it "does nothing if the current session is already paused" do
Bugsnag.start_session

expect(Bugsnag::SessionTracker.get_current_session[:paused?]).to be(false)

Bugsnag.pause_session

expect(Bugsnag::SessionTracker.get_current_session[:paused?]).to be(true)

Bugsnag.pause_session

expect(Bugsnag::SessionTracker.get_current_session[:paused?]).to be(true)
end
end

context "#resume_session" do
it "returns false and does nothing when there is a current session" do
Bugsnag.start_session

expect(Bugsnag::SessionTracker.get_current_session[:paused?]).to be(false)

expect(Bugsnag.resume_session).to be(false)

expect(Bugsnag::SessionTracker.get_current_session[:paused?]).to be(false)
end

it "returns false and does nothing when a session is started after one has been paused" do
Bugsnag.start_session
Bugsnag.pause_session
Bugsnag.start_session

expect(Bugsnag::SessionTracker.get_current_session[:paused?]).to be(false)

expect(Bugsnag.resume_session).to be(false)

expect(Bugsnag::SessionTracker.get_current_session[:paused?]).to be(false)
end

it "returns false and starts a new session when there is no current or paused session" do
expect(Bugsnag.resume_session).to be(false)

expect(Bugsnag::SessionTracker.get_current_session).not_to be(nil)
end

it "returns true and makes the paused session the active session when there is no current session" do
Bugsnag.start_session
Bugsnag.pause_session

expect(Bugsnag::SessionTracker.get_current_session[:paused?]).to be(true)

expect(Bugsnag.resume_session).to be(true)

expect(Bugsnag::SessionTracker.get_current_session).not_to be(nil)
end
end
end
2 changes: 2 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ def ruby_version_greater_equal?(target_version)
Bugsnag.instance_variable_set(:@session_tracker, Bugsnag::SessionTracker.new)
Bugsnag.instance_variable_set(:@cleaner, Bugsnag::Cleaner.new(Bugsnag.configuration))

Thread.current[Bugsnag::SessionTracker::THREAD_SESSION] = nil

Bugsnag.configure do |bugsnag|
bugsnag.api_key = "c9d60ae4c7e70c4b6c4ebd3e8056d2b8"
bugsnag.release_stage = "production"
Expand Down

0 comments on commit 9595bbe

Please sign in to comment.