legionio 1.6.18 → 1.6.20

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: 5796d0724836fec4f8426fa337d5c6de420fbdf81c94fa9d070ca2a1a6abd9c7
4
- data.tar.gz: bbaa73b5ee1d36c1c8b7cb9536d1c7e4144464cacfe68c1b4fee55b9bfea08df
3
+ metadata.gz: 9dd4d5c0f258dd9199195ac95f89dbcaaeccc3ccbf844924f463dfecffe6304c
4
+ data.tar.gz: 453721f4e03bf0c9ff3387575749241cb3bc9c8093b5c366d7b3434e823558be
5
5
  SHA512:
6
- metadata.gz: 2c1a641e20d067fddcf0d949c3d2f11bbbcca141783b20e581f361bb916821c07e3538037e00c21f7866a12399d3a2c3ce56ad040f8af5713ff2969ea4ee1463
7
- data.tar.gz: 568b3efc39291f9e2593b373eb3009db4d708fc7030b38237924d52acf2933c5a64e0aafb4e39265fe8997a7fd7a66aded647d969a4dcb3a8af1e3207afe0c29
6
+ metadata.gz: 440288ee7de2bf883920879b3658c1cdf1c2a27546e39be8641f7d92dc91956c0ed6042e3ab4400103de06be0adb1fbca4738d215d9e6e0b8f26c05a617d2a2c
7
+ data.tar.gz: cb62010a5b8062fbef95bf39efc642d02251897ae9447901bbafbf230a8ec4012c695ca12f87000c24374b0c073c4caa90a380118b12fa02759e8856ae9a4e0f
data/CHANGELOG.md CHANGED
@@ -2,8 +2,35 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.6.20] - 2026-03-27
6
+
7
+ ### Changed
8
+ - Bump `legion-logging` dependency to `>= 1.4.0` (required for `log_exception`, writer lambdas)
9
+
10
+ ### Fixed
11
+ - `subscription.rb` (both `on_delivery` and `subscribe` blocks): initialize `fn = nil` before `process_message` so the rescue interpolation never raises `NameError` if message processing fails before `fn` is assigned
12
+ - `Helpers::Logger#lex_name` removed to avoid overriding `Helpers::Base#lex_name` (underscore contract used by settings/routing); renamed to private `log_lex_name` used only within this module for gem name derivation
13
+ - `Helpers::Logger#handle_exception`: use `spec&.version&.to_s` so nil spec version produces `nil` rather than `""` in structured log output
14
+ - README: update version badge from `v1.6.18` to `v1.6.20`
15
+
16
+ ## [1.6.19] - 2026-03-27
17
+
18
+ ### Fixed
19
+ - `teardown_logging_transport`: rescue block in `setup_logging_transport` now calls `teardown_logging_transport` to clean up any partially-created `@log_session` on failure
20
+ - `teardown_logging_transport`: guard `open?` call with `respond_to?(:open?)` check to avoid `NoMethodError` on session objects that do not implement the method
21
+ - `service_logging_transport_spec`: early-return specs now assert `create_dedicated_session` was not called and `@log_session` remains nil, rather than the vacuous `respond_to(:call)` check
22
+ - `service_logging_transport_spec`: replaced vacuous `not_to eq(owner)` assertion with `have_received(:create_dedicated_session)` to verify the dedicated session was actually created
23
+
5
24
  ## [1.6.18] - 2026-03-27
6
25
 
26
+ ### Added
27
+ - `setup_logging_transport`: dedicated AMQP session for log and exception forwarding, replacing the previous `register_logging_hooks` approach; writer lambda wiring is gated by `Settings[:logging][:transport]` feature flags
28
+ - `teardown_logging_transport`: cleanly shuts down the dedicated logging AMQP session during the shutdown sequence
29
+
30
+ ### Changed
31
+ - Split `log.error(e.message); log.error(e.backtrace)` patterns replaced with `log.log_exception` across 14 files for structured, single-call exception logging
32
+ - `Extensions::Helpers::Logger#handle_exception` rewritten to use `log.log_exception` with full lex context
33
+
7
34
  ### Fixed
8
35
  - `legionio pipeline image analyze`: `call_llm` no longer passes unsupported `messages:` keyword to `Legion::LLM.chat`; now creates a chat object and sends multimodal content via `chat.ask`, returning a plain hash with `:content` and `:usage` keys
