eventboss 1.9.4 → 1.9.6

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: f7539971f5531148328e2fb1c5f49f356c72bd011ac041c5bb4be7ae60042c9c
4
- data.tar.gz: 61bcde73bc9d682981548d3d3ca461fca6299a76a5eb257a30bc1755d2769453
3
+ metadata.gz: 8cfeed8d922fa6e1c2c54dc1a102cac91cf9aab04eaa09a47cf05f4c54ff494b
4
+ data.tar.gz: 80c0347ab204fb98721dd119725a74de48d0d5e8b0273690cb792d9f6b14a9ea
5
5
  SHA512:
6
- metadata.gz: d977ac2326358fcb9aa49167046c95e1e65dc31c9f23cf3af091b35c04b25dd857b8f6c1c7c4c43d70ea8ceb0311d6d7e24e8e7017191e4714648b7ae26da43a
7
- data.tar.gz: 5b3a6450ad0ae267fd3222e818a85836bbec8bc3825b7a45fe8d20f4968032877102f799958b14c2a2e2c0e6401457a07b76ed0d348dea898e582b18b63a3c87
6
+ metadata.gz: 2dcce6ecb9afd37faa61e31f992e69abebfa68ed31e3c794b9854bfa978dfdae29da330ace493c4b995dac1359941ad9369bd647e61546c71ace9a3fead74dc3
7
+ data.tar.gz: e6e56a441f86bd989a2aa6b6640ded041dfc1d7380b79364718138f34bc3af14677f687cef103e76f57053a989d462cf40dec8129f985796f39eaffff21946a9
@@ -0,0 +1,51 @@
1
+ name: Publish Gem
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ jobs:
7
+ publish:
8
+ runs-on: ubuntu-latest
9
+ permissions:
10
+ contents: read
11
+ packages: write
12
+ steps:
13
+ - name: Check out repository
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: ruby
20
+ # runs 'bundle install' and caches installed gems automatically
21
+ bundler-cache: true
22
+
23
+ - name: "Publish gem eventboss"
24
+ shell: bash
25
+ env:
26
+ GEM_HOST_API_KEY: ${{ secrets.EVENTBOSS_RUBYGEMS_TOKEN }}
27
+
28
+ run: |
29
+ set -euo pipefail
30
+ # make this path safe so git commands work on a root-owned mount
31
+ git config --global --add safe.directory /usr/src/app
32
+
33
+ echo "📦 Building & publishing eventboss"
34
+ # Build the gem
35
+ gem build eventboss.gemspec
36
+
37
+ set +e
38
+ push_output=$(gem push *.gem 2>&1)
39
+ push_exit_code=$?
40
+ set -e
41
+
42
+ if [[ $push_exit_code -eq 0 ]]; then
43
+ echo "✅ Successfully published eventboss!"
44
+ elif grep -q "already been pushed" <<< "$push_output"; then
45
+ echo "::warning title=Gem already pushed::Gem eventboss version already published. Skipping this step."
46
+ echo "::notice title=Skipped publishing::Gem eventboss version already exists."
47
+ exit 0
48
+ else
49
+ echo "::error title=Gem Push Failed::$push_output"
50
+ exit 1
51
+ fi
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  .ruby-version
10
+ .idea/
data/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ 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.6]
8
+
9
+ ### Added
10
+ - Enhanced Sentry integration
11
+
12
+ ### Deprecated
13
+ - `Eventboss::ErrorHandlers::Sentry` is now deprecated in favor of `Eventboss::Sentry::ErrorHandler`
14
+
7
15
  ## [1.9.2] - 2025-01-21
8
16
 
9
17
  - Fix typo in instance var during shut down
data/README.md CHANGED
@@ -108,6 +108,12 @@ Use fixed account ID for localstack setup:
108
108
  EVENTBUS_ACCOUNT_ID=000000000000
109
109
  ```
110
110
 
111
+ ### AWS Credentials Validation
112
+
113
+ When running in container environments with IAM roles (indicated by the presence of `AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE`, which is set automatically by AWS when IAM role is used), Eventboss validates that the AWS client is using the correct credentials provider (`Aws::ECSCredentials`). If the wrong credentials provider is detected, Eventboss will log an error and exit with code 1 to prevent running with invalid credentials.
114
+
115
+ This feature is particularly useful in Kubernetes environments where pods should be using ECS credentials for IAM role authentication.
116
+
111
117
  Be aware that `eventbus:deadletter:reload` rake task won't load your configuration if you are not using ENVs
112
118
  in non Rails app, although to make it work you can extend your `Rakefile` with:
113
119
 
@@ -151,6 +157,17 @@ Eventboss.configure do |config|
151
157
  end
152
158
  ```
