From ff83f0017d26da17e1babb3f27e073e0d733d195 Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Mon, 18 Aug 2025 15:33:56 +0200 Subject: [PATCH 01/17] Add sentry middleware --- eventboss.gemspec | 1 + lib/eventboss/long_poller.rb | 3 +- lib/eventboss/sentry/configure.rb | 11 ++++ lib/eventboss/sentry/error_handler.rb | 15 +++++ lib/eventboss/sentry/integration.rb | 14 +++++ lib/eventboss/sentry/server_middleware.rb | 72 +++++++++++++++++++++++ 6 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 lib/eventboss/sentry/configure.rb create mode 100644 lib/eventboss/sentry/error_handler.rb create mode 100644 lib/eventboss/sentry/integration.rb create mode 100644 lib/eventboss/sentry/server_middleware.rb diff --git a/eventboss.gemspec b/eventboss.gemspec index 62232ee..4e386ba 100644 --- a/eventboss.gemspec +++ b/eventboss.gemspec @@ -28,4 +28,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "bundler", ">= 1" spec.add_development_dependency 'rake', '>= 10.0' spec.add_development_dependency "rspec", "~> 3.0" + spec.add_development_dependency "sentry-ruby" end diff --git a/lib/eventboss/long_poller.rb b/lib/eventboss/long_poller.rb index 67845c5..8f36bc6 100644 --- a/lib/eventboss/long_poller.rb +++ b/lib/eventboss/long_poller.rb @@ -73,7 +73,8 @@ def fetch_messages @client.receive_message( queue_url: queue.url, max_number_of_messages: 10, - wait_time_seconds: TIME_WAIT + wait_time_seconds: TIME_WAIT, + attribute_names: ['SentTimestamp', 'ApproximateReceiveCount'] ).messages end end diff --git a/lib/eventboss/sentry/configure.rb b/lib/eventboss/sentry/configure.rb new file mode 100644 index 0000000..b3e6e82 --- /dev/null +++ b/lib/eventboss/sentry/configure.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative 'integration' + +# Auto configure eventboss to use sentry + +Eventboss.configure do |config| + config.server_middleware.add Eventboss::Sentry::ServerMiddleware + config.error_handlers << Eventboss::Sentry::ErrorHandler.new +end + diff --git a/lib/eventboss/sentry/error_handler.rb b/lib/eventboss/sentry/error_handler.rb new file mode 100644 index 0000000..2c3a694 --- /dev/null +++ b/lib/eventboss/sentry/error_handler.rb @@ -0,0 +1,15 @@ +module Eventboss + module Sentry + class ErrorHandler + def call(exception, _context = {}) + return unless ::Sentry.initialized? + + Eventboss::Sentry::Integration.capture_exception( + exception, + contexts: { eventboss: { } }, + hint: { background: false } + ) + end + end + end +end diff --git a/lib/eventboss/sentry/integration.rb b/lib/eventboss/sentry/integration.rb new file mode 100644 index 0000000..5be946f --- /dev/null +++ b/lib/eventboss/sentry/integration.rb @@ -0,0 +1,14 @@ +require 'sentry-ruby' +require 'sentry/integrable' +require_relative 'error_handler' +require_relative 'server_middleware' + +module Eventboss + module Sentry + class Integration + extend ::Sentry::Integrable + + register_integration name: "eventboss", version: Eventboss::VERSION + end + end +end diff --git a/lib/eventboss/sentry/server_middleware.rb b/lib/eventboss/sentry/server_middleware.rb new file mode 100644 index 0000000..a4dda6e --- /dev/null +++ b/lib/eventboss/sentry/server_middleware.rb @@ -0,0 +1,72 @@ +module Eventboss + module Sentry + class ServerMiddleware < Eventboss::Middleware::Base + OP_NAME = 'queue.process' + SPAN_ORIGIN = 'auto.queue.eventboss' + + def call(work) + return yield unless ::Sentry.initialized? + + ::Sentry.clone_hub_to_current_thread + scope = ::Sentry.get_current_scope + scope.clear + scope.set_tags(queue: work.queue.name, message_id: work.message.message_id) + scope.set_transaction_name(work.listener.to_s, source: :task) + transaction = start_transaction(scope) + + if transaction + scope.set_span(transaction) + transaction.set_data(::Sentry::Span::DataConventions::MESSAGING_MESSAGE_ID, work.message.message_id) + transaction.set_data(::Sentry::Span::DataConventions::MESSAGING_DESTINATION_NAME, work.queue.name) + + if (latency = extract_latency(work.message)) + transaction.set_data(::Sentry::Span::DataConventions::MESSAGING_MESSAGE_RECEIVE_LATENCY, latency) + end + + if (retry_count = extract_receive_count(work.message)) + transaction.set_data(::Sentry::Span::DataConventions::MESSAGING_MESSAGE_RETRY_COUNT, retry_count) + end + end + + begin + yield + rescue StandardError + finish_transaction(transaction, 500) + raise + end + + finish_transaction(transaction, 200) + end + + def start_transaction(scope) + options = { + name: scope.transaction_name, + source: scope.transaction_source, + op: OP_NAME, + origin: SPAN_ORIGIN + } + + ::Sentry.start_transaction(**options) + end + + def finish_transaction(transaction, status) + return unless transaction + + transaction.set_http_status(status) + transaction.finish + end + + def extract_latency(message) + if sent_timestamp = message.attributes.fetch('SentTimestamp', nil) + Time.now - Time.at(sent_timestamp.to_i / 1000.0) + end + end + + def extract_receive_count(message) + if receive_count = message.attributes.fetch('ApproximateReceiveCount', nil) + receive_count.to_i - 1 + end + end + end + end +end \ No newline at end of file From 084e0a1469f3c82d77bfd2c0ef1faa320c69e391 Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Tue, 19 Aug 2025 10:08:37 +0200 Subject: [PATCH 02/17] Update long_poller_spec.rb --- spec/eventboss/long_poller_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/eventboss/long_poller_spec.rb b/spec/eventboss/long_poller_spec.rb index b10f7cb..be7c9b3 100644 --- a/spec/eventboss/long_poller_spec.rb +++ b/spec/eventboss/long_poller_spec.rb @@ -31,7 +31,8 @@ it 'calls client with proper attributes' do expect(client).to receive(:receive_message) - .with(queue_url: 'url', max_number_of_messages: 10, wait_time_seconds: 10) + .with(queue_url: 'url', max_number_of_messages: 10, wait_time_seconds: 10, + attribute_names: ["SentTimestamp", "ApproximateReceiveCount"]) subject.fetch_and_dispatch end From 4bf972d9d227b6a0b1b7e6946b078b7ac6094c3a Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Tue, 19 Aug 2025 12:50:16 +0200 Subject: [PATCH 03/17] Prefix transaction name with Eventboss --- lib/eventboss/sentry/server_middleware.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/eventboss/sentry/server_middleware.rb b/lib/eventboss/sentry/server_middleware.rb index a4dda6e..8bbade7 100644 --- a/lib/eventboss/sentry/server_middleware.rb +++ b/lib/eventboss/sentry/server_middleware.rb @@ -11,7 +11,7 @@ def call(work) scope = ::Sentry.get_current_scope scope.clear scope.set_tags(queue: work.queue.name, message_id: work.message.message_id) - scope.set_transaction_name(work.listener.to_s, source: :task) + scope.set_transaction_name(extract_transaction_name(work), source: :task) transaction = start_transaction(scope) if transaction @@ -56,6 +56,9 @@ def finish_transaction(transaction, status) transaction.finish end + def extract_transaction_name(work) + "Eventboss/#{work.listener.to_s}" + end def extract_latency(message) if sent_timestamp = message.attributes.fetch('SentTimestamp', nil) Time.now - Time.at(sent_timestamp.to_i / 1000.0) From ca09ee037e6d659d52628c957b381bb53e55d8bb Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Tue, 19 Aug 2025 12:50:37 +0200 Subject: [PATCH 04/17] remove env from queue name --- lib/eventboss/sentry/server_middleware.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/eventboss/sentry/server_middleware.rb b/lib/eventboss/sentry/server_middleware.rb index 8bbade7..de9f5ac 100644 --- a/lib/eventboss/sentry/server_middleware.rb +++ b/lib/eventboss/sentry/server_middleware.rb @@ -4,20 +4,27 @@ class ServerMiddleware < Eventboss::Middleware::Base OP_NAME = 'queue.process' SPAN_ORIGIN = 'auto.queue.eventboss' + # since sentry has env selector, we can remove it from queue names + QUEUES_WITHOUT_ENV = Hash.new do |hash, key| + hash[key] = key + .gsub("-#{Eventboss.env}-", '') + .gsub("-#{Eventboss.env}", '') + end + def call(work) return yield unless ::Sentry.initialized? ::Sentry.clone_hub_to_current_thread scope = ::Sentry.get_current_scope scope.clear - scope.set_tags(queue: work.queue.name, message_id: work.message.message_id) + scope.set_tags(queue: extract_queue_name(work), message_id: work.message.message_id) scope.set_transaction_name(extract_transaction_name(work), source: :task) transaction = start_transaction(scope) if transaction scope.set_span(transaction) transaction.set_data(::Sentry::Span::DataConventions::MESSAGING_MESSAGE_ID, work.message.message_id) - transaction.set_data(::Sentry::Span::DataConventions::MESSAGING_DESTINATION_NAME, work.queue.name) + transaction.set_data(::Sentry::Span::DataConventions::MESSAGING_DESTINATION_NAME, extract_queue_name(work)) if (latency = extract_latency(work.message)) transaction.set_data(::Sentry::Span::DataConventions::MESSAGING_MESSAGE_RECEIVE_LATENCY, latency) @@ -59,6 +66,11 @@ def finish_transaction(transaction, status) def extract_transaction_name(work) "Eventboss/#{work.listener.to_s}" end + + def extract_queue_name(work) + QUEUES_WITHOUT_ENV[work.queue.name] + end + def extract_latency(message) if sent_timestamp = message.attributes.fetch('SentTimestamp', nil) Time.now - Time.at(sent_timestamp.to_i / 1000.0) From 35867d6c2d38d491a5d7ff20ec5525661deddc99 Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Tue, 19 Aug 2025 12:51:15 +0200 Subject: [PATCH 05/17] remove env from queue name --- lib/eventboss/sentry/server_middleware.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/eventboss/sentry/server_middleware.rb b/lib/eventboss/sentry/server_middleware.rb index de9f5ac..b96b6e0 100644 --- a/lib/eventboss/sentry/server_middleware.rb +++ b/lib/eventboss/sentry/server_middleware.rb @@ -7,8 +7,8 @@ class ServerMiddleware < Eventboss::Middleware::Base # since sentry has env selector, we can remove it from queue names QUEUES_WITHOUT_ENV = Hash.new do |hash, key| hash[key] = key - .gsub("-#{Eventboss.env}-", '') - .gsub("-#{Eventboss.env}", '') + .gsub("-#{Eventboss.env}-", '-ENV-') + .gsub("-#{Eventboss.env}", '-ENV') end def call(work) From 9345072a509cb06fc9ecd85f009a450121f0953a Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:14:15 +0200 Subject: [PATCH 06/17] add distributed tracing --- lib/eventboss/long_poller.rb | 3 ++- lib/eventboss/publisher.rb | 33 +++++++++++++++++++++++ lib/eventboss/sentry/server_middleware.rb | 19 ++++++++++--- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/lib/eventboss/long_poller.rb b/lib/eventboss/long_poller.rb index 8f36bc6..0f684b9 100644 --- a/lib/eventboss/long_poller.rb +++ b/lib/eventboss/long_poller.rb @@ -74,7 +74,8 @@ def fetch_messages queue_url: queue.url, max_number_of_messages: 10, wait_time_seconds: TIME_WAIT, - attribute_names: ['SentTimestamp', 'ApproximateReceiveCount'] + attribute_names: ['SentTimestamp', 'ApproximateReceiveCount'], + message_attribute_names: ['sentry-trace', 'baggage', 'sentry_user'] ).messages end end diff --git a/lib/eventboss/publisher.rb b/lib/eventboss/publisher.rb index c62aa22..a2aae23 100644 --- a/lib/eventboss/publisher.rb +++ b/lib/eventboss/publisher.rb @@ -10,6 +10,28 @@ def initialize(event_name, sns_client, configuration, opts = {}) end def publish(payload) + if defined?(::Eventboss::Sentry::Integration) && ::Sentry.initialized? + publish_with_sentry(payload) + else + publish_without_sentry(payload) + end + end + + private + + def publish_with_sentry(payload) + topic_arn = Topic.build_arn(event_name: event_name, source_app: source) + + ::Sentry.with_child_span(op: "queue.publish", description: "#{source}##{event_name}") do + sns_client.publish( + topic_arn: topic_arn, + message: json_payload(payload), + message_attributes: build_sentry_message_attributes + ) + end + end + + def publish_without_sentry(payload) topic_arn = Topic.build_arn(event_name: event_name, source_app: source) sns_client.publish( topic_arn: topic_arn, @@ -24,5 +46,16 @@ def publish(payload) def json_payload(payload) payload.is_a?(String) ? payload : payload.to_json end + + def build_sentry_message_attributes + attributes = ::Sentry.get_trace_propagation_headers + .slice('sentry-trace', 'baggage') + .transform_values do |header_value| + { string_value: header_value, data_type: 'String' } + end + user = ::Sentry.get_current_scope.user + attributes['sentry_user'] = user unless user.empty? + attributes + end end end diff --git a/lib/eventboss/sentry/server_middleware.rb b/lib/eventboss/sentry/server_middleware.rb index b96b6e0..bdd5557 100644 --- a/lib/eventboss/sentry/server_middleware.rb +++ b/lib/eventboss/sentry/server_middleware.rb @@ -17,9 +17,12 @@ def call(work) ::Sentry.clone_hub_to_current_thread scope = ::Sentry.get_current_scope scope.clear + if (user = extract_sentry_user(work)) + scope.set_user(user) + end scope.set_tags(queue: extract_queue_name(work), message_id: work.message.message_id) scope.set_transaction_name(extract_transaction_name(work), source: :task) - transaction = start_transaction(scope) + transaction = start_transaction(scope, work) if transaction scope.set_span(transaction) @@ -45,7 +48,7 @@ def call(work) finish_transaction(transaction, 200) end - def start_transaction(scope) + def start_transaction(scope, work) options = { name: scope.transaction_name, source: scope.transaction_source, @@ -53,7 +56,13 @@ def start_transaction(scope) origin: SPAN_ORIGIN } - ::Sentry.start_transaction(**options) + env = { + 'sentry-trace' => work.message.message_attributes['sentry-trace']&.string_value, + 'baggage' => work.message.message_attributes['baggage']&.string_value + } + + transaction = ::Sentry.continue_trace(env, **options) + ::Sentry.start_transaction(transaction: transaction, **options) end def finish_transaction(transaction, status) @@ -63,6 +72,10 @@ def finish_transaction(transaction, status) transaction.finish end + def extract_sentry_user(work) + work.message.message_attributes["sentry_user"]&.string_value + end + def extract_transaction_name(work) "Eventboss/#{work.listener.to_s}" end From ff672d1d5537dda031fd95e080342e8b073af0e1 Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:18:45 +0200 Subject: [PATCH 07/17] Update long_poller_spec.rb --- spec/eventboss/long_poller_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/eventboss/long_poller_spec.rb b/spec/eventboss/long_poller_spec.rb index be7c9b3..236fbc9 100644 --- a/spec/eventboss/long_poller_spec.rb +++ b/spec/eventboss/long_poller_spec.rb @@ -32,7 +32,8 @@ it 'calls client with proper attributes' do expect(client).to receive(:receive_message) .with(queue_url: 'url', max_number_of_messages: 10, wait_time_seconds: 10, - attribute_names: ["SentTimestamp", "ApproximateReceiveCount"]) + attribute_names: ["SentTimestamp", "ApproximateReceiveCount"], + message_attribute_names: ["sentry-trace", "baggage", "sentry_user"]) subject.fetch_and_dispatch end From 393a1fc8a4b7fddde7916fba95eb1499360eda99 Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Tue, 19 Aug 2025 16:04:02 +0200 Subject: [PATCH 08/17] fix sentry_user --- lib/eventboss/publisher.rb | 7 ++++++- lib/eventboss/sentry/server_middleware.rb | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/eventboss/publisher.rb b/lib/eventboss/publisher.rb index a2aae23..66210ed 100644 --- a/lib/eventboss/publisher.rb +++ b/lib/eventboss/publisher.rb @@ -54,7 +54,12 @@ def build_sentry_message_attributes { string_value: header_value, data_type: 'String' } end user = ::Sentry.get_current_scope.user - attributes['sentry_user'] = user unless user.empty? + if user && !user.empty? + attributes['sentry_user'] = { + string_value: user.to_json, + data_type: 'String' + } + end attributes end end diff --git a/lib/eventboss/sentry/server_middleware.rb b/lib/eventboss/sentry/server_middleware.rb index bdd5557..a8e7adb 100644 --- a/lib/eventboss/sentry/server_middleware.rb +++ b/lib/eventboss/sentry/server_middleware.rb @@ -73,7 +73,9 @@ def finish_transaction(transaction, status) end def extract_sentry_user(work) - work.message.message_attributes["sentry_user"]&.string_value + if (value = work.message.message_attributes["sentry_user"]&.string_value) + JSON.parse(value) + end end def extract_transaction_name(work) From 3138f40df9cae44e93d56ecd6bf06441f565d524 Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Wed, 20 Aug 2025 08:29:43 +0200 Subject: [PATCH 09/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eventboss.gemspec | 1 - lib/eventboss/publisher.rb | 2 +- lib/eventboss/sentry/server_middleware.rb | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/eventboss.gemspec b/eventboss.gemspec index 4e386ba..62232ee 100644 --- a/eventboss.gemspec +++ b/eventboss.gemspec @@ -28,5 +28,4 @@ Gem::Specification.new do |spec| spec.add_development_dependency "bundler", ">= 1" spec.add_development_dependency 'rake', '>= 10.0' spec.add_development_dependency "rspec", "~> 3.0" - spec.add_development_dependency "sentry-ruby" end diff --git a/lib/eventboss/publisher.rb b/lib/eventboss/publisher.rb index 66210ed..f415615 100644 --- a/lib/eventboss/publisher.rb +++ b/lib/eventboss/publisher.rb @@ -22,7 +22,7 @@ def publish(payload) def publish_with_sentry(payload) topic_arn = Topic.build_arn(event_name: event_name, source_app: source) - ::Sentry.with_child_span(op: "queue.publish", description: "#{source}##{event_name}") do + ::Sentry.with_child_span(op: "queue.publish", description: "Eventboss push #{source}/#{event_name}") do sns_client.publish( topic_arn: topic_arn, message: json_payload(payload), diff --git a/lib/eventboss/sentry/server_middleware.rb b/lib/eventboss/sentry/server_middleware.rb index a8e7adb..e33e34b 100644 --- a/lib/eventboss/sentry/server_middleware.rb +++ b/lib/eventboss/sentry/server_middleware.rb @@ -7,8 +7,8 @@ class ServerMiddleware < Eventboss::Middleware::Base # since sentry has env selector, we can remove it from queue names QUEUES_WITHOUT_ENV = Hash.new do |hash, key| hash[key] = key - .gsub("-#{Eventboss.env}-", '-ENV-') - .gsub("-#{Eventboss.env}", '-ENV') + .gsub(/-#{Eventboss.env}-deadletter$/, '-ENV-deadletter') + .gsub(/-#{Eventboss.env}$/, '-ENV') end def call(work) From 803af4dadb248dab7552d5c0f225cf1382e43241 Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Wed, 20 Aug 2025 08:45:56 +0200 Subject: [PATCH 10/17] documentation --- README.md | 17 +++++++++++++++++ lib/eventboss/error_handlers/sentry.rb | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/README.md b/README.md index 23b6aee..d7cd10f 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,23 @@ Eventboss.configure do |config| end ``` +### Sentry Integration + +Eventboss provides built-in integration with [Sentry](https://sentry.io/) for error monitoring and performance tracking. The simplest way to enable Sentry integration is to require the configuration module: + +```ruby +require 'eventboss/sentry/configure' +``` + +For more advanced configuration options, you can manually configure the integration: + +```ruby +Eventboss.configure do |config| + config.server_middleware.add Eventboss::Sentry::ServerMiddleware + config.error_handlers << Eventboss::Sentry::ErrorHandler.new +end +``` + ### Middlewares Server middlewares intercept the execution of your `Listeners`. You can use to extract and run common functions on every message received. diff --git a/lib/eventboss/error_handlers/sentry.rb b/lib/eventboss/error_handlers/sentry.rb index 5672eb3..71d0408 100644 --- a/lib/eventboss/error_handlers/sentry.rb +++ b/lib/eventboss/error_handlers/sentry.rb @@ -1,6 +1,12 @@ module Eventboss module ErrorHandlers class Sentry + def initialize + warn "[DEPRECATED] Eventboss::ErrorHandlers::Sentry is deprecated. " \ + "Use Eventboss::Sentry::ErrorHandler instead. " \ + "For automatic configuration, require 'eventboss/sentry/configure'. " \ + "This class will be removed in a future version." + super def call(exception, context = {}) eventboss_context = { component: 'eventboss' } eventboss_context[:action] = context[:processor].class.to_s if context[:processor] From 2c66a278efc4e1c8993fb17db9d032dcc3666dff Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Wed, 20 Aug 2025 08:53:43 +0200 Subject: [PATCH 11/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/eventboss/error_handlers/sentry.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/eventboss/error_handlers/sentry.rb b/lib/eventboss/error_handlers/sentry.rb index 71d0408..aa71be9 100644 --- a/lib/eventboss/error_handlers/sentry.rb +++ b/lib/eventboss/error_handlers/sentry.rb @@ -7,6 +7,8 @@ def initialize "For automatic configuration, require 'eventboss/sentry/configure'. " \ "This class will be removed in a future version." super + end + def call(exception, context = {}) eventboss_context = { component: 'eventboss' } eventboss_context[:action] = context[:processor].class.to_s if context[:processor] From c8ec62a8cefc5b76389177c96ad2bd734d9c674a Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Wed, 20 Aug 2025 09:01:42 +0200 Subject: [PATCH 12/17] Update README.md --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index d7cd10f..282e981 100644 --- a/README.md +++ b/README.md @@ -165,13 +165,7 @@ Eventboss provides built-in integration with [Sentry](https://sentry.io/) for er require 'eventboss/sentry/configure' ``` -For more advanced configuration options, you can manually configure the integration: - -```ruby -Eventboss.configure do |config| - config.server_middleware.add Eventboss::Sentry::ServerMiddleware - config.error_handlers << Eventboss::Sentry::ErrorHandler.new -end +For more advanced configuration options, you can manually configure the integration. Please inspect [lib/eventboss/sentry/configure.rb](lib/eventboss/sentry/configure.rb) to see options. ``` ### Middlewares From 0bafb08ead66d3d20ac78315d349b0fae03ba606 Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Wed, 20 Aug 2025 09:07:35 +0200 Subject: [PATCH 13/17] update version --- CHANGELOG.md | 8 ++++++++ lib/eventboss/version.rb | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de2d002..1da7010 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.9.6] + +### Added +- Enhanced Sentry integration + +### Deprecated +- `Eventboss::ErrorHandlers::Sentry` is now deprecated in favor of `Eventboss::Sentry::ErrorHandler` + ## [1.9.2] - 2025-01-21 - Fix typo in instance var during shut down diff --git a/lib/eventboss/version.rb b/lib/eventboss/version.rb index a11dd83..ec1f59d 100644 --- a/lib/eventboss/version.rb +++ b/lib/eventboss/version.rb @@ -1,3 +1,3 @@ module Eventboss - VERSION = "1.9.5" + VERSION = "1.9.6" end From 9e2565271b620c16d05d10f8b243783231bbf289 Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Wed, 20 Aug 2025 09:33:15 +0200 Subject: [PATCH 14/17] Update publisher.rb --- lib/eventboss/publisher.rb | 55 +++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/lib/eventboss/publisher.rb b/lib/eventboss/publisher.rb index f415615..a532b5f 100644 --- a/lib/eventboss/publisher.rb +++ b/lib/eventboss/publisher.rb @@ -9,58 +9,69 @@ def initialize(event_name, sns_client, configuration, opts = {}) @source = configuration.eventboss_app_name unless opts[:generic] end + # Publishes the payload to the SNS topic. + # If Sentry is enabled, it wraps the publish action in a Sentry span for tracing. def publish(payload) - if defined?(::Eventboss::Sentry::Integration) && ::Sentry.initialized? - publish_with_sentry(payload) + sns_params = build_sns_params(payload) + publisher_action = -> { sns_client.publish(sns_params) } + + if sentry_enabled? + with_sentry_span(&publisher_action) else - publish_without_sentry(payload) + publisher_action.call end end private - def publish_with_sentry(payload) - topic_arn = Topic.build_arn(event_name: event_name, source_app: source) + attr_reader :event_name, :sns_client, :configuration, :source - ::Sentry.with_child_span(op: "queue.publish", description: "Eventboss push #{source}/#{event_name}") do - sns_client.publish( - topic_arn: topic_arn, - message: json_payload(payload), - message_attributes: build_sentry_message_attributes - ) - end + def sentry_enabled? + defined?(::Eventboss::Sentry::Integration) && ::Sentry.initialized? end - def publish_without_sentry(payload) - topic_arn = Topic.build_arn(event_name: event_name, source_app: source) - sns_client.publish( - topic_arn: topic_arn, - message: json_payload(payload) - ) + def build_sns_params(payload) + { + topic_arn: Topic.build_arn(event_name: event_name, source_app: source), + message: json_payload(payload), + message_attributes: sentry_enabled? ? build_sentry_message_attributes : {} + } end - private + def with_sentry_span + queue_name = Queue.build_name(destination: source, event_name: event_name, env: Eventboss.env, source_app: source) - attr_reader :event_name, :sns_client, :configuration, :source + ::Sentry.with_child_span(op: 'queue.publish', description: "Eventboss push #{source}/#{event_name}") do |span| + span.set_data(::Sentry::Span::DataConventions::MESSAGING_DESTINATION_NAME, Eventboss::Sentry::ServerMiddleware::QUEUES_WITHOUT_ENV[queue_name]) + + message = yield # Executes the publisher_action lambda + + span.set_data(::Sentry::Span::DataConventions::MESSAGING_MESSAGE_ID, message.message_id) + message + end + end def json_payload(payload) payload.is_a?(String) ? payload : payload.to_json end + # Constructs SNS message attributes for Sentry trace propagation. def build_sentry_message_attributes attributes = ::Sentry.get_trace_propagation_headers .slice('sentry-trace', 'baggage') .transform_values do |header_value| { string_value: header_value, data_type: 'String' } end - user = ::Sentry.get_current_scope.user + + user = ::Sentry.get_current_scope&.user if user && !user.empty? attributes['sentry_user'] = { string_value: user.to_json, data_type: 'String' } end + attributes end end -end +end \ No newline at end of file From 531b8ca97b917b6c70b81ae5f6c283d3958ce56c Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Wed, 20 Aug 2025 09:35:04 +0200 Subject: [PATCH 15/17] Update publisher_spec.rb --- spec/eventboss/publisher_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/eventboss/publisher_spec.rb b/spec/eventboss/publisher_spec.rb index 1d56de8..75ad99f 100644 --- a/spec/eventboss/publisher_spec.rb +++ b/spec/eventboss/publisher_spec.rb @@ -26,7 +26,8 @@ it 'publishes to sns with source app name by default' do expect(sns_client).to receive(:publish).with( topic_arn: "arn:aws:sns:::eventboss-app_name1-event_name-ping", - message: "{}" + message: "{}", + message_attributes: {} ) expect(subject).to eq(sns_response) end @@ -53,7 +54,8 @@ it 'publishes to sns without app name' do expect(sns_client).to receive(:publish).with( topic_arn: "arn:aws:sns:::eventboss-event_name-ping", - message: "{}" + message: "{}", + message_attributes: {} ) subject end From e725c0d649c836cf0e8324f18e15364841768fcf Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Wed, 20 Aug 2025 09:37:34 +0200 Subject: [PATCH 16/17] Update publisher.rb --- lib/eventboss/publisher.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/eventboss/publisher.rb b/lib/eventboss/publisher.rb index a532b5f..2c121b8 100644 --- a/lib/eventboss/publisher.rb +++ b/lib/eventboss/publisher.rb @@ -13,7 +13,7 @@ def initialize(event_name, sns_client, configuration, opts = {}) # If Sentry is enabled, it wraps the publish action in a Sentry span for tracing. def publish(payload) sns_params = build_sns_params(payload) - publisher_action = -> { sns_client.publish(sns_params) } + publisher_action = -> { sns_client.publish(**sns_params) } if sentry_enabled? with_sentry_span(&publisher_action) From f89edb5c1bb1b7675c81cdc60d5af34f2d94eac9 Mon Sep 17 00:00:00 2001 From: Andrzej Bisewski <152981096+andrzejbisewski@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:21:34 +0200 Subject: [PATCH 17/17] Apply review remarks --- lib/eventboss/publisher.rb | 38 ++++++----------------- lib/eventboss/sentry/context.rb | 35 +++++++++++++++++++++ lib/eventboss/sentry/integration.rb | 1 + lib/eventboss/sentry/server_middleware.rb | 9 +----- 4 files changed, 46 insertions(+), 37 deletions(-) create mode 100644 lib/eventboss/sentry/context.rb diff --git a/lib/eventboss/publisher.rb b/lib/eventboss/publisher.rb index 2c121b8..d539618 100644 --- a/lib/eventboss/publisher.rb +++ b/lib/eventboss/publisher.rb @@ -9,16 +9,9 @@ def initialize(event_name, sns_client, configuration, opts = {}) @source = configuration.eventboss_app_name unless opts[:generic] end - # Publishes the payload to the SNS topic. - # If Sentry is enabled, it wraps the publish action in a Sentry span for tracing. def publish(payload) - sns_params = build_sns_params(payload) - publisher_action = -> { sns_client.publish(**sns_params) } - - if sentry_enabled? - with_sentry_span(&publisher_action) - else - publisher_action.call + with_sentry_span do + sns_client.publish(**build_sns_params(payload)) end end @@ -34,17 +27,19 @@ def build_sns_params(payload) { topic_arn: Topic.build_arn(event_name: event_name, source_app: source), message: json_payload(payload), - message_attributes: sentry_enabled? ? build_sentry_message_attributes : {} + message_attributes: sentry_enabled? ? build_sns_message_attributes : {} } end def with_sentry_span + return yield unless sentry_enabled? + queue_name = Queue.build_name(destination: source, event_name: event_name, env: Eventboss.env, source_app: source) ::Sentry.with_child_span(op: 'queue.publish', description: "Eventboss push #{source}/#{event_name}") do |span| - span.set_data(::Sentry::Span::DataConventions::MESSAGING_DESTINATION_NAME, Eventboss::Sentry::ServerMiddleware::QUEUES_WITHOUT_ENV[queue_name]) + span.set_data(::Sentry::Span::DataConventions::MESSAGING_DESTINATION_NAME, ::Eventboss::Sentry::Context.queue_name_for_sentry(queue_name)) - message = yield # Executes the publisher_action lambda + message = yield span.set_data(::Sentry::Span::DataConventions::MESSAGING_MESSAGE_ID, message.message_id) message @@ -55,23 +50,8 @@ def json_payload(payload) payload.is_a?(String) ? payload : payload.to_json end - # Constructs SNS message attributes for Sentry trace propagation. - def build_sentry_message_attributes - attributes = ::Sentry.get_trace_propagation_headers - .slice('sentry-trace', 'baggage') - .transform_values do |header_value| - { string_value: header_value, data_type: 'String' } - end - - user = ::Sentry.get_current_scope&.user - if user && !user.empty? - attributes['sentry_user'] = { - string_value: user.to_json, - data_type: 'String' - } - end - - attributes + def build_sns_message_attributes + ::Eventboss::Sentry::Context.build_sns_message_attributes end end end \ No newline at end of file diff --git a/lib/eventboss/sentry/context.rb b/lib/eventboss/sentry/context.rb new file mode 100644 index 0000000..05bc9bc --- /dev/null +++ b/lib/eventboss/sentry/context.rb @@ -0,0 +1,35 @@ +module Eventboss + module Sentry + class Context + # since sentry has env selector, we can remove it from queue names + QUEUES_WITHOUT_ENV = Hash.new do |hash, key| + hash[key] = key + .gsub(/-#{Eventboss.env}-deadletter$/, '-ENV-deadletter') + .gsub(/-#{Eventboss.env}$/, '-ENV') + end + + def self.queue_name_for_sentry(queue_name) + QUEUES_WITHOUT_ENV[queue_name] + end + + # Constructs SNS message attributes for Sentry trace propagation. + def self.build_sns_message_attributes + attributes = ::Sentry.get_trace_propagation_headers + .slice('sentry-trace', 'baggage') + .transform_values do |header_value| + { string_value: header_value, data_type: 'String' } + end + + user = ::Sentry.get_current_scope&.user + if user && !user.empty? + attributes['sentry_user'] = { + string_value: user.to_json, + data_type: 'String' + } + end + + attributes + end + end + end +end \ No newline at end of file diff --git a/lib/eventboss/sentry/integration.rb b/lib/eventboss/sentry/integration.rb index 5be946f..3401d54 100644 --- a/lib/eventboss/sentry/integration.rb +++ b/lib/eventboss/sentry/integration.rb @@ -1,6 +1,7 @@ require 'sentry-ruby' require 'sentry/integrable' require_relative 'error_handler' +require_relative 'context' require_relative 'server_middleware' module Eventboss diff --git a/lib/eventboss/sentry/server_middleware.rb b/lib/eventboss/sentry/server_middleware.rb index e33e34b..003d66a 100644 --- a/lib/eventboss/sentry/server_middleware.rb +++ b/lib/eventboss/sentry/server_middleware.rb @@ -4,13 +4,6 @@ class ServerMiddleware < Eventboss::Middleware::Base OP_NAME = 'queue.process' SPAN_ORIGIN = 'auto.queue.eventboss' - # since sentry has env selector, we can remove it from queue names - QUEUES_WITHOUT_ENV = Hash.new do |hash, key| - hash[key] = key - .gsub(/-#{Eventboss.env}-deadletter$/, '-ENV-deadletter') - .gsub(/-#{Eventboss.env}$/, '-ENV') - end - def call(work) return yield unless ::Sentry.initialized? @@ -83,7 +76,7 @@ def extract_transaction_name(work) end def extract_queue_name(work) - QUEUES_WITHOUT_ENV[work.queue.name] + ::Eventboss::Sentry::Context.queue_name_for_sentry(work.queue.name) end def extract_latency(message)