eventboss 1.9.5 → 1.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00edb1f9a676e16224c4914f8411c073bd9f29658d1b878b4b6bb5e4639d8126
4
- data.tar.gz: 42f8f5f0d71c515203e9601ec327717b026d43855cea1d0915901f8de38aafd7
3
+ metadata.gz: 32c271ea01136f0e14f98ae6163f543f58edd8d45dd7cc0aa30c7facfda6fb44
4
+ data.tar.gz: 7c45094a9e1e205f90759bcf3f11a261cafcef2b9e1e51e65c3948bad9b3ae50
5
5
  SHA512:
6
- metadata.gz: 83eb1f2faa512f4147e4a3b4f523b3a0d60f4429bd27932da4f811ab8c7a006457abc15b7f5ef4f7fea734dd4c736d6e35e5d598c292097020156ca94cde42ac
7
- data.tar.gz: e623aa96514dd1c4be6c97c64d24c4b3a5d0fe54740636437b02bbd31d821cae1fd607b28009b6fb6337819cdb0f992464860c39a5336dcd8ac650e1daaa3df5
6
+ metadata.gz: a2e07d2f3c12b4e3f5c5c5c319b9f6663c498b8e398e6da7e955fcec98372601d3f0365d60d9dd32f7ae891d79bc5c6d824fb574f1a85e6d710013106516f434
7
+ data.tar.gz: 12884bb40cfd6ee48b294e10c4b92ab2cb230ee32274dc3d271a4f348c9bbf174d7a72ed455f314034af8e9579ae97e9e88d67b44a82b47c526b4bfb7b2da52e
data/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.9.7]
8
+
9
+ - Fix undefined method 'set_data' for nil span in Sentry integration
10
+
11
+ ## [1.9.6]
12
+
13
+ ### Added
14
+ - Enhanced Sentry integration
15
+
16
+ ### Deprecated
17
+ - `Eventboss::ErrorHandlers::Sentry` is now deprecated in favor of `Eventboss::Sentry::ErrorHandler`
18
+
7
19
  ## [1.9.2] - 2025-01-21
8
20
 
9
21
  - Fix typo in instance var during shut down
data/README.md CHANGED
@@ -157,6 +157,17 @@ Eventboss.configure do |config|
157
157
  end
158
158
  ```
159
159
 
160
+ ### Sentry Integration
161
+
162
+ 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:
163
+
164
+ ```ruby
165
+ require 'eventboss/sentry/configure'
166
+ ```
167
+
168
+ 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.
169
+ ```
170
+
160
171
  ### Middlewares
161
172
 
162
173
  Server middlewares intercept the execution of your `Listeners`. You can use to extract and run common functions on every message received.
@@ -1,6 +1,14 @@
1
1
  module Eventboss
2
2
  module ErrorHandlers
3
3
  class Sentry
4
+ def initialize
5
+ warn "[DEPRECATED] Eventboss::ErrorHandlers::Sentry is deprecated. " \
6
+ "Use Eventboss::Sentry::ErrorHandler instead. " \
7
+ "For automatic configuration, require 'eventboss/sentry/configure'. " \
8
+ "This class will be removed in a future version."
9
+ super
10
+ end
11
+
4
12
  def call(exception, context = {})
5
13
  eventboss_context = { component: 'eventboss' }
6
14
  eventboss_context[:action] = context[:processor].class.to_s if context[:processor]
@@ -73,7 +73,9 @@ module Eventboss
73
73
  @client.receive_message(
74
74
  queue_url: queue.url,
75
75
  max_number_of_messages: 10,
76
- wait_time_seconds: TIME_WAIT
76
+ wait_time_seconds: TIME_WAIT,
77
+ attribute_names: ['SentTimestamp', 'ApproximateReceiveCount'],
78
+ message_attribute_names: ['sentry-trace', 'baggage', 'sentry_user']
77
79
  ).messages
78
80
  end
79
81
  end
@@ -10,19 +10,48 @@ module Eventboss
10
10
  end
11
11
 
12
12
  def publish(payload)
13
- topic_arn = Topic.build_arn(event_name: event_name, source_app: source)
14
- sns_client.publish(
15
- topic_arn: topic_arn,
16
- message: json_payload(payload)
17
- )
13
+ with_sentry_span do
14
+ sns_client.publish(**build_sns_params(payload))
15
+ end
18
16
  end