153
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
+
154
171
  ### Middlewares
155
172
 
156
173
  Server middlewares intercept the execution of your `Listeners`. You can use to extract and run common functions on every message received.
@@ -20,6 +20,7 @@ module Eventboss
20
20
  :eventboss_account_id,
21
21
  :eventboss_use_default_credentials,
22
22
  :aws_access_key_id,
23
+ :aws_container_authorization_token_file,
23
24
  :aws_secret_access_key,
24
25
  :aws_session_token,
25
26
  :aws_sns_endpoint,
@@ -81,7 +82,7 @@ module Eventboss
81
82
 
82
83
  def credentials
83
84
  return Aws::Credentials.new(aws_access_key_id, aws_secret_access_key, aws_session_token) if development_mode?
84
-
85
+
85
86
  Aws::Credentials.new(
86
87
  aws_access_key_id,
87
88
  aws_secret_access_key
@@ -104,6 +105,10 @@ module Eventboss
104
105
  defined_or_default('eventboss_use_default_credentials') { ENV['EVENTBOSS_USE_DEFAULT_CREDENTIALS'] == 'true' }
105
106
  end
106
107
 
108
+ def aws_container_authorization_token_file
109
+ defined_or_default('aws_container_authorization_token_file') { ENV['AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE'] }
110
+ end
111
+
107
112
  def aws_access_key_id
108
113
  defined_or_default('aws_access_key_id') { ENV['AWS_ACCESS_KEY_ID'] }
109
114
  end
@@ -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))
41
+
42
+ message = yield
43
+
44
+ span.set_data(::Sentry::Span::DataConventions::MESSAGING_MESSAGE_ID, message.message_id)
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
@@ -25,6 +25,7 @@ module Eventboss
25
25
  Eventboss::DevelopmentMode.setup_infrastructure(queues) if config.development_mode?
26
26
 
27
27
  begin
28
+ validate_client!(client, config)
28
29
  launcher.start
29
30
  handle_signals(self_read, launcher)
30
31
  rescue Interrupt
@@ -35,6 +36,18 @@ module Eventboss
35
36
 
36
37
  private
37
38
 
39
+ def validate_client!(client, config)
40
+ provider = client.config.credentials.class
41
+
42
+ if config.aws_container_authorization_token_file && provider != Aws::ECSCredentials
43
+ logger.error('runner') do
44
+ "AWS client was initiated with wrong credentials provider: #{provider}. " \
45
+ "Expected: Aws::ECSCredentials. Shutting down."
46
+ end
47
+ exit 1
48
+ end
49
+ end
50
+
38
51
  def setup_signals(signals)
39
52
  self_read, self_write = IO.pipe
40
53
 
@@ -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.4"
2
+ VERSION = "1.9.6"
3
3
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eventboss
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.4
4
+ version: 1.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - AirHelp
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-06-25 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: aws-sdk-sqs
@@ -117,6 +116,7 @@ extensions: []
117
116
  extra_rdoc_files: []
118
117
  files:
119
118
  - ".github/workflows/bundler_audit.yml"
119
+ - ".github/workflows/publish_gem.yml"
120
120
  - ".github/workflows/rspec.yml"
121
121
  - ".gitignore"
122
122
  - ".rspec"
@@ -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
@@ -169,7 +174,6 @@ homepage: https://github.com/AirHelp/eventboss
169
174
  licenses:
170
175
  - MIT
171
176
  metadata: {}
172
- post_install_message:
173
177
  rdoc_options: []
174
178
  require_paths:
175
179
  - lib
@@ -184,8 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
184
188
  - !ruby/object:Gem::Version
185
189
  version: '0'
186
190
  requirements: []
187
- rubygems_version: 3.5.22
188
- signing_key:
191
+ rubygems_version: 3.6.9
189
192
  specification_version: 4
190
193
  summary: Eventboss Ruby Client.
191
194
  test_files: []