Add Sentry

Stephen Paul Weber created

Capture in #panic and a few other places, add customer and route context
as well as some initial breadcrumbs in the register command.

Change summary

Gemfile    |  1 
sgx_jmp.rb | 63 ++++++++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 56 insertions(+), 8 deletions(-)

Detailed changes

Gemfile 🔗

@@ -13,6 +13,7 @@ gem "em_promise.rb", "~> 0.0.2"
 gem "eventmachine"
 gem "money-open-exchange-rates"
 gem "ruby-bandwidth-iris"
+gem "sentry-ruby"
 
 group(:development) do
 	gem "pry-reload"

sgx_jmp.rb 🔗

@@ -10,6 +10,9 @@ require "dhall"
 require "em-hiredis"
 require "em_promise"
 require "ruby-bandwidth-iris"
+require "sentry-ruby"
+
+Sentry.init
 
 CONFIG =
 	Dhall::Coder
@@ -42,6 +45,17 @@ BandwidthIris::Client.global_options = {
 	password: CONFIG[:creds][:password]
 }
 
+def new_sentry_hub(stanza, name: nil)
+	hub = Sentry.get_current_hub&.new_from_top
+	raise "Sentry.init has not been called" unless hub
+
+	hub.push_scope
+	hub.current_scope.clear_breadcrumbs
+	hub.current_scope.set_transaction_name(name) if name
+	hub.current_scope.set_user(jid: stanza.from.stripped.to_s)
+	hub
+end
+
 # Braintree is not async, so wrap in EM.defer for now
 class AsyncBraintree
 	def initialize(environment:, merchant_id:, public_key:, private_key:, **)
@@ -79,10 +93,15 @@ end
 
 BRAINTREE = AsyncBraintree.new(**CONFIG[:braintree])
 
-def panic(e)
+def panic(e, hub=nil)
 	m = e.respond_to?(:message) ? e.message : e
 	warn "Error raised during event loop: #{e.class}: #{m}"
 	warn e.backtrace if e.respond_to?(:backtrace)
+	if e.is_a?(::Exception)
+		(hub || Sentry).capture_exception(e, hint: { background: false })
+	else
+		(hub || Sentry).capture_message(e, hint: { background: false })
+	end
 	exit 1
 end
 
@@ -121,13 +140,25 @@ message to: /\Aaccount@/ do |m|
 end
 
 message to: /\Acustomer_/, from: /@#{CONFIG[:sgx]}(\/|\Z)/ do |m|
+	sentry_hub = new_sentry_hub(iq, name: iq.node)
 	Customer.for_customer_id(
 		m.to.node.delete_prefix("customer_")
-	).then { |customer| customer.stanza_to(m) }.catch(&method(:panic))
+	).then { |customer|
+		sentry_hub.current_scope.set_user(
+			id: customer.customer_id,
+			jid: iq.from.stripped.to_s
+		)
+		customer.stanza_to(m)
+	}.catch { |e| panic(e, sentry_hub) }
 end
 
 message do |m|
+	sentry_hub = new_sentry_hub(iq, name: iq.node)
 	Customer.for_jid(m.from.stripped).then { |customer|
+		sentry_hub.current_scope.set_user(
+			id: customer.customer_id,
+			jid: iq.from.stripped.to_s
+		)
 		today = Time.now.utc.to_date
 		EMPromise.all([
 			REDIS.zremrangebylex(
@@ -143,7 +174,7 @@ message do |m|
 			),
 			customer.stanza_from(m)
 		])
-	}.catch(&method(:panic))
+	}.catch { |e| panic(e, sentry_hub) }
 end
 
 message :error? do |m|
@@ -228,15 +259,28 @@ disco_items node: "http://jabber.org/protocol/commands" do |iq|
 end
 
 command :execute?, node: "jabber:iq:register", sessionid: nil do |iq|
-	Customer.for_jid(iq.from.stripped).catch {
+	sentry_hub = new_sentry_hub(iq, name: iq.node)
+	EMPromise.resolve(nil).then {
+		Customer.for_jid(iq.from.stripped)
+	}.catch {
+		sentry_hub.add_breadcrumb(Sentry::Breadcrumb.new(
+			message: "Customer.create"
+		))
 		Customer.create(iq.from.stripped)
 	}.then { |customer|
+		sentry_hub.current_scope.set_user(
+			id: customer.customer_id,
+			jid: iq.from.stripped.to_s
+		)
+		sentry_hub.add_breadcrumb(Sentry::Breadcrumb.new(
+			message: "Registration.for"
+		))
 		Registration.for(
 			iq,
 			customer,
 			web_register_manager
 		).then(&:write)
-	}.catch(&method(:panic))
+	}.catch { |e| panic(e, sentry_hub) }
 end
 
 def reply_with_note(iq, text, type: :info)
@@ -249,6 +293,7 @@ def reply_with_note(iq, text, type: :info)
 end
 
 command :execute?, node: "buy-credit", sessionid: nil do |iq|
+	sentry_hub = new_sentry_hub(iq, name: iq.node)
 	reply = iq.reply
 	reply.allowed_actions = [:complete]
 
@@ -265,12 +310,14 @@ command :execute?, node: "buy-credit", sessionid: nil do |iq|
 	}.then { |amount|
 		reply_with_note(iq, "$#{'%.2f' % amount} added to your account balance.")
 	}.catch { |e|
+		sentry_hub.capture_exception(e)
 		text = "Failed to buy credit, system said: #{e.message}"
 		reply_with_note(iq, text, type: :error)
-	}.catch(&method(:panic))
+	}.catch { |e| panic(e, sentry_hub) }
 end
 
 command :execute?, node: "web-register", sessionid: nil do |iq|
+	sentry_hub = new_sentry_hub(iq, name: iq.node)
 	jid = iq.form.field("jid")&.value.to_s.strip
 	tel = iq.form.field("tel")&.value.to_s.strip
 	if iq.from.stripped != CONFIG[:web_register][:from]
@@ -284,11 +331,11 @@ command :execute?, node: "web-register", sessionid: nil do |iq|
 			cmd.node = "push-register"
 			cmd.form.fields = [var: "to", value: jid]
 			cmd.form.type = "submit"
-		}).then do |result|
+		}).then { |result|
 			final_jid = result.form.field("from")&.value.to_s.strip
 			web_register_manager[final_jid] = tel
 			BLATHER << iq.reply.tap { |reply| reply.status = :completed }
-		end
+		}.catch { |e| panic(e, sentry_hub) }
 	end
 end