19
17
 
20
18
  private
21
19
 
22
20
  attr_reader :event_name, :sns_client, :configuration, :source
23
21
 
22
+ def sentry_enabled?
23
+ defined?(::Eventboss::Sentry::Integration) && ::Sentry.initialized?
24
+ end
25
+
26
+ def build_sns_params(payload)
27
+ {
28
+ topic_arn: Topic.build_arn(event_name: event_name, source_app: source),
29
+ message: json_payload(payload),
30
+ message_attributes: sentry_enabled? ? build_sns_message_attributes : {}
31
+ }
32
+ end
33
+
34
+ def with_sentry_span
35
+ return yield unless sentry_enabled?
36
+
37
+ queue_name = Queue.build_name(destination: source, event_name: event_name, env: Eventboss.env, source_app: source)
38
+
39
+ ::Sentry.with_child_span(op: 'queue.publish', description: "Eventboss push #{source}/#{event_name}") do |span|
40
+ span.set_data(::Sentry::Span::DataConventions::MESSAGING_DESTINATION_NAME, ::Eventboss::Sentry::Context.queue_name_for_sentry(queue_name)) if span
41
+
42
+ message = yield
43
+
44
+ span.set_data(::Sentry::Span::DataConventions::MESSAGING_MESSAGE_ID, message.message_id) if span
45
+ message
46
+ end
47
+ end
48
+
24
49
  def json_payload(payload)
25
50
  payload.is_a?(String) ? payload : payload.to_json
26
51
  end
52
+
53
+ def build_sns_message_attributes
54
+ ::Eventboss::Sentry::Context.build_sns_message_attributes
55
+ end
27
56
  end