9
36
  - `legionio ai trace search/summarize`: both commands now call `setup_connection` before invoking `TraceSearch`, ensuring `Legion::LLM` is booted so `TraceSearch.generate_filter` can use structured LLM output instead of returning "no filter generated"; added `class_option :config_dir` and `class_option :verbose` to `TraceCommand`
data/README.md CHANGED
@@ -14,7 +14,7 @@ Schedule tasks, chain services into dependency graphs, run them concurrently via
14
14
  ╰──────────────────────────────────────╯
15
15
  ```
16
16
 
17
- **Ruby >= 3.4** | **v1.5.20** | **Apache-2.0** | [@Esity](https://github.com/Esity)
17
+ **Ruby >= 3.4** | **v1.6.20** | **Apache-2.0** | [@Esity](https://github.com/Esity)
18
18
 
19
19
  ---
20
20
 
data/legionio.gemspec CHANGED
@@ -56,7 +56,7 @@ Gem::Specification.new do |spec|
56
56
  spec.add_dependency 'legion-crypt', '>= 1.4.17'
57
57
  spec.add_dependency 'legion-data', '>= 1.6.7'
58
58
  spec.add_dependency 'legion-json', '>= 1.2.1'
59
- spec.add_dependency 'legion-logging', '>= 1.3.2'
59
+ spec.add_dependency 'legion-logging', '>= 1.4.0'
60
60
  spec.add_dependency 'legion-settings', '>= 1.3.19'
61
61
  spec.add_dependency 'legion-transport', '>= 1.4.4'
62
62
 
@@ -66,8 +66,7 @@ module Legion
66
66
 
67
67
  dispatch_hook(context, payload: payload, runner: runner, function: function)
68
68
  rescue StandardError => e
69
- Legion::Logging.error "API #{request.request_method} #{request.path_info}: #{e.class} — #{e.message}"
70
- Legion::Logging.error e.backtrace&.first(5)
69
+ Legion::Logging.log_exception(e, payload_summary: "API #{request.request_method} #{request.path_info}", component_type: :api)
71
70
  context.json_error('internal_error', e.message, status_code: 500)
72
71
  end
73
72
 
@@ -53,8 +53,7 @@ module Legion
53
53
  context.json_response({ task_id: result[:task_id], status: result[:status],
54
54
  result: result[:result] }.compact)
55
55
  rescue StandardError => e
56
- Legion::Logging.error "API POST /api/lex/#{request.path_info.sub(%r{^/api/lex/}, '')}: #{e.class} — #{e.message}"
57
- Legion::Logging.error e.backtrace&.first(5)
56
+ Legion::Logging.log_exception(e, payload_summary: "API POST /api/lex/#{request.path_info.sub(%r{^/api/lex/}, '')}", component_type: :api)
58
57
  context.json_error('internal_error', e.message, status_code: 500)
59
58
  end
60
59
 
data/lib/legion/api.rb CHANGED
@@ -95,8 +95,7 @@ module Legion
95
95
  error do
96
96
  content_type :json
97
97
  err = env['sinatra.error']
98
- Legion::Logging.error "API #{request.request_method} #{request.path_info} returned 500: #{err.class} — #{err.message}"
99
- Legion::Logging.error err.backtrace&.first(10)
98
+ Legion::Logging.log_exception(err, payload_summary: "API #{request.request_method} #{request.path_info} returned 500", component_type: :api)
100
99
  Legion::JSON.dump({
101
100
  error: { code: 'internal_error', message: err.message },
102
101
  meta: { timestamp: Time.now.utc.iso8601, node: Legion::Settings[:client][:name] }
data/lib/legion/events.rb CHANGED
@@ -31,8 +31,7 @@ module Legion
31
31
  listeners[event_name.to_s].each do |listener|
32
32
  listener.call(event)
33
33
  rescue StandardError => e
34
- Legion::Logging.warn "[Events] listener error on #{event_name}: #{e.message}"
35
- Legion::Logging.error e.backtrace&.first(5)
34
+ Legion::Logging.log_exception(e, payload_summary: "[Events] listener error on #{event_name}", component_type: :event)
36
35
  end
37
36
 
38
37
  # Also fire wildcard listeners
@@ -9,8 +9,7 @@ module Legion
9
9
  def runner
10
10
  Legion::Runner.run(runner_class: runner_class, function: function, check_subtask: check_subtask?, generate_task: generate_task?)
11
11
  rescue StandardError => e
12
- Legion::Logging.error e.message
13
- Legion::Logging.error e.backtrace
12
+ Legion::Logging.log_exception(e, component_type: :actor)
14
13
  end
15
14
 
16
15
  def manual
@@ -23,8 +22,7 @@ module Legion
23
22
  klass.send(func, **args)
24
23
  end
25
24
  rescue StandardError => e
26
- Legion::Logging.error e.message
27
- Legion::Logging.error e.backtrace
25
+ Legion::Logging.log_exception(e, component_type: :actor)
28
26
  end
29
27
 
30
28
  def function
@@ -16,15 +16,13 @@ module Legion
16
16
  begin
17
17
  skip_or_run { use_runner? ? runner : manual }
18
18
  rescue StandardError => e
19
- log.error "[Every] tick failed for #{self.class}: #{e.message}" if defined?(log)
20
- log.error e.backtrace if defined?(log)
19
+ log.log_exception(e, payload_summary: "[Every] tick failed for #{self.class}", component_type: :actor) if defined?(log)
21
20
  end
22
21
  end
23
22
 
24
23
  @timer.execute
25
24
  rescue StandardError => e
26
- log.error e.message
27
- log.error e.backtrace
25
+ log.log_exception(e, component_type: :actor)
28
26
  end
29
27
 
30
28
  def time
@@ -49,8 +47,7 @@ module Legion
49
47
 
50
48
  @timer.shutdown
51
49
  rescue StandardError => e
52
- log.error e.message
53
- log.error e.backtrace
50
+ log.log_exception(e, component_type: :actor)
54
51
  end
55
52
  end
56
53
  end
@@ -13,8 +13,7 @@ module Legion
13
13
  @loop = true
14
14
  async.run
15
15
  rescue StandardError => e
16
- Legion::Logging.error e
17
- Legion::Logging.error e.backtrace
16
+ Legion::Logging.log_exception(e, component_type: :actor)
18
17
  end
19
18
 
20
19
  def run
@@ -17,13 +17,11 @@ check_subtask: check_subtask? }}"
17
17
  @timer = Concurrent::TimerTask.new(execution_interval: time, run_now: run_now?) do
18
18
  skip_or_run { poll_cycle }
19
19
  rescue StandardError => e
20
- Legion::Logging.fatal e.message
21
- Legion::Logging.fatal e.backtrace
20
+ Legion::Logging.log_exception(e, level: :fatal, component_type: :actor)
22
21
  end
23
22
  @timer.execute
24
23
  rescue StandardError => e
25
- Legion::Logging.error e.message
26
- Legion::Logging.error e.backtrace
24
+ Legion::Logging.log_exception(e, component_type: :actor)
27
25
  end
28
26
 
29
27
  def poll_cycle
@@ -55,8 +53,7 @@ check_subtask: check_subtask? }}"
55
53
  log.debug("#{self.class} result: #{results}")
56
54
  results
57
55
  rescue StandardError => e
58
- Legion::Logging.fatal e.message
59
- Legion::Logging.fatal e.backtrace
56
+ Legion::Logging.log_exception(e, level: :fatal, component_type: :actor)
60
57
  end
61
58
 
62
59
  def cache_name
@@ -91,7 +88,7 @@ check_subtask: check_subtask? }}"
91
88
  Legion::Logging.debug 'Cancelling Legion Poller'
92
89
  @timer.shutdown
93
90
  rescue StandardError => e
94
- Legion::Logging.error e.message
91
+ Legion::Logging.log_exception(e, component_type: :actor)
95
92
  end
96
93
  end
97
94
  end
@@ -17,8 +17,7 @@ module Legion
17
17
  @queue = queue.new
18
18
  @queue.channel.prefetch(prefetch) if defined? prefetch
19
19
  rescue StandardError => e
20
- log.fatal e.message
21
- log.fatal e.backtrace
20
+ log.log_exception(e, level: :fatal, component_type: :actor)
22
21
  end
23
22
 
24
23
  def create_queue
@@ -48,12 +47,13 @@ module Legion
48
47
  true
49
48
  end
50
49
 
51
- def prepare # rubocop:disable Metrics/AbcSize
50
+ def prepare
52
51
  @queue = queue.new
53
52
  @queue.channel.prefetch(prefetch) if defined? prefetch
54
53
  consumer_tag = "#{Legion::Settings[:client][:name]}_#{lex_name}_#{runner_name}_#{SecureRandom.uuid}"
55
54
  @consumer = Bunny::Consumer.new(@queue.channel, @queue, consumer_tag, false, false)
56
55
  @consumer.on_delivery do |delivery_info, metadata, payload|
56
+ fn = nil
57
57
  message = process_message(payload, metadata, delivery_info)
58
58
  fn = find_function(message)
59
59
  log.debug "[Subscription] message received: #{lex_name}/#{fn}" if defined?(log)
@@ -76,14 +76,12 @@ module Legion
76
76
 
77
77
  cancel if Legion::Settings[:client][:shutting_down]
78
78
  rescue StandardError => e
79
- log.error "[Subscription] message processing failed: #{lex_name}/#{fn}: #{e.message}"
80
- log.error e.backtrace
79
+ log.log_exception(e, payload_summary: "[Subscription] message processing failed: #{lex_name}/#{fn}", component_type: :actor)
81
80
  @queue.reject(delivery_info.delivery_tag) if manual_ack
82
81
  end
83
82
  log.info "[Subscription] prepared: #{lex_name}/#{runner_name}"
84
83
  rescue StandardError => e
85
- log.fatal "Subscription#prepare failed: #{e.message}"
86
- log.fatal e.backtrace
84
+ log.log_exception(e, level: :fatal, payload_summary: 'Subscription#prepare failed', component_type: :actor)
87
85
  end
88
86
 
89
87
  def activate
@@ -159,6 +157,7 @@ module Legion
159
157
  metadata = rmq_message.last
160
158
  delivery_info = rmq_message.first
161
159
 
160
+ fn = nil
162
161
  message = process_message(payload, metadata, delivery_info)
163
162
  fn = find_function(message)
164
163
  log.debug "[Subscription] message received: #{lex_name}/#{fn}" if defined?(log)
@@ -185,8 +184,7 @@ module Legion
185
184
 
186
185
  cancel if Legion::Settings[:client][:shutting_down]
187
186
  rescue StandardError => e
188
- log.error "[Subscription] message processing failed: #{lex_name}/#{fn}: #{e.message}"
189
- log.error e.backtrace
187
+ log.log_exception(e, payload_summary: "[Subscription] message processing failed: #{lex_name}/#{fn}", component_type: :actor)
190
188
  log.warn "[Subscription] nacking message for #{lex_name}/#{fn}"
191
189
  @queue.reject(delivery_info.delivery_tag) if manual_ack
192
190
  end
@@ -220,9 +220,7 @@ module Legion
220
220
  lex_class.const_set(:Data, Module.new { extend Legion::Extensions::Data })
221
221
  end
222
222
  rescue StandardError => e
223
- Legion::Logging.error "[Core] auto_generate_data failed for #{name}: #{e.message}" if defined?(Legion::Logging)
224
- log.error e.message
225
- log.error e.backtrace
223
+ log.log_exception(e, payload_summary: "[Core] auto_generate_data failed for #{name}", component_type: :builder)
226
224
  end
227
225
  end
228
226
  end
@@ -7,9 +7,17 @@ module Legion
7
7
  include Legion::Logging::Helper
8
8
 
9
9
  def handle_exception(exception, task_id: nil, **opts)
10
- log.error exception.message + " for task_id: #{task_id} but was logged "
11
- log.error exception.backtrace[0..10]
12
- log.error opts
10
+ spec = gem_spec_for_lex
11
+ log.log_exception(exception,
12
+ lex: log_lex_name,
13
+ component_type: derive_component_type,
14
+ gem_name: lex_gem_name,
15
+ lex_version: spec&.version&.to_s,
16
+ gem_path: spec&.full_gem_path,
17
+ source_code_uri: spec&.metadata&.[]('source_code_uri'),
18
+ handled: true,
19
+ payload_summary: opts.empty? ? nil : opts,
20
+ task_id: task_id)
13
21
 
14
22
  unless task_id.nil?
15
23
  Legion::Transport::Messages::TaskLog.new(
@@ -25,6 +33,51 @@ module Legion
25
33
 
26
34
  raise Legion::Exception::HandledTask
27
35
  end
36
+
37
+ private
38
+
39
+ def derive_component_type
40
+ parts = respond_to?(:calling_class_array) ? calling_class_array : self.class.to_s.split('::')
41
+ match = parts.find { |p| Legion::Extensions::Helpers::Base::NAMESPACE_BOUNDARIES.include?(p) }
42
+ case match
43
+ when 'Runners' then :runner
44
+ when 'Actor', 'Actors' then :actor
45
+ when 'Transport' then :transport
46
+ when 'Helpers' then :helper
47
+ when 'Data' then :data
48
+ else :unknown
49
+ end
50
+ rescue StandardError
51
+ :unknown
52
+ end
53
+
54
+ def lex_gem_name
55
+ base_name = log_lex_name
56
+ return nil unless base_name
57
+
58
+ "lex-#{base_name}"
59
+ rescue StandardError
60
+ nil
61
+ end
62
+
63
+ def gem_spec_for_lex
64
+ name = lex_gem_name
65
+ return nil unless name
66
+
67
+ Gem::Specification.find_by_name(name)
68
+ rescue Gem::MissingSpecError
69
+ nil
70
+ end
71
+
72
+ def log_lex_name
73
+ if respond_to?(:segments)
74
+ segments.join('-')
75
+ else
76
+ derive_log_tag
77
+ end
78
+ rescue StandardError
79
+ nil
80
+ end
28
81
  end
29
82
  end
30
83
  end
@@ -16,8 +16,7 @@ module Legion
16
16
  return true if Legion::Data::Model::TaskLog.insert(task_id: task_id, function_id: function_id, entry: Legion::JSON.dump(payload))
17
17
  end
18
18
  rescue StandardError => e
19
- log.warn e.backtrace
20
- log.warn("generate_task_log failed, reverting to rmq message, e: #{e.message}")
19
+ log.log_exception(e, level: :warn, payload_summary: 'generate_task_log failed, reverting to rmq message', component_type: :helper)
21
20
  end
22
21
  Legion::Transport::Messages::TaskLog.new(task_id: task_id, runner_class: runner_class, function: function, entry: payload).publish
23
22
  end
@@ -41,8 +40,7 @@ module Legion
41
40
  end
42
41
  Legion::Transport::Messages::TaskUpdate.new(**update_hash).publish
43
42
  rescue StandardError => e
44
- log.fatal e.message
45
- log.fatal e.backtrace
43
+ log.log_exception(e, level: :fatal, component_type: :helper)
46
44
  raise e
47
45
  end
48
46
 
@@ -24,8 +24,7 @@ module Legion
24
24
  auto_create_dlx_queue
25
25
  log.info "[Transport] built exchanges=#{@exchanges.count} queues=#{@queues.count} for #{lex_name}"
26
26
  rescue StandardError => e
27
- log.error "[Transport] build failed for #{lex_name}: #{e.message}"
28
- log.error e.backtrace
27
+ log.log_exception(e, payload_summary: "[Transport] build failed for #{lex_name}", component_type: :transport)
29
28
  end
30
29
 
31
30
  def generate_base_modules
@@ -140,9 +139,7 @@ module Legion
140
139
  to = to.is_a?(String) ? Kernel.const_get(to).new : to.new
141
140
  to.bind(from, routing_key: routing_key)
142
141
  rescue StandardError => e
143
- log.fatal e.message
144
- log.fatal e.backtrace
145
- log.fatal({ from: from, to: to, routing_key: routing_key })
142
+ log.log_exception(e, level: :fatal, payload_summary: { from: from, to: to, routing_key: routing_key }, component_type: :transport)
146
143
  end
147
144
 
148
145
  def e_to_q
@@ -266,8 +266,7 @@ module Legion
266
266
  end
267
267
  true
268
268
  rescue StandardError => e
269
- Legion::Logging.error e.message
270
- Legion::Logging.error e.backtrace
269
+ Legion::Logging.log_exception(e, lex: entry[:gem_name], component_type: :boot)
271
270
  false
272
271
  end
273
272
 
@@ -21,8 +21,7 @@ module Legion
21
21
  Legion::Transport::Messages::TaskUpdate.new(task_id: task_id, status: status, **).publish
22
22
  rescue StandardError => e
23
23
  retries += 1
24
- Legion::Logging.warn "[Status] update_rmq failed (attempt #{retries}/3): #{e.message}"
25
- Legion::Logging.fatal e.backtrace
24
+ Legion::Logging.log_exception(e, level: :fatal, payload_summary: "[Status] update_rmq failed (attempt #{retries}/3)", component_type: :runner)
26
25
  retry if retries < 3
27
26
  end
28
27
 
@@ -32,9 +31,9 @@ module Legion
32
31
  task = Legion::Data::Model::Task[task_id]
33
32
  task.update(status: status)
34
33
  rescue StandardError => e
35
- Legion::Logging.warn "[Status] update_db failed for task_id=#{task_id}: #{e.message}"
36
- Legion::Logging.warn '[Status] falling back to RabbitMQ update'
37
- Legion::Logging.warn e.backtrace
34
+ Legion::Logging.log_exception(e, level: :warn,
35
+ payload_summary: "[Status] update_db failed for task_id=#{task_id}, falling back to RabbitMQ update",
36
+ component_type: :runner)
38
37
  update_rmq(task_id: task_id, status: status, **)
39
38
  end
40
39
 
@@ -61,8 +60,7 @@ module Legion
61
60
 
62
61
  { success: true, task_id: Legion::Data::Model::Task.insert(insert), **insert }
63
62
  rescue StandardError => e
64
- Legion::Logging.error e.message
65
- Legion::Logging.error e.backtrace
63
+ Legion::Logging.log_exception(e, component_type: :runner)
66
64
  raise(e)
67
65
  end
68
66
  end
@@ -48,7 +48,7 @@ module Legion
48
48
  if transport
49
49
  setup_transport
50
50
  Legion::Readiness.mark_ready(:transport)
51
- register_logging_hooks
51
+ setup_logging_transport
52
52
  end
53
53
 
54
54
  setup_dispatch
@@ -248,14 +248,18 @@ module Legion
248
248
  Legion::Logging.setup(log_level: log_level, level: log_level, trace: true)
249
249
  end
250
250
 
251
- def reconfigure_logging(cli_level)
252
- logging_settings = Legion::Settings[:logging] || {}
253
- level = cli_level || logging_settings[:level] || 'info'
251
+ def reconfigure_logging(cli_level = nil)
252
+ ls = Legion::Settings[:logging] || {}
253
+ level = cli_level || ls[:level] || 'info'
254
+
254
255
  Legion::Logging.setup(
255
- level: level,
256
- log_file: logging_settings[:log_file],
257
- log_stdout: logging_settings[:log_stdout],
258
- trace: logging_settings.fetch(:trace, true)
256
+ level: level,
257
+ format: (ls[:format] || 'text').to_sym,
258
+ log_file: ls[:log_file],
259
+ log_stdout: ls.fetch(:log_stdout, true),
260
+ trace: ls.fetch(:trace, true),
261
+ async: ls.fetch(:async, true),
262
+ include_pid: ls.fetch(:include_pid, false)
259
263
  )
260
264
  end
261
265
 
@@ -365,34 +369,73 @@ module Legion
365
369
  Legion::Logging.info 'Legion::Transport connected'
366
370
  end
367
371
 
368
- def register_logging_hooks
372
+ def setup_logging_transport # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
369
373
  return unless defined?(Legion::Transport::Connection)
370
374
  return unless Legion::Transport::Connection.session_open?
371
- return unless Legion::Transport::Connection.respond_to?(:log_channel)
372
375
 
373
- log_ch = Legion::Transport::Connection.log_channel
374
- unless log_ch
375
- Legion::Logging.debug 'No dedicated log channel available, log forwarding disabled'
376
- return
376
+ lt_settings = begin
377
+ Legion::Settings.dig(:logging, :transport) || {}
378
+ rescue StandardError
379
+ {}
377
380
  end
381
+ return unless lt_settings[:enabled] == true
378
382
 
379
- require 'legion/transport/exchanges/logging' unless defined?(Legion::Transport::Exchanges::Logging)
380
- exchange = Legion::Transport::Exchanges::Logging.new('legion.logging', channel: log_ch)
383
+ forward_logs = lt_settings.fetch(:forward_logs, true)
384
+ forward_exceptions = lt_settings.fetch(:forward_exceptions, true)
385
+ return unless forward_logs || forward_exceptions
381
386
 
382
- %i[fatal error warn].each do |level|
383
- Legion::Logging.send(:"on_#{level}") do |event|
384
- next unless log_ch&.open?
387
+ log_session = Legion::Transport::Connection.create_dedicated_session(name: 'legion-logging')
388
+ @log_session = log_session
389
+ log_channel = log_session.create_channel
390
+ log_channel.prefetch(1)
391
+ exchange = log_channel.topic('legion.logging', durable: true)
385
392
 
386
- source = event[:lex] || 'core'
387
- routing_key = "legion.#{source}.#{level}"
388
- exchange.publish(Legion::JSON.dump(event), routing_key: routing_key)
389
- rescue StandardError
390
- nil
391
- end
393
+ if forward_logs
394
+ Legion::Logging.log_writer = lambda { |event, routing_key:|
395
+ begin
396
+ next unless log_channel&.open?
397
+
398
+ exchange.publish(Legion::JSON.dump(event), routing_key: routing_key)
399
+ rescue StandardError
400
+ nil
401
+ end
402
+ }
392
403
  end
393
404
 
394
- Legion::Logging.enable_hooks!
395
- Legion::Logging.info('Logging hooks registered (dedicated channel)')
405
+ if forward_exceptions
406
+ Legion::Logging.exception_writer = lambda { |event, routing_key:, headers:, properties:|
407
+ begin
408
+ next unless log_channel&.open?
409
+
410
+ exchange.publish(
411
+ Legion::JSON.dump(event),
412
+ routing_key: routing_key,
413
+ headers: headers,
414
+ **properties
415
+ )
416
+ rescue StandardError
417
+ nil
418
+ end
419
+ }
420
+ end
421
+
422
+ modes = []
423
+ modes << 'logs' if forward_logs
424
+ modes << 'exceptions' if forward_exceptions
425
+ Legion::Logging.info("Logging transport wired: #{modes.join(' + ')} (dedicated session)")
426
+ rescue StandardError => e
427
+ Legion::Logging.warn "Logging transport setup failed: #{e.message}"
428
+ teardown_logging_transport
429
+ end
430
+
431
+ def teardown_logging_transport
432
+ Legion::Logging.log_writer = nil
433
+ Legion::Logging.exception_writer = nil
434
+ @log_session&.close if @log_session.respond_to?(:close) &&
435
+ (!@log_session.respond_to?(:open?) || @log_session.open?)
436
+ @log_session = nil
437
+ rescue StandardError
438
+ nil
396
439
  end
397
440
 
398
441
  def setup_alerts
@@ -547,6 +590,7 @@ module Legion
547
590
  shutdown_component('Cache') { Legion::Cache.shutdown }
548
591
  Legion::Readiness.mark_not_ready(:cache)
549
592
 
593
+ teardown_logging_transport
550
594
  shutdown_component('Transport') { Legion::Transport::Connection.shutdown }
551
595
  Legion::Readiness.mark_not_ready(:transport)
552
596
 
@@ -558,7 +602,7 @@ module Legion
558
602
  Legion::Events.emit('service.shutdown')
559
603
  end
560
604
 
561
- def reload
605
+ def reload # rubocop:disable Metrics/MethodLength
562
606
  return if @reloading
563
607
 
564
608
  @reloading = true
@@ -583,6 +627,7 @@ module Legion
583
627
  shutdown_component('Cache') { Legion::Cache.shutdown }
584
628
  Legion::Readiness.mark_not_ready(:cache)
585
629
 
630
+ teardown_logging_transport
586
631
  shutdown_component('Transport') { Legion::Transport::Connection.shutdown }
587
632
  Legion::Readiness.mark_not_ready(:transport)
588
633
 
@@ -599,7 +644,8 @@ module Legion
599
644
 
600
645
  setup_transport
601
646
  Legion::Readiness.mark_ready(:transport)
602
- register_logging_hooks
647
+ teardown_logging_transport
648
+ setup_logging_transport
603
649
 
604
650
  require 'legion/cache' unless defined?(Legion::Cache)
605
651
  Legion::Cache.setup
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Legion
4
- VERSION = '1.6.18'
4
+ VERSION = '1.6.20'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legionio
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.18
4
+ version: 1.6.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -281,14 +281,14 @@ dependencies:
281
281
  requirements:
282
282
  - - ">="
283
283
  - !ruby/object:Gem::Version
284
- version: 1.3.2
284
+ version: 1.4.0
285
285
  type: :runtime
286
286
  prerelease: false
287
287
  version_requirements: !ruby/object:Gem::Requirement
288
288
  requirements:
289
289
  - - ">="
290
290
  - !ruby/object:Gem::Version
291
- version: 1.3.2
291
+ version: 1.4.0
292
292
  - !ruby/object:Gem::Dependency
293
293
  name: legion-settings
294
294
  requirement: !ruby/object:Gem::Requirement