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 +4 -4
- data/.github/workflows/publish_gem.yml +51 -0
- data/.gitignore +1 -0
- data/CHANGELOG.md +8 -0
- data/README.md +17 -0
- data/lib/eventboss/configuration.rb +6 -1
- data/lib/eventboss/error_handlers/sentry.rb +8 -0
- data/lib/eventboss/long_poller.rb +3 -1
- data/lib/eventboss/publisher.rb +35 -6
- data/lib/eventboss/runner.rb +13 -0
- data/lib/eventboss/sentry/configure.rb +11 -0
- data/lib/eventboss/sentry/context.rb +35 -0
- data/lib/eventboss/sentry/error_handler.rb +15 -0
- data/lib/eventboss/sentry/integration.rb +15 -0
- data/lib/eventboss/sentry/server_middleware.rb +95 -0
- data/lib/eventboss/version.rb +1 -1
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8cfeed8d922fa6e1c2c54dc1a102cac91cf9aab04eaa09a47cf05f4c54ff494b
|
4
|
+
data.tar.gz: 80c0347ab204fb98721dd119725a74de48d0d5e8b0273690cb792d9f6b14a9ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
|
data/lib/eventboss/publisher.rb
CHANGED
@@ -10,19 +10,48 @@ module Eventboss
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def publish(payload)
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
data/lib/eventboss/runner.rb
CHANGED
@@ -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
|
data/lib/eventboss/version.rb
CHANGED
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
|
+
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:
|
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.
|
188
|
-
signing_key:
|
191
|
+
rubygems_version: 3.6.9
|
189
192
|
specification_version: 4
|
190
193
|
summary: Eventboss Ruby Client.
|
191
194
|
test_files: []
|