28
- end
57
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'integration'
4
+
5
+ # Auto configure eventboss to use sentry
6
+
7
+ Eventboss.configure do |config|
8
+ config.server_middleware.add Eventboss::Sentry::ServerMiddleware
9
+ config.error_handlers << Eventboss::Sentry::ErrorHandler.new
10
+ end
11
+
@@ -0,0 +1,35 @@
1
+ module Eventboss
2
+ module Sentry
3
+ class Context
4
+ # since sentry has env selector, we can remove it from queue names
5
+ QUEUES_WITHOUT_ENV = Hash.new do |hash, key|
6
+ hash[key] = key
7
+ .gsub(/-#{Eventboss.env}-deadletter$/, '-ENV-deadletter')
8
+ .gsub(/-#{Eventboss.env}$/, '-ENV')
9
+ end
10
+
11
+ def self.queue_name_for_sentry(queue_name)
12
+ QUEUES_WITHOUT_ENV[queue_name]
13
+ end
14
+
15
+ # Constructs SNS message attributes for Sentry trace propagation.
16
+ def self.build_sns_message_attributes
17
+ attributes = ::Sentry.get_trace_propagation_headers
18
+ .slice('sentry-trace', 'baggage')
19
+ .transform_values do |header_value|
20
+ { string_value: header_value, data_type: 'String' }
21
+ end
22
+
23
+ user = ::Sentry.get_current_scope&.user
24
+ if user && !user.empty?
25
+ attributes['sentry_user'] = {
26
+ string_value: user.to_json,
27
+ data_type: 'String'
28
+ }
29
+ end
30
+
31
+ attributes
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ module Eventboss
2
+ module Sentry
3
+ class ErrorHandler
4
+ def call(exception, _context = {})
5
+ return unless ::Sentry.initialized?
6
+
7
+ Eventboss::Sentry::Integration.capture_exception(
8
+ exception,
9
+ contexts: { eventboss: { } },
10
+ hint: { background: false }
11
+ )
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require 'sentry-ruby'
2
+ require 'sentry/integrable'
3
+ require_relative 'error_handler'
4
+ require_relative 'context'
5
+ require_relative 'server_middleware'
6
+
7
+ module Eventboss
8
+ module Sentry
9
+ class Integration
10
+ extend ::Sentry::Integrable
11
+
12
+ register_integration name: "eventboss", version: Eventboss::VERSION
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,95 @@
1
+ module Eventboss
2
+ module Sentry
3
+ class ServerMiddleware < Eventboss::Middleware::Base
4
+ OP_NAME = 'queue.process'
5
+ SPAN_ORIGIN = 'auto.queue.eventboss'
6
+
7
+ def call(work)
8
+ return yield unless ::Sentry.initialized?
9
+
10
+ ::Sentry.clone_hub_to_current_thread
11
+ scope = ::Sentry.get_current_scope
12
+ scope.clear
13
+ if (user = extract_sentry_user(work))
14
+ scope.set_user(user)
15
+ end
16
+ scope.set_tags(queue: extract_queue_name(work), message_id: work.message.message_id)
17
+ scope.set_transaction_name(extract_transaction_name(work), source: :task)
18
+ transaction = start_transaction(scope, work)
19
+
20
+ if transaction
21
+ scope.set_span(transaction)
22
+ transaction.set_data(::Sentry::Span::DataConventions::MESSAGING_MESSAGE_ID, work.message.message_id)
23
+ transaction.set_data(::Sentry::Span::DataConventions::MESSAGING_DESTINATION_NAME, extract_queue_name(work))
24
+
25
+ if (latency = extract_latency(work.message))
26
+ transaction.set_data(::Sentry::Span::DataConventions::MESSAGING_MESSAGE_RECEIVE_LATENCY, latency)
27
+ end
28
+
29
+ if (retry_count = extract_receive_count(work.message))
30
+ transaction.set_data(::Sentry::Span::DataConventions::MESSAGING_MESSAGE_RETRY_COUNT, retry_count)
31
+ end
32
+ end
33
+
34
+ begin
35
+ yield
36
+ rescue StandardError
37
+ finish_transaction(transaction, 500)
38
+ raise
39
+ end
40
+
41
+ finish_transaction(transaction, 200)
42
+ end
43
+
44
+ def start_transaction(scope, work)
45
+ options = {
46
+ name: scope.transaction_name,
47
+ source: scope.transaction_source,
48
+ op: OP_NAME,
49
+ origin: SPAN_ORIGIN
50
+ }
51
+
52
+ env = {
53
+ 'sentry-trace' => work.message.message_attributes['sentry-trace']&.string_value,
54
+ 'baggage' => work.message.message_attributes['baggage']&.string_value
55
+ }
56
+
57
+ transaction = ::Sentry.continue_trace(env, **options)
58
+ ::Sentry.start_transaction(transaction: transaction, **options)
59
+ end
60
+
61
+ def finish_transaction(transaction, status)
62
+ return unless transaction
63
+
64
+ transaction.set_http_status(status)
65
+ transaction.finish
66
+ end
67
+
68
+ def extract_sentry_user(work)
69
+ if (value = work.message.message_attributes["sentry_user"]&.string_value)
70
+ JSON.parse(value)
71
+ end
72
+ end
73
+
74
+ def extract_transaction_name(work)
75
+ "Eventboss/#{work.listener.to_s}"
76
+ end
77
+
78
+ def extract_queue_name(work)
79
+ ::Eventboss::Sentry::Context.queue_name_for_sentry(work.queue.name)
80
+ end
81
+
82
+ def extract_latency(message)
83
+ if sent_timestamp = message.attributes.fetch('SentTimestamp', nil)
84
+ Time.now - Time.at(sent_timestamp.to_i / 1000.0)
85
+ end
86
+ end
87
+
88
+ def extract_receive_count(message)
89
+ if receive_count = message.attributes.fetch('ApproximateReceiveCount', nil)
90
+ receive_count.to_i - 1
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,3 +1,3 @@
1
1
  module Eventboss
2
- VERSION = "1.9.5"
2
+ VERSION = "1.9.7"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eventboss
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.5
4
+ version: 1.9.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - AirHelp
@@ -157,6 +157,11 @@ files:
157
157
  - lib/eventboss/safe_thread.rb
158
158
  - lib/eventboss/scripts.rb
159
159
  - lib/eventboss/sender.rb
160
+ - lib/eventboss/sentry/configure.rb
161
+ - lib/eventboss/sentry/context.rb
162
+ - lib/eventboss/sentry/error_handler.rb
163
+ - lib/eventboss/sentry/integration.rb
164
+ - lib/eventboss/sentry/server_middleware.rb
160
165
  - lib/eventboss/sns_client.rb
161
166
  - lib/eventboss/topic.rb
162
167
  - lib/eventboss/unit_of_work.rb