legion-transport 1.4.21 → 1.4.23
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/CHANGELOG.md +24 -0
- data/README.md +26 -1
- data/lib/legion/transport/errors.rb +2 -0
- data/lib/legion/transport/helper.rb +0 -4
- data/lib/legion/transport/kafka/consumer.rb +3 -3
- data/lib/legion/transport/kafka/incoming_message.rb +1 -1
- data/lib/legion/transport/kafka/producer.rb +3 -2
- data/lib/legion/transport/kafka.rb +3 -3
- data/lib/legion/transport/message.rb +129 -20
- data/lib/legion/transport/routes.rb +16 -10
- data/lib/legion/transport/settings.rb +2 -2
- data/lib/legion/transport/spool.rb +5 -5
- data/lib/legion/transport/tenant_quota.rb +0 -2
- data/lib/legion/transport/tenant_topology.rb +0 -2
- data/lib/legion/transport/version.rb +1 -1
- data/lib/legion/transport.rb +1 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 264c545a4a225961456f42d8ca076d8cfe5a90967c4c80e44ea9aa14aaaee7df
|
|
4
|
+
data.tar.gz: 74d4e81af0e1da63558f6ee32988a3affd016a9bc13902ce6c28e4614249a70f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9611220c8d8b6395746ebefecbcd9b7558fe6dcb917c22b29c9bf17e9fc1dcc79fa7215e083f28c201b3b2f1ae746d5d3f2c8ae9ba8798f11e014edb83fe9f24
|
|
7
|
+
data.tar.gz: 307cc9c69ce19f240cb65272c6d0bb37030bd077c1ab511353ffb64fc7748480c927d4de4a714d16b1fe481878e7d1e0eca7df41eafde82eec80c64a4297668c
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
### Changed
|
|
6
|
+
- Routes module now uses `extend Legion::Logging::Helper` with `log.*` and `handle_exception` instead of direct `Legion::Logging` calls
|
|
7
|
+
|
|
8
|
+
### Removed
|
|
9
|
+
- Unnecessary `defined?(Legion::Logging)` guards in routes.rb — legion-logging is a hard gemspec dependency
|
|
10
|
+
- Unnecessary `defined?(Legion::Settings)` and `Legion.const_defined?('Settings')` guards in transport.rb, settings.rb, helper.rb, tenant_quota.rb, and tenant_topology.rb — legion-settings is a hard gemspec dependency
|
|
11
|
+
|
|
12
|
+
## [1.4.23] - 2026-05-05
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- `Message#publish` now supports live-request reliability options: `mandatory: true`, `publisher_confirm: true`, `publish_confirm_timeout_ms:`, `spool: false`, and structured publish results with `:accepted`, `:unroutable`, `:nacked`, `:confirm_timeout`, `:spooled`, or `:failed` status.
|
|
16
|
+
- Structured publish result hash: `{ status:, accepted:, exchange:, routing_key:, message_id:, correlation_id:, return_reply_code:, return_reply_text: }` returned when any reliability option is active.
|
|
17
|
+
- `reply_to:` field included in AMQP envelope options for RPC-style request/reply patterns.
|
|
18
|
+
- `routing_key:` override in publish options allows callers to redirect a message at publish time without constructing a new message instance.
|
|
19
|
+
- `persistent:` can now be overridden per-publish via options (previously only set at message construction).
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- `Message#publish` signature changed from `publish(options = @options)` to `publish(options = nil)` — passed options now **merge** with default `@options` instead of replacing them outright. Existing callers passing no arguments are unaffected.
|
|
23
|
+
|
|
24
|
+
## [1.4.22] - 2026-04-24
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- `x-legion-identity-trust` header on AMQP messages alongside existing `x-legion-identity-canonical-name`
|
|
28
|
+
|
|
5
29
|
## [1.4.21] - 2026-04-17
|
|
6
30
|
|
|
7
31
|
### Fixed
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Legion::Transport is the Ruby gem responsible for connecting LegionIO to its FIFO queue system (RabbitMQ over AMQP 0.9.1). It provides thread-safe connection management, exchange/queue abstractions, message publishing with optional encryption, and consumer wrappers.
|
|
4
4
|
|
|
5
|
-
**Version**: 1.4.
|
|
5
|
+
**Version**: 1.4.23
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -13,6 +13,7 @@ Legion::Transport is the Ruby gem responsible for connecting LegionIO to its FIF
|
|
|
13
13
|
- Dynamic credential retrieval from HashiCorp Vault
|
|
14
14
|
- Auto-recovery on connection loss (configurable attempts, 5s shutdown timeout)
|
|
15
15
|
- Dead letter exchange support
|
|
16
|
+
- Reliable publish with publisher confirms, mandatory routing, and structured result reporting
|
|
16
17
|
- Spool buffer for disk-backed message persistence when RabbitMQ is unavailable
|
|
17
18
|
- `InProcess` adapter for lite mode (`LEGION_MODE=lite`): no RabbitMQ required, uses in-memory pub/sub
|
|
18
19
|
- `Helper` mixin for LEX extensions: `transport_path`, `transport_class`, `messages`, `queues`, `exchanges`
|
|
@@ -66,6 +67,30 @@ Legion::Transport::Messages::Task.new(
|
|
|
66
67
|
).publish
|
|
67
68
|
```
|
|
68
69
|
|
|
70
|
+
### Reliable Publish
|
|
71
|
+
|
|
72
|
+
Pass reliability options to `#publish` to get structured feedback on message delivery:
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
msg = Legion::Transport::Messages::Task.new(
|
|
76
|
+
function: 'process_order',
|
|
77
|
+
routing_key: 'orders.process',
|
|
78
|
+
correlation_id: SecureRandom.uuid
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
result = msg.publish(
|
|
82
|
+
mandatory: true, # broker returns message if unroutable
|
|
83
|
+
publisher_confirm: true, # wait for broker acknowledgment
|
|
84
|
+
publish_confirm_timeout_ms: 500, # confirm wait timeout
|
|
85
|
+
spool: false # disable disk spool on failure (fail fast)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
result[:status] # => :accepted, :unroutable, :nacked, :confirm_timeout, :spooled, or :failed
|
|
89
|
+
result[:accepted] # => true/false
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Options merge with the message's defaults — you can override `routing_key:` or `persistent:` per-publish without constructing a new message.
|
|
93
|
+
|
|
69
94
|
### Creating a Queue
|
|
70
95
|
|
|
71
96
|
```ruby
|
|
@@ -11,8 +11,6 @@ module Legion
|
|
|
11
11
|
# Override in your LEX to set a custom default message TTL for the extension.
|
|
12
12
|
# Resolution chain: per-call :ttl option -> LEX override -> Settings -> nil (no expiration)
|
|
13
13
|
def transport_default_ttl
|
|
14
|
-
return nil unless defined?(Legion::Settings)
|
|
15
|
-
|
|
16
14
|
Legion::Settings.dig(:transport, :messages, :ttl)
|
|
17
15
|
rescue StandardError => e
|
|
18
16
|
handle_exception(e, level: :warn, handled: true, operation: :transport_default_ttl)
|
|
@@ -58,8 +56,6 @@ module Legion
|
|
|
58
56
|
# --- Status ---
|
|
59
57
|
|
|
60
58
|
def transport_connected?
|
|
61
|
-
return false unless defined?(Legion::Settings)
|
|
62
|
-
|
|
63
59
|
!!Legion::Settings.dig(:transport, :connected)
|
|
64
60
|
rescue StandardError => e
|
|
65
61
|
handle_exception(e, level: :warn, handled: true, operation: :transport_connected)
|
|
@@ -43,8 +43,8 @@ module Legion
|
|
|
43
43
|
commit_if_needed(consumer, count)
|
|
44
44
|
break if max_messages && count >= max_messages
|
|
45
45
|
end
|
|
46
|
-
rescue StopIteration
|
|
47
|
-
|
|
46
|
+
rescue StopIteration => e
|
|
47
|
+
handle_exception(e, level: :warn, handled: true, operation: 'transport.kafka.consumer.loop_exit')
|
|
48
48
|
ensure
|
|
49
49
|
consumer.close
|
|
50
50
|
end
|
|
@@ -178,7 +178,7 @@ module Legion
|
|
|
178
178
|
result = consumer.offsets_for_times(offsets_for_times)
|
|
179
179
|
result.to_h[topic]&.first&.last || 0
|
|
180
180
|
rescue StandardError => e
|
|
181
|
-
handle_exception(e, level: :
|
|
181
|
+
handle_exception(e, level: :warn, handled: true, operation: 'transport.kafka.consumer.resolve_offset',
|
|
182
182
|
topic: topic)
|
|
183
183
|
0
|
|
184
184
|
end
|
|
@@ -32,7 +32,7 @@ module Legion
|
|
|
32
32
|
|
|
33
33
|
Legion::JSON.parse(@payload)
|
|
34
34
|
rescue StandardError => e
|
|
35
|
-
handle_exception(e, level: :
|
|
35
|
+
handle_exception(e, level: :warn, handled: true, operation: 'transport.kafka.incoming_message.decoded_payload',
|
|
36
36
|
topic: topic, offset: offset)
|
|
37
37
|
@payload
|
|
38
38
|
end
|
|
@@ -29,7 +29,8 @@ module Legion
|
|
|
29
29
|
report = delivery_handle.wait(max_wait_timeout: delivery_timeout)
|
|
30
30
|
log_publish(topic, key, report)
|
|
31
31
|
{ topic: report.topic_name, partition: report.partition, offset: report.offset }
|
|
32
|
-
rescue Legion::Transport::Kafka::PublishError
|
|
32
|
+
rescue Legion::Transport::Kafka::PublishError => e
|
|
33
|
+
handle_exception(e, level: :error, handled: false, operation: 'transport.kafka.producer.publish')
|
|
33
34
|
raise
|
|
34
35
|
rescue StandardError => e
|
|
35
36
|
handle_exception(e, level: :error, handled: false, operation: 'transport.kafka.producer.publish',
|
|
@@ -124,7 +125,7 @@ module Legion
|
|
|
124
125
|
|
|
125
126
|
Legion::JSON.dump(payload)
|
|
126
127
|
rescue StandardError => e
|
|
127
|
-
handle_exception(e, level: :
|
|
128
|
+
handle_exception(e, level: :warn, handled: true, operation: 'transport.kafka.producer.encode')
|
|
128
129
|
payload.to_s
|
|
129
130
|
end
|
|
130
131
|
|
|
@@ -40,7 +40,7 @@ module Legion
|
|
|
40
40
|
require_rdkafka
|
|
41
41
|
true
|
|
42
42
|
rescue Legion::Transport::Kafka::UnavailableError => e
|
|
43
|
-
handle_exception(e, level: :
|
|
43
|
+
handle_exception(e, level: :warn, handled: true, operation: 'transport.kafka.enabled')
|
|
44
44
|
false
|
|
45
45
|
end
|
|
46
46
|
|
|
@@ -122,7 +122,7 @@ module Legion
|
|
|
122
122
|
def kafka_settings
|
|
123
123
|
Legion::Settings[:transport][:kafka]
|
|
124
124
|
rescue StandardError => e
|
|
125
|
-
handle_exception(e, level: :
|
|
125
|
+
handle_exception(e, level: :warn, handled: true, operation: 'transport.kafka.settings')
|
|
126
126
|
Legion::Transport::Kafka::DEFAULTS
|
|
127
127
|
end
|
|
128
128
|
|
|
@@ -143,7 +143,7 @@ module Legion
|
|
|
143
143
|
def require_rdkafka
|
|
144
144
|
require 'rdkafka'
|
|
145
145
|
rescue LoadError => e
|
|
146
|
-
handle_exception(e, level: :
|
|
146
|
+
handle_exception(e, level: :warn, handled: true, operation: 'transport.kafka.require_rdkafka')
|
|
147
147
|
raise Legion::Transport::Kafka::UnavailableError,
|
|
148
148
|
'rdkafka gem is required for Kafka support — add gem "rdkafka" to your Gemfile'
|
|
149
149
|
end
|
|
@@ -23,9 +23,10 @@ module Legion
|
|
|
23
23
|
1_048_576
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def publish(options =
|
|
26
|
+
def publish(options = nil)
|
|
27
27
|
raise unless @valid
|
|
28
28
|
|
|
29
|
+
publish_options = options ? @options.merge(options) : @options
|
|
29
30
|
validate_payload_size
|
|
30
31
|
ex_class = exchange
|
|
31
32
|
exchange_dest = if ex_class.respond_to?(:cached_instance)
|
|
@@ -35,25 +36,132 @@ module Legion
|
|
|
35
36
|
else
|
|
36
37
|
ex_class
|
|
37
38
|
end
|
|
39
|
+
return_state = {}
|
|
40
|
+
install_return_listener(exchange_dest, publish_options, return_state)
|
|
41
|
+
prepare_publisher_confirms(exchange_dest, publish_options)
|
|
38
42
|
exchange_dest.publish(encode_message,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
expiration: options[:expiration] || expiration,
|
|
45
|
-
headers: headers,
|
|
46
|
-
persistent: persistent,
|
|
47
|
-
message_id: message_id,
|
|
48
|
-
correlation_id: correlation_id,
|
|
49
|
-
app_id: app_id,
|
|
50
|
-
timestamp: timestamp)
|
|
51
|
-
ex_name = exchange_dest.respond_to?(:name) ? exchange_dest.name : exchange_dest.to_s
|
|
52
|
-
log.debug "Published to exchange=#{ex_name} routing_key=#{routing_key || ''} class=#{self.class.name}"
|
|
43
|
+
**publish_envelope_options(publish_options))
|
|
44
|
+
result = publish_result(exchange_dest, publish_options, return_state)
|
|
45
|
+
return result if return_publish_result?(publish_options)
|
|
46
|
+
|
|
47
|
+
nil
|
|
53
48
|
rescue Bunny::ConnectionClosedError, Bunny::ChannelAlreadyClosed, Bunny::ChannelError,
|
|
54
49
|
Bunny::NetworkErrorWrapper, IOError, Timeout::Error => e
|
|
55
|
-
handle_exception(e, level: :warn, handled: true, operation: 'transport.message.publish',
|
|
56
|
-
|
|
50
|
+
handle_exception(e, level: :warn, handled: true, operation: 'transport.message.publish',
|
|
51
|
+
spooled: spool_enabled?(publish_options))
|
|
52
|
+
spool_message(e, publish_options) if spool_enabled?(publish_options)
|
|
53
|
+
publish_failure_result(spool_enabled?(publish_options) ? :spooled : :failed, e, publish_options)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def publish_envelope_options(options)
|
|
57
|
+
{
|
|
58
|
+
routing_key: options[:routing_key] || routing_key || '',
|
|
59
|
+
content_type: options[:content_type] || content_type,
|
|
60
|
+
content_encoding: options[:content_encoding] || content_encoding,
|
|
61
|
+
type: options[:type] || type,
|
|
62
|
+
priority: options[:priority] || priority,
|
|
63
|
+
expiration: options[:expiration] || expiration,
|
|
64
|
+
headers: headers,
|
|
65
|
+
persistent: options.key?(:persistent) ? options[:persistent] : persistent,
|
|
66
|
+
message_id: message_id,
|
|
67
|
+
correlation_id: correlation_id,
|
|
68
|
+
reply_to: reply_to,
|
|
69
|
+
app_id: app_id,
|
|
70
|
+
timestamp: timestamp
|
|
71
|
+
}.tap do |envelope|
|
|
72
|
+
envelope[:mandatory] = true if options[:mandatory] == true
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def publish_result(exchange_dest, options, return_state)
|
|
77
|
+
confirmed_status = confirm_publish(exchange_dest, options)
|
|
78
|
+
status = return_state[:returned] ? :unroutable : confirmed_status
|
|
79
|
+
ex_name = exchange_dest.respond_to?(:name) ? exchange_dest.name : exchange_dest.to_s
|
|
80
|
+
log.debug "Published to exchange=#{ex_name} routing_key=#{options[:routing_key] || routing_key || ''} class=#{self.class.name}"
|
|
81
|
+
{
|
|
82
|
+
status: status,
|
|
83
|
+
accepted: status == :accepted,
|
|
84
|
+
exchange: ex_name,
|
|
85
|
+
routing_key: options[:routing_key] || routing_key || '',
|
|
86
|
+
message_id: message_id,
|
|
87
|
+
return_reply_code: return_state[:reply_code],
|
|
88
|
+
return_reply_text: return_state[:reply_text],
|
|
89
|
+
correlation_id: correlation_id
|
|
90
|
+
}.compact
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def prepare_publisher_confirms(exchange_dest, options)
|
|
94
|
+
return unless options[:publisher_confirm] == true
|
|
95
|
+
|
|
96
|
+
confirm_channel = publish_channel(exchange_dest)
|
|
97
|
+
return unless confirm_channel.respond_to?(:confirm_select)
|
|
98
|
+
|
|
99
|
+
confirm_channel.confirm_select
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def confirm_publish(exchange_dest, options)
|
|
103
|
+
return :accepted unless options[:publisher_confirm] == true
|
|
104
|
+
|
|
105
|
+
confirm_channel = publish_channel(exchange_dest)
|
|
106
|
+
return :accepted unless confirm_channel.respond_to?(:wait_for_confirms)
|
|
107
|
+
|
|
108
|
+
timeout = options[:publish_confirm_timeout_ms]
|
|
109
|
+
confirmed = if timeout
|
|
110
|
+
confirm_channel.wait_for_confirms(timeout.to_f / 1000.0)
|
|
111
|
+
else
|
|
112
|
+
confirm_channel.wait_for_confirms
|
|
113
|
+
end
|
|
114
|
+
confirmed == false ? :nacked : :accepted
|
|
115
|
+
rescue Timeout::Error => e
|
|
116
|
+
handle_exception(e, level: :warn, handled: true, operation: 'transport.message.confirm_publish')
|
|
117
|
+
:confirm_timeout
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def publish_channel(exchange_dest)
|
|
121
|
+
return exchange_dest.channel if exchange_dest.respond_to?(:channel)
|
|
122
|
+
|
|
123
|
+
channel
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def install_return_listener(exchange_dest, options, return_state)
|
|
127
|
+
return unless options[:mandatory] == true
|
|
128
|
+
|
|
129
|
+
return_channel = publish_channel(exchange_dest)
|
|
130
|
+
return unless return_channel.respond_to?(:on_return)
|
|
131
|
+
|
|
132
|
+
expected_correlation_id = correlation_id
|
|
133
|
+
expected_message_id = message_id
|
|
134
|
+
return_channel.on_return do |return_info, properties, _content|
|
|
135
|
+
next if properties.respond_to?(:correlation_id) && properties.correlation_id &&
|
|
136
|
+
expected_correlation_id && properties.correlation_id != expected_correlation_id
|
|
137
|
+
next if properties.respond_to?(:message_id) && properties.message_id &&
|
|
138
|
+
expected_message_id && properties.message_id != expected_message_id
|
|
139
|
+
|
|
140
|
+
return_state[:returned] = true
|
|
141
|
+
return_state[:reply_code] = return_info.reply_code if return_info.respond_to?(:reply_code)
|
|
142
|
+
return_state[:reply_text] = return_info.reply_text if return_info.respond_to?(:reply_text)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def spool_enabled?(options)
|
|
147
|
+
options.fetch(:spool, true) != false
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def return_publish_result?(options)
|
|
151
|
+
options[:return_result] == true || options[:mandatory] == true || options[:publisher_confirm] == true ||
|
|
152
|
+
options[:spool] == false
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def publish_failure_result(status, error, options = @options)
|
|
156
|
+
{
|
|
157
|
+
status: status,
|
|
158
|
+
accepted: false,
|
|
159
|
+
error_class: error.class.name,
|
|
160
|
+
error: error.message,
|
|
161
|
+
routing_key: options[:routing_key] || routing_key || '',
|
|
162
|
+
message_id: message_id,
|
|
163
|
+
correlation_id: correlation_id
|
|
164
|
+
}
|
|
57
165
|
end
|
|
58
166
|
|
|
59
167
|
def app_id
|
|
@@ -220,6 +328,7 @@ module Legion
|
|
|
220
328
|
id = Legion::Identity::Process.identity_hash
|
|
221
329
|
{
|
|
222
330
|
'x-legion-identity-canonical-name' => id[:canonical_name].to_s,
|
|
331
|
+
'x-legion-identity-trust' => id[:trust].to_s,
|
|
223
332
|
'x-legion-identity-id' => id[:id].to_s,
|
|
224
333
|
'x-legion-identity-kind' => id[:kind].to_s,
|
|
225
334
|
'x-legion-identity-mode' => id[:mode].to_s,
|
|
@@ -265,18 +374,18 @@ module Legion
|
|
|
265
374
|
default_affinity
|
|
266
375
|
end
|
|
267
376
|
|
|
268
|
-
def spool_message(error)
|
|
377
|
+
def spool_message(error, options = @options)
|
|
269
378
|
return unless defined?(Legion::Transport::Spool)
|
|
270
379
|
|
|
271
380
|
Legion::Transport::Spool.write(
|
|
272
381
|
exchange: exchange_name_for_spool,
|
|
273
|
-
routing_key: routing_key || '',
|
|
382
|
+
routing_key: options[:routing_key] || routing_key || '',
|
|
274
383
|
payload: message,
|
|
275
384
|
headers: @options[:headers],
|
|
276
385
|
priority: priority,
|
|
277
386
|
message_id: message_id,
|
|
278
387
|
correlation_id: correlation_id,
|
|
279
|
-
persistent: persistent
|
|
388
|
+
persistent: options.key?(:persistent) ? options[:persistent] : persistent
|
|
280
389
|
)
|
|
281
390
|
log.info("Message spooled due to: #{error.message}")
|
|
282
391
|
rescue StandardError => e
|
|
@@ -7,9 +7,13 @@
|
|
|
7
7
|
# LegionIO/lib/legion/api/transport.rb is preserved for backward compatibility but guards
|
|
8
8
|
# its registration with defined?(Legion::Transport::Routes) so double-registration is avoided.
|
|
9
9
|
|
|
10
|
+
require 'legion/logging/helper'
|
|
11
|
+
|
|
10
12
|
module Legion
|
|
11
13
|
module Transport
|
|
12
14
|
module Routes
|
|
15
|
+
extend Legion::Logging::Helper
|
|
16
|
+
|
|
13
17
|
def self.registered(app)
|
|
14
18
|
register_helpers(app)
|
|
15
19
|
register_status(app)
|
|
@@ -18,6 +22,7 @@ module Legion
|
|
|
18
22
|
end
|
|
19
23
|
|
|
20
24
|
def self.register_helpers(app)
|
|
25
|
+
app.helpers Legion::Logging::Helper
|
|
21
26
|
register_json_helpers(app)
|
|
22
27
|
register_transport_helpers(app)
|
|
23
28
|
end
|
|
@@ -47,7 +52,8 @@ module Legion
|
|
|
47
52
|
|
|
48
53
|
begin
|
|
49
54
|
parsed = Legion::JSON.load(raw)
|
|
50
|
-
rescue StandardError
|
|
55
|
+
rescue StandardError => e
|
|
56
|
+
handle_exception(e, level: :warn, handled: true, operation: :parse_request_body)
|
|
51
57
|
halt 400, { 'Content-Type' => 'application/json' },
|
|
52
58
|
Legion::JSON.dump({ error: { code: 'invalid_json', message: 'request body is not valid JSON' } })
|
|
53
59
|
end
|
|
@@ -73,7 +79,7 @@ module Legion
|
|
|
73
79
|
.map { |klass| { name: klass.name } }
|
|
74
80
|
.sort_by { |h| h[:name].to_s }
|
|
75
81
|
rescue NameError => e
|
|
76
|
-
|
|
82
|
+
handle_exception(e, level: :warn, handled: true, operation: :transport_subclasses)
|
|
77
83
|
[]
|
|
78
84
|
end
|
|
79
85
|
end
|
|
@@ -85,19 +91,19 @@ module Legion
|
|
|
85
91
|
connected = begin
|
|
86
92
|
Legion::Settings[:transport][:connected]
|
|
87
93
|
rescue StandardError => e
|
|
88
|
-
|
|
94
|
+
handle_exception(e, level: :warn, handled: true, operation: :transport_status_connected)
|
|
89
95
|
false
|
|
90
96
|
end
|
|
91
97
|
session_open = begin
|
|
92
98
|
Legion::Transport::Connection.session_open?
|
|
93
99
|
rescue StandardError => e
|
|
94
|
-
|
|
100
|
+
handle_exception(e, level: :warn, handled: true, operation: :transport_status_session_open)
|
|
95
101
|
false
|
|
96
102
|
end
|
|
97
103
|
channel_open = begin
|
|
98
104
|
Legion::Transport::Connection.channel_open?
|
|
99
105
|
rescue StandardError => e
|
|
100
|
-
|
|
106
|
+
handle_exception(e, level: :warn, handled: true, operation: :transport_status_channel_open)
|
|
101
107
|
false
|
|
102
108
|
end
|
|
103
109
|
connector = defined?(Legion::Transport::TYPE) ? Legion::Transport::TYPE.to_s : 'unknown'
|
|
@@ -121,14 +127,14 @@ module Legion
|
|
|
121
127
|
|
|
122
128
|
def self.register_publish(app)
|
|
123
129
|
app.post '/api/transport/publish' do
|
|
124
|
-
|
|
130
|
+
log.debug "API: POST /api/transport/publish params=#{params.keys}"
|
|
125
131
|
body = parse_request_body
|
|
126
132
|
unless body[:exchange]
|
|
127
|
-
|
|
133
|
+
log.warn 'API POST /api/transport/publish returned 422: exchange is required'
|
|
128
134
|
halt 422, json_error('missing_field', 'exchange is required', status_code: 422)
|
|
129
135
|
end
|
|
130
136
|
unless body[:routing_key]
|
|
131
|
-
|
|
137
|
+
log.warn 'API POST /api/transport/publish returned 422: routing_key is required'
|
|
132
138
|
halt 422, json_error('missing_field', 'routing_key is required', status_code: 422)
|
|
133
139
|
end
|
|
134
140
|
|
|
@@ -136,10 +142,10 @@ module Legion
|
|
|
136
142
|
exchange: body[:exchange], routing_key: body[:routing_key], **(body[:payload] || {})
|
|
137
143
|
)
|
|
138
144
|
message.publish
|
|
139
|
-
|
|
145
|
+
log.info "API: published message to exchange=#{body[:exchange]} routing_key=#{body[:routing_key]}"
|
|
140
146
|
json_response({ published: true, exchange: body[:exchange], routing_key: body[:routing_key] }, status_code: 201)
|
|
141
147
|
rescue StandardError => e
|
|
142
|
-
|
|
148
|
+
handle_exception(e, level: :error, handled: true, operation: :transport_publish)
|
|
143
149
|
json_error('publish_error', e.message, status_code: 500)
|
|
144
150
|
end
|
|
145
151
|
end
|
|
@@ -13,7 +13,7 @@ module Legion
|
|
|
13
13
|
host = ENV['transport.connection.host'] || '127.0.0.1'
|
|
14
14
|
port = (ENV['transport.connection.port'] || DEFAULT_AMQP_PORT).to_i
|
|
15
15
|
|
|
16
|
-
existing =
|
|
16
|
+
existing = Legion::Settings[:transport][:connection] || {}
|
|
17
17
|
extra_server = existing[:server]
|
|
18
18
|
extra_servers = existing[:servers] || []
|
|
19
19
|
extra_hosts = existing[:hosts] || []
|
|
@@ -145,7 +145,7 @@ module Legion
|
|
|
145
145
|
end
|
|
146
146
|
|
|
147
147
|
begin
|
|
148
|
-
Legion::Settings.merge_settings('transport', Legion::Transport::Settings.default)
|
|
148
|
+
Legion::Settings.merge_settings('transport', Legion::Transport::Settings.default)
|
|
149
149
|
rescue StandardError => e
|
|
150
150
|
Legion::Transport::Settings.handle_exception(e, level: :fatal, handled: true, operation: 'transport.settings.merge')
|
|
151
151
|
end
|
|
@@ -75,7 +75,7 @@ module Legion
|
|
|
75
75
|
stream_lines(file) { n += 1 }
|
|
76
76
|
n
|
|
77
77
|
rescue StandardError => e
|
|
78
|
-
handle_exception(e, level: :
|
|
78
|
+
handle_exception(e, level: :warn, handled: true, operation: 'transport.spool.count', file: file)
|
|
79
79
|
0
|
|
80
80
|
end
|
|
81
81
|
end
|
|
@@ -90,7 +90,7 @@ module Legion
|
|
|
90
90
|
File.delete(file)
|
|
91
91
|
log.info "Evicted stale spool file=#{file}"
|
|
92
92
|
rescue StandardError => e
|
|
93
|
-
handle_exception(e, level: :
|
|
93
|
+
handle_exception(e, level: :warn, handled: true, operation: 'transport.spool.evict_stale', file: file)
|
|
94
94
|
nil
|
|
95
95
|
end
|
|
96
96
|
end
|
|
@@ -138,7 +138,7 @@ module Legion
|
|
|
138
138
|
total = files.sum do |f|
|
|
139
139
|
File.size(f)
|
|
140
140
|
rescue StandardError => e
|
|
141
|
-
handle_exception(e, level: :
|
|
141
|
+
handle_exception(e, level: :warn, handled: true, operation: 'transport.spool.over_limits', file: f)
|
|
142
142
|
0
|
|
143
143
|
end
|
|
144
144
|
total >= @max_total_bytes
|
|
@@ -155,7 +155,7 @@ module Legion
|
|
|
155
155
|
File.delete(oldest)
|
|
156
156
|
log.info "Evicted oldest spool file=#{oldest}"
|
|
157
157
|
rescue StandardError => e
|
|
158
|
-
handle_exception(e, level: :
|
|
158
|
+
handle_exception(e, level: :warn, handled: true, operation: 'transport.spool.evict_oldest')
|
|
159
159
|
break
|
|
160
160
|
end
|
|
161
161
|
end
|
|
@@ -165,7 +165,7 @@ module Legion
|
|
|
165
165
|
files.sum do |f|
|
|
166
166
|
File.size(f)
|
|
167
167
|
rescue StandardError => e
|
|
168
|
-
handle_exception(e, level: :
|
|
168
|
+
handle_exception(e, level: :warn, handled: true, operation: 'transport.spool.total_bytes', file: f)
|
|
169
169
|
0
|
|
170
170
|
end
|
|
171
171
|
end
|
|
@@ -73,8 +73,6 @@ module Legion
|
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
def quota_settings(tenant_id)
|
|
76
|
-
return nil unless defined?(Legion::Settings)
|
|
77
|
-
|
|
78
76
|
Legion::Settings.dig(:transport, :tenant_topology, :quotas, tenant_id.to_sym)
|
|
79
77
|
rescue StandardError => e
|
|
80
78
|
handle_exception(e, level: :warn, handled: true, operation: :tenant_quota_settings, tenant_id: tenant_id)
|
|
@@ -46,8 +46,6 @@ module Legion
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
private_class_method def self.transport_settings
|
|
49
|
-
return {} unless defined?(Legion::Settings)
|
|
50
|
-
|
|
51
49
|
Legion::Settings[:transport] || {}
|
|
52
50
|
rescue StandardError => e
|
|
53
51
|
handle_exception(e, level: :warn, handled: true, operation: :tenant_topology_transport_settings)
|
data/lib/legion/transport.rb
CHANGED