legionio 1.7.8 → 1.7.13

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.
@@ -1,11 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'timeout'
4
+ require 'legion/logging'
4
5
  require_relative 'readiness'
5
6
  require_relative 'process_role'
6
7
 
7
8
  module Legion
8
9
  class Service
10
+ include Legion::Logging::Helper
11
+
12
+ class << self
13
+ include Legion::Logging::Helper
14
+
15
+ private
16
+
17
+ def resolve_logger_settings
18
+ raw_logging = (Legion::Settings[:logging] if defined?(Legion::Settings) && Legion::Settings.respond_to?(:[]))
19
+ raw_logging.is_a?(Hash) ? raw_logging : Legion::Logging::Settings.default
20
+ end
21
+ end
22
+
9
23
  def modules
10
24
  base = [Legion::Crypt, Legion::Transport, Legion::Cache, Legion::Data, Legion::Supervision]
11
25
  base << Legion::LLM if defined?(Legion::LLM)
@@ -14,27 +28,27 @@ module Legion
14
28
  end
15
29
 
16
30
  def initialize(transport: nil, cache: nil, data: nil, supervision: nil, extensions: nil, # rubocop:disable Metrics/CyclomaticComplexity,Metrics/ParameterLists,Metrics/MethodLength,Metrics/PerceivedComplexity,Metrics/AbcSize
17
- crypt: nil, api: nil, llm: nil, gaia: nil, log_level: 'info', http_port: nil,
31
+ crypt: nil, api: nil, llm: nil, gaia: nil, log_level: nil, http_port: nil,
18
32
  role: nil)
19
33
  role_opts = Legion::ProcessRole.resolve(role || Legion::ProcessRole.current)
20
- transport = role_opts[:transport] if transport.nil?
21
- cache = role_opts[:cache] if cache.nil?
22
- data = role_opts[:data] if data.nil?
34
+ transport = role_opts[:transport] if transport.nil?
35
+ cache = role_opts[:cache] if cache.nil?
36
+ data = role_opts[:data] if data.nil?
23
37
  supervision = role_opts[:supervision] if supervision.nil?
24
38
  extensions = role_opts[:extensions] if extensions.nil?
25
- crypt = role_opts[:crypt] if crypt.nil?
26
- api = role_opts[:api] if api.nil?
27
- llm = role_opts[:llm] if llm.nil?
28
- gaia = role_opts[:gaia] if gaia.nil?
39
+ crypt = role_opts[:crypt] if crypt.nil?
40
+ api = role_opts[:api] if api.nil?
41
+ llm = role_opts[:llm] if llm.nil?
42
+ gaia = role_opts[:gaia] if gaia.nil?
29
43
 
30
- setup_logging(log_level: log_level)
31
- Legion::Logging.debug('Starting Legion::Service')
44
+ setup_logging(log_level: bootstrap_log_level(log_level))
45
+ log.debug('Starting Legion::Service')
32
46
  setup_settings
33
47
  apply_cli_overrides(http_port: http_port)
34
48
  setup_compliance
35
49
  setup_local_mode
36
50
  reconfigure_logging(log_level)
37
- Legion::Logging.info("node name: #{Legion::Settings[:client][:name]}")
51
+ log.info("node name: #{Legion::Settings[:client][:name]}")
38
52
 
39
53
  if crypt
40
54
  require 'legion/crypt'
@@ -59,12 +73,12 @@ module Legion
59
73
  Legion::Cache.setup
60
74
  Legion::Readiness.mark_ready(:cache)
61
75
  rescue StandardError => e
62
- Legion::Logging.warn "Legion::Cache remote failed: #{e.message}, falling back to Cache::Local"
76
+ handle_exception(e, level: :warn, operation: 'service.initialize.cache', fallback: 'cache_local')
63
77
  begin
64
78
  Legion::Cache::Local.setup
65
- Legion::Logging.info 'Legion::Cache::Local connected (fallback)'
79
+ log.info 'Legion::Cache::Local connected (fallback)'
66
80
  rescue StandardError => e2
67
- Legion::Logging.warn "Legion::Cache::Local also failed: #{e2.message}"
81
+ handle_exception(e2, level: :warn, operation: 'service.initialize.cache_local')
68
82
  end
69
83
  Legion::Readiness.mark_ready(:cache)
70
84
  end
@@ -75,13 +89,13 @@ module Legion
75
89
  setup_data
76
90
  Legion::Readiness.mark_ready(:data)
77
91
  rescue StandardError => e
78
- Legion::Logging.warn "Legion::Data remote failed: #{e.message}, falling back to Data::Local"
92
+ handle_exception(e, level: :warn, operation: 'service.initialize.data', fallback: 'data_local')
79
93
  begin
80
94
  require 'legion/data'
81
95
  Legion::Data::Local.setup if defined?(Legion::Data::Local)
82
- Legion::Logging.info 'Legion::Data::Local connected (fallback)'
96
+ log.info 'Legion::Data::Local connected (fallback)'
83
97
  rescue StandardError => e2
84
- Legion::Logging.warn "Legion::Data::Local also failed: #{e2.message}"
98
+ handle_exception(e2, level: :warn, operation: 'service.initialize.data_local')
85
99
  end
86
100
  Legion::Readiness.mark_ready(:data)
87
101
  end
@@ -94,30 +108,33 @@ module Legion
94
108
  begin
95
109
  setup_llm
96
110
  Legion::Readiness.mark_ready(:llm)
97
- rescue LoadError
98
- Legion::Logging.info 'Legion::LLM gem is not installed'
111
+ rescue LoadError => e
112
+ handle_exception(e, level: :debug, operation: 'service.initialize.llm', availability: 'missing')
113
+ log.info 'Legion::LLM gem is not installed'
99
114
  rescue StandardError => e
100
- Legion::Logging.warn "Legion::LLM failed: #{e.message}"
115
+ handle_exception(e, level: :warn, operation: 'service.initialize.llm')
101
116
  end
102
117
  end
103
118
 
104
119
  begin
105
120
  setup_apollo
106
121
  Legion::Readiness.mark_ready(:apollo)
107
- rescue LoadError
108
- Legion::Logging.info 'Legion::Apollo gem is not installed, starting without Apollo'
122
+ rescue LoadError => e
123
+ handle_exception(e, level: :debug, operation: 'service.initialize.apollo', availability: 'missing')
124
+ log.info 'Legion::Apollo gem is not installed, starting without Apollo'
109
125
  rescue StandardError => e
110
- Legion::Logging.warn "Legion::Apollo failed to load: #{e.message}"
126
+ handle_exception(e, level: :warn, operation: 'service.initialize.apollo')
111
127
  end
112
128
 
113
129
  if gaia
114
130
  begin
115
131
  setup_gaia
116
132
  Legion::Readiness.mark_ready(:gaia)
117
- rescue LoadError
118
- Legion::Logging.info 'Legion::Gaia gem is not installed'
133
+ rescue LoadError => e
134
+ handle_exception(e, level: :debug, operation: 'service.initialize.gaia', availability: 'missing')
135
+ log.info 'Legion::Gaia gem is not installed'
119
136
  rescue StandardError => e
120
- Legion::Logging.warn "Legion::Gaia failed: #{e.message}"
137
+ handle_exception(e, level: :warn, operation: 'service.initialize.gaia')
121
138
  end
122
139
  end
123
140
 
@@ -154,7 +171,7 @@ module Legion
154
171
 
155
172
  def setup_local_mode
156
173
  if lite_mode?
157
- Legion::Logging.info 'Starting in lite mode (zero infrastructure)'
174
+ log.info 'Starting in lite mode (zero infrastructure)'
158
175
  Legion::Settings[:dev] = true
159
176
  require 'legion/transport/local'
160
177
  require 'legion/crypt/mock_vault' if defined?(Legion::Crypt)
@@ -163,7 +180,7 @@ module Legion
163
180
 
164
181
  return unless local_mode?
165
182
 
166
- Legion::Logging.info 'Starting in local development mode'
183
+ log.info 'Starting in local development mode'
167
184
  Legion::Settings[:dev] = true
168
185
 
169
186
  require 'legion/transport/local'
@@ -181,26 +198,28 @@ module Legion
181
198
  end
182
199
 
183
200
  def setup_data
184
- Legion::Logging.info 'Setting up Legion::Data'
201
+ log.info 'Setting up Legion::Data'
185
202
  require 'legion/data'
186
203
  Legion::Settings.merge_settings(:data, Legion::Data::Settings.default)
187
204
  Legion::Data.setup
188
- Legion::Logging.info 'Legion::Data connected'
189
- rescue LoadError
190
- Legion::Logging.info 'Legion::Data gem is not installed, please install it manually with gem install legion-data'
205
+ log.info 'Legion::Data connected'
206
+ rescue LoadError => e
207
+ handle_exception(e, level: :debug, operation: 'service.setup_data', availability: 'missing')
208
+ log.info 'Legion::Data gem is not installed, please install it manually with gem install legion-data'
191
209
  rescue StandardError => e
192
- Legion::Logging.warn "Legion::Data failed to load, starting without it. e: #{e.message}"
210
+ handle_exception(e, level: :warn, operation: 'service.setup_data')
193
211
  end
194
212
 
195
213
  def setup_rbac
196
214
  require 'legion/rbac'
197
215
  Legion::Rbac.setup
198
216
  Legion::Readiness.mark_ready(:rbac)
199
- Legion::Logging.info 'Legion::Rbac loaded'
200
- rescue LoadError
201
- Legion::Logging.debug 'Legion::Rbac gem is not installed, starting without RBAC'
217
+ log.info 'Legion::Rbac loaded'
218
+ rescue LoadError => e
219
+ handle_exception(e, level: :debug, operation: 'service.setup_rbac', availability: 'missing')
220
+ log.debug 'Legion::Rbac gem is not installed, starting without RBAC'
202
221
  rescue StandardError => e
203
- Legion::Logging.warn "Legion::Rbac failed to load: #{e.message}"
222
+ handle_exception(e, level: :warn, operation: 'service.setup_rbac')
204
223
  end
205
224
 
206
225
  def setup_cluster
@@ -212,20 +231,24 @@ module Legion
212
231
 
213
232
  @cluster_leader = Legion::Cluster::Leader.new
214
233
  @cluster_leader.start
215
- Legion::Logging.info('Cluster leader election started')
234
+ log.info('Cluster leader election started')
216
235
  rescue StandardError => e
217
- Legion::Logging.warn("Cluster leader setup failed: #{e.message}")
236
+ handle_exception(e, level: :warn, operation: 'service.setup_cluster')
218
237
  end
219
238
 
220
239
  def setup_settings
221
240
  require 'legion/settings'
222
241
  directories = Legion::Settings::Loader.default_directories
223
242
  existing = directories.select { |d| Dir.exist?(d) }
224
- Legion::Logging.info "Settings search directories: #{directories.inspect}"
225
- existing.each { |d| Legion::Logging.info "Settings: will load from #{d}" }
226
- Legion::Settings.load(config_dirs: existing)
243
+ log.info "Settings search directories: #{directories.inspect}"
244
+ existing.each { |d| log.info "Settings: will load from #{d}" }
245
+ if Legion::Settings.respond_to?(:loaded?) && Legion::Settings.loaded?
246
+ log.info 'Legion::Settings already loaded, skipping reload'
247
+ else
248
+ Legion::Settings.load(config_dirs: existing)
249
+ end
227
250
  Legion::Readiness.mark_ready(:settings)
228
- Legion::Logging.info('Legion::Settings Loaded')
251
+ log.info('Legion::Settings Loaded')
229
252
  self.class.log_privacy_mode_status
230
253
  end
231
254
 
@@ -233,9 +256,9 @@ module Legion
233
256
  require 'legion/compliance'
234
257
  Legion::Compliance.setup
235
258
  rescue LoadError => e
236
- Legion::Logging.debug "Compliance module not available: #{e.message}"
259
+ handle_exception(e, level: :debug, operation: 'service.setup_compliance', availability: 'missing')
237
260
  rescue StandardError => e
238
- Legion::Logging.warn "Compliance setup failed: #{e.message}"
261
+ handle_exception(e, level: :warn, operation: 'service.setup_compliance')
239
262
  end
240
263
 
241
264
  def apply_cli_overrides(http_port: nil)
@@ -243,7 +266,7 @@ module Legion
243
266
 
244
267
  Legion::Settings[:api] ||= {}
245
268
  Legion::Settings[:api][:port] = http_port
246
- Legion::Logging.info "CLI override: API port set to #{http_port}"
269
+ log.info "CLI override: API port set to #{http_port}"
247
270
  end
248
271
 
249
272
  def setup_logging(log_level: 'info', **_opts)
@@ -253,7 +276,12 @@ module Legion
253
276
 
254
277
  def reconfigure_logging(cli_level = nil)
255
278
  ls = Legion::Settings[:logging] || {}
256
- level = cli_level || ls[:level] || 'info'
279
+ level = if cli_level.respond_to?(:empty?) && cli_level.empty?
280
+ nil
281
+ else
282
+ cli_level
283
+ end
284
+ level ||= ls[:level] || 'info'
257
285
 
258
286
  Legion::Logging.setup(
259
287
  level: level,
@@ -262,13 +290,14 @@ module Legion
262
290
  log_stdout: ls.fetch(:log_stdout, true),
263
291
  trace: ls.fetch(:trace, true),
264
292
  async: ls.fetch(:async, true),
265
- include_pid: ls.fetch(:include_pid, false)
293
+ include_pid: ls.fetch(:include_pid, false),
294
+ color: true
266
295
  )
267
296
  end
268
297
 
269
298
  def setup_api # rubocop:disable Metrics/MethodLength
270
299
  if @api_thread&.alive?
271
- Legion::Logging.warn 'API already running, skipping duplicate setup_api call'
300
+ log.warn 'API already running, skipping duplicate setup_api call'
272
301
  return
273
302
  end
274
303
 
@@ -282,7 +311,7 @@ module Legion
282
311
  Legion::API.set :server, :puma
283
312
  Legion::API.set :environment, :production
284
313
 
285
- puma_cfg = api_settings[:puma]
314
+ puma_cfg = api_settings[:puma]
286
315
  min_threads = puma_cfg[:min_threads]
287
316
  max_threads = puma_cfg[:max_threads]
288
317
  thread_spec = "#{min_threads}:#{max_threads}"
@@ -296,18 +325,18 @@ module Legion
296
325
  Legion::API.set :ssl_bind_options, tls_cfg
297
326
  Legion::API.set :server_settings, { quiet: true, Threads: thread_spec, **puma_timeouts,
298
327
  **ssl_server_settings(tls_cfg, bind, port) }
299
- Legion::Logging.info "Starting Legion API (TLS) on #{bind}:#{port}"
328
+ log.info "Starting Legion API (TLS) on #{bind}:#{port}"
300
329
  else
301
330
  require 'puma'
302
331
  puma_log = ::Puma::LogWriter.new(StringIO.new, StringIO.new)
303
332
  Legion::API.set :server_settings, { log_writer: puma_log, quiet: true, Threads: thread_spec, **puma_timeouts }
304
- Legion::Logging.info "Starting Legion API on #{bind}:#{port}"
333
+ log.info "Starting Legion API on #{bind}:#{port}"
305
334
  end
306
335
 
307
336
  @api_thread = Thread.new do
308
337
  retries = 0
309
338
  max_retries = api_settings[:bind_retries]
310
- retry_wait = api_settings[:bind_retry_wait]
339
+ retry_wait = api_settings[:bind_retry_wait]
311
340
 
312
341
  begin
313
342
  raise Errno::EADDRINUSE, "port #{port} already bound" if port_in_use?(bind, port)
@@ -316,11 +345,11 @@ module Legion
316
345
  rescue Errno::EADDRINUSE
317
346
  retries += 1
318
347
  if retries <= max_retries
319
- Legion::Logging.warn "Port #{port} in use, retrying in #{retry_wait}s (attempt #{retries}/#{max_retries})"
348
+ log.warn "Port #{port} in use, retrying in #{retry_wait}s (attempt #{retries}/#{max_retries})"
320
349
  sleep retry_wait
321
350
  retry
322
351
  else
323
- Legion::Logging.error "Port #{port} still in use after #{max_retries} attempts, API disabled"
352
+ log.error "Port #{port} still in use after #{max_retries} attempts, API disabled"
324
353
  Legion::Readiness.mark_not_ready(:api)
325
354
  end
326
355
  ensure
@@ -329,59 +358,62 @@ module Legion
329
358
  end
330
359
  Legion::Readiness.mark_ready(:api)
331
360
  rescue LoadError => e
332
- Legion::Logging.warn "Legion API dependencies not available: #{e.message}"
361
+ handle_exception(e, level: :warn, operation: 'service.setup_api', dependency: 'api')
333
362
  rescue StandardError => e
334
- Legion::Logging.warn "Legion API failed to start: #{e.message}"
363
+ handle_exception(e, level: :warn, operation: 'service.setup_api')
335
364
  end
336
365
 
337
366
  def setup_llm
338
- Legion::Logging.info 'Setting up Legion::LLM'
367
+ log.info 'Setting up Legion::LLM'
339
368
  require 'legion/llm'
340
369
  Legion::Settings.merge_settings('llm', Legion::LLM::Settings.default)
341
370
  Legion::LLM.start
342
- Legion::Logging.info 'Legion::LLM started'
343
- rescue LoadError
344
- Legion::Logging.info 'Legion::LLM gem is not installed, starting without LLM support'
371
+ log.info 'Legion::LLM started'
372
+ rescue LoadError => e
373
+ handle_exception(e, level: :debug, operation: 'service.setup_llm', availability: 'missing')
374
+ log.info 'Legion::LLM gem is not installed, starting without LLM support'
345
375
  rescue StandardError => e
346
- Legion::Logging.warn "Legion::LLM failed to load: #{e.message}"
376
+ handle_exception(e, level: :warn, operation: 'service.setup_llm')
347
377
  end
348
378
 
349
379
  def setup_gaia
350
- Legion::Logging.info 'Setting up Legion::Gaia'
380
+ log.info 'Setting up Legion::Gaia'
351
381
  require 'legion/gaia'
352
382
  Legion::Settings.merge_settings('gaia', Legion::Gaia::Settings.default)
353
383
  Legion::Gaia.boot
354
- Legion::Logging.info 'Legion::Gaia booted'
355
- rescue LoadError
356
- Legion::Logging.info 'Legion::Gaia gem is not installed, starting without cognitive layer'
384
+ log.info 'Legion::Gaia booted'
385
+ rescue LoadError => e
386
+ handle_exception(e, level: :debug, operation: 'service.setup_gaia', availability: 'missing')
387
+ log.info 'Legion::Gaia gem is not installed, starting without cognitive layer'
357
388
  rescue StandardError => e
358
- Legion::Logging.warn "Legion::Gaia failed to load: #{e.message}"
389
+ handle_exception(e, level: :warn, operation: 'service.setup_gaia')
359
390
  end
360
391
 
361
392
  def setup_apollo
362
- Legion::Logging.info 'Setting up Legion::Apollo'
393
+ log.info 'Setting up Legion::Apollo'
363
394
  require 'legion/apollo'
364
395
  Legion::Apollo.start
365
396
  Legion::Apollo::Local.start if defined?(Legion::Apollo::Local)
366
- Legion::Logging.info 'Legion::Apollo started'
367
- rescue LoadError
368
- Legion::Logging.info 'Legion::Apollo gem is not installed, starting without Apollo'
397
+ log.info 'Legion::Apollo started'
398
+ rescue LoadError => e
399
+ handle_exception(e, level: :debug, operation: 'service.setup_apollo', availability: 'missing')
400
+ log.info 'Legion::Apollo gem is not installed, starting without Apollo'
369
401
  rescue StandardError => e
370
- Legion::Logging.warn "Legion::Apollo failed to load: #{e.message}"
402
+ handle_exception(e, level: :warn, operation: 'service.setup_apollo')
371
403
  end
372
404
 
373
405
  def setup_dispatch
374
406
  require 'legion/dispatch'
375
407
  Legion::Dispatch.dispatcher.start
376
- Legion::Logging.info "[Service] Dispatch started (strategy: #{Legion::Dispatch.dispatcher.class.name})"
408
+ log.info "[Service] Dispatch started (strategy: #{Legion::Dispatch.dispatcher.class.name})"
377
409
  end
378
410
 
379
411
  def setup_transport
380
- Legion::Logging.info 'Setting up Legion::Transport'
412
+ log.info 'Setting up Legion::Transport'
381
413
  require 'legion/transport'
382
414
  Legion::Settings.merge_settings('transport', Legion::Transport::Settings.default)
383
415
  Legion::Transport::Connection.setup
384
- Legion::Logging.info 'Legion::Transport connected'
416
+ log.info 'Legion::Transport connected'
385
417
  end
386
418
 
387
419
  def setup_logging_transport # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
@@ -390,12 +422,13 @@ module Legion
390
422
 
391
423
  lt_settings = begin
392
424
  Legion::Settings.dig(:logging, :transport) || {}
393
- rescue StandardError
425
+ rescue StandardError => e
426
+ handle_exception(e, level: :debug, operation: 'service.setup_logging_transport.read_settings')
394
427
  {}
395
428
  end
396
429
  return unless lt_settings[:enabled] == true
397
430
 
398
- forward_logs = lt_settings.fetch(:forward_logs, true)
431
+ forward_logs = lt_settings.fetch(:forward_logs, true)
399
432
  forward_exceptions = lt_settings.fetch(:forward_exceptions, true)
400
433
  return unless forward_logs || forward_exceptions
401
434
 
@@ -437,9 +470,9 @@ module Legion
437
470
  modes = []
438
471
  modes << 'logs' if forward_logs
439
472
  modes << 'exceptions' if forward_exceptions
440
- Legion::Logging.info("Logging transport wired: #{modes.join(' + ')} (dedicated session)")
473
+ log.info("Logging transport wired: #{modes.join(' + ')} (dedicated session)")
441
474
  rescue StandardError => e
442
- Legion::Logging.warn "Logging transport setup failed: #{e.message}"
475
+ handle_exception(e, level: :warn, operation: 'service.setup_logging_transport')
443
476
  teardown_logging_transport
444
477
  end
445
478
 
@@ -449,31 +482,28 @@ module Legion
449
482
  @log_session&.close if @log_session.respond_to?(:close) &&
450
483
  (!@log_session.respond_to?(:open?) || @log_session.open?)
451
484
  @log_session = nil
452
- rescue StandardError
485
+ rescue StandardError => e
486
+ handle_exception(e, level: :debug, operation: 'service.teardown_logging_transport')
453
487
  nil
454
488
  end
455
489
 
456
490
  def setup_alerts
457
- enabled = begin
458
- Legion::Settings[:alerts][:enabled]
459
- rescue StandardError => e
460
- Legion::Logging.debug "Service#setup_alerts failed to read alerts.enabled: #{e.message}" if defined?(Legion::Logging)
461
- false
462
- end
491
+ alerts_settings = Legion::Settings[:alerts]
492
+ enabled = alerts_settings.is_a?(Hash) ? alerts_settings[:enabled] : false
463
493
  return unless enabled
464
494
 
465
495
  require 'legion/alerts'
466
496
  Legion::Alerts.setup
467
497
  rescue StandardError => e
468
- Legion::Logging.warn "Alerts setup failed: #{e.message}"
498
+ handle_exception(e, level: :warn, operation: 'service.setup_alerts')
469
499
  end
470
500
 
471
501
  def setup_metrics
472
502
  require 'legion/metrics'
473
503
  Legion::Metrics.setup
474
- Legion::Logging.debug 'Legion::Metrics initialized'
504
+ log.debug 'Legion::Metrics initialized'
475
505
  rescue StandardError => e
476
- Legion::Logging.warn "Legion::Metrics setup failed: #{e.message}"
506
+ handle_exception(e, level: :warn, operation: 'service.setup_metrics')
477
507
  end
478
508
 
479
509
  def setup_task_outcome_observer
@@ -482,14 +512,14 @@ module Legion
482
512
 
483
513
  Legion::TaskOutcomeObserver.setup
484
514
  rescue StandardError => e
485
- Legion::Logging.warn "TaskOutcomeObserver setup failed: #{e.message}"
515
+ handle_exception(e, level: :warn, operation: 'service.setup_task_outcome_observer')
486
516
  end
487
517
 
488
518
  def setup_telemetry
489
519
  return unless begin
490
520
  Legion::Settings.dig(:telemetry, :enabled)
491
521
  rescue StandardError => e
492
- Legion::Logging.debug "Service#setup_telemetry failed to read telemetry.enabled: #{e.message}" if defined?(Legion::Logging)
522
+ handle_exception(e, level: :debug, operation: 'service.setup_telemetry.read_enabled')
493
523
  false
494
524
  end
495
525
 
@@ -510,11 +540,12 @@ module Legion
510
540
  )
511
541
  end
512
542
 
513
- Legion::Logging.info "OpenTelemetry initialized: endpoint=#{endpoint} service=#{service_name}"
514
- rescue LoadError
515
- Legion::Logging.info 'OpenTelemetry gems not installed, starting without telemetry'
543
+ log.info "OpenTelemetry initialized: endpoint=#{endpoint} service=#{service_name}"
544
+ rescue LoadError => e
545
+ handle_exception(e, level: :debug, operation: 'service.setup_telemetry', availability: 'missing')
546
+ log.info 'OpenTelemetry gems not installed, starting without telemetry'
516
547
  rescue StandardError => e
517
- Legion::Logging.warn "OpenTelemetry setup failed: #{e.message}"
548
+ handle_exception(e, level: :warn, operation: 'service.setup_telemetry', endpoint: endpoint, service_name: service_name)
518
549
  end
519
550
 
520
551
  def setup_audit_archiver
@@ -525,15 +556,15 @@ module Legion
525
556
  loop do
526
557
  Legion::Audit::ArchiverActor.new.run_archival
527
558
  rescue StandardError => e
528
- Legion::Logging.error "[Audit::ArchiverActor] error: #{e.message}" if defined?(Legion::Logging)
559
+ handle_exception(e, level: :error, operation: 'service.audit_archiver.run')
529
560
  ensure
530
561
  sleep Legion::Audit::ArchiverActor::INTERVAL_SECONDS
531
562
  end
532
563
  end
533
564
  @audit_archiver_thread.abort_on_exception = false
534
- Legion::Logging.info 'Audit archiver actor started' if defined?(Legion::Logging)
565
+ log.info 'Audit archiver actor started'
535
566
  rescue StandardError => e
536
- Legion::Logging.warn "Audit archiver setup failed: #{e.message}" if defined?(Legion::Logging)
567
+ handle_exception(e, level: :warn, operation: 'service.setup_audit_archiver')
537
568
  end
538
569
 
539
570
  def shutdown_audit_archiver
@@ -545,16 +576,16 @@ module Legion
545
576
  require_relative 'telemetry/safety_metrics'
546
577
  Legion::Telemetry::SafetyMetrics.start
547
578
  rescue LoadError => e
548
- Legion::Logging.debug "Service#setup_safety_metrics: safety_metrics not available: #{e.message}" if defined?(Legion::Logging)
579
+ handle_exception(e, level: :debug, operation: 'service.setup_safety_metrics', availability: 'missing')
549
580
  rescue StandardError => e
550
- Legion::Logging.debug "[safety_metrics] setup skipped: #{e.message}" if defined?(Legion::Logging)
581
+ handle_exception(e, level: :debug, operation: 'service.setup_safety_metrics')
551
582
  end
552
583
 
553
584
  def setup_supervision
554
- Legion::Logging.info 'Setting up Legion::Supervision'
585
+ log.info 'Setting up Legion::Supervision'
555
586
  require 'legion/supervision'
556
587
  @supervision = Legion::Supervision.setup
557
- Legion::Logging.info 'Legion::Supervision started'
588
+ log.info 'Legion::Supervision started'
558
589
  end
559
590
 
560
591
  def shutdown_api
@@ -565,11 +596,11 @@ module Legion
565
596
  @api_thread = nil
566
597
  Legion::Readiness.mark_not_ready(:api)
567
598
  rescue StandardError => e
568
- Legion::Logging.warn "API shutdown error: #{e.message}"
599
+ handle_exception(e, level: :warn, operation: 'service.shutdown_api')
569
600
  end
570
601
 
571
602
  def shutdown
572
- Legion::Logging.info('Legion::Service.shutdown was called')
603
+ log.info('Legion::Service.shutdown was called')
573
604
  @shutdown = true
574
605
  Legion::Settings[:client][:shutting_down] = true
575
606
  Legion::Events.emit('service.shutting_down')
@@ -630,7 +661,7 @@ module Legion
630
661
  return if @reloading
631
662
 
632
663
  @reloading = true
633
- Legion::Logging.info 'Legion::Service.reload was called'
664
+ log.info 'Legion::Service.reload was called'
634
665
  Legion::Settings[:client][:ready] = false
635
666
 
636
667
  shutdown_network_watchdog
@@ -693,7 +724,7 @@ module Legion
693
724
  setup_network_watchdog
694
725
  Legion::Settings[:client][:ready] = true
695
726
  Legion::Events.emit('service.ready')
696
- Legion::Logging.info 'Legion has been reloaded'
727
+ log.info 'Legion has been reloaded'
697
728
  ensure
698
729
  @reloading = false
699
730
  end
@@ -707,9 +738,9 @@ module Legion
707
738
  return unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
708
739
 
709
740
  loaded = Legion::Extensions::Codegen::Helpers::GeneratedRegistry.load_on_boot
710
- Legion::Logging.info("Loaded #{loaded} generated functions") if defined?(Legion::Logging) && loaded.to_i.positive?
741
+ log.info("Loaded #{loaded} generated functions") if loaded.to_i.positive?
711
742
  rescue StandardError => e
712
- Legion::Logging.warn("setup_generated_functions failed: #{e.message}") if defined?(Legion::Logging)
743
+ handle_exception(e, level: :warn, operation: 'service.setup_generated_functions')
713
744
  end
714
745
 
715
746
  def setup_mtls_rotation
@@ -724,11 +755,11 @@ module Legion
724
755
 
725
756
  @cert_rotation = Legion::Crypt::CertRotation.new
726
757
  @cert_rotation.start
727
- Legion::Logging.info '[mTLS] CertRotation started'
758
+ log.info '[mTLS] CertRotation started'
728
759
  rescue LoadError => e
729
- Legion::Logging.warn "mTLS rotation skipped: #{e.message}"
760
+ handle_exception(e, level: :warn, operation: 'service.setup_mtls_rotation', availability: 'missing')
730
761
  rescue StandardError => e
731
- Legion::Logging.warn "mTLS rotation setup failed: #{e.message}"
762
+ handle_exception(e, level: :warn, operation: 'service.setup_mtls_rotation')
732
763
  end
733
764
 
734
765
  def shutdown_mtls_rotation
@@ -737,7 +768,7 @@ module Legion
737
768
  @cert_rotation.stop
738
769
  @cert_rotation = nil
739
770
  rescue StandardError => e
740
- Legion::Logging.warn "mTLS rotation shutdown error: #{e.message}"
771
+ handle_exception(e, level: :warn, operation: 'service.shutdown_mtls_rotation')
741
772
  end
742
773
 
743
774
  def self.log_privacy_mode_status
@@ -754,21 +785,21 @@ module Legion
754
785
  end
755
786
 
756
787
  if Legion.const_defined?('Logging')
757
- Legion::Logging.info(message)
788
+ log.info(message)
758
789
  else
759
790
  $stdout.puts "[Legion] #{message}"
760
791
  end
761
792
  rescue StandardError => e
762
- Legion::Logging.debug "Service#log_privacy_mode_status failed: #{e.message}" if defined?(Legion::Logging)
793
+ handle_exception(e, level: :debug, operation: 'service.log_privacy_mode_status') if defined?(Legion::Logging)
763
794
  nil
764
795
  end
765
796
 
766
797
  def shutdown_component(name, timeout: 5, &)
767
798
  Timeout.timeout(timeout, &)
768
799
  rescue Timeout::Error
769
- Legion::Logging.warn "#{name} shutdown timed out after #{timeout}s, forcing"
800
+ log.warn "#{name} shutdown timed out after #{timeout}s, forcing"
770
801
  rescue StandardError => e
771
- Legion::Logging.warn "#{name} shutdown error: #{e.class}: #{e.message}"
802
+ handle_exception(e, level: :warn, operation: 'service.shutdown_component', component: name, timeout: timeout)
772
803
  end
773
804
 
774
805
  def setup_network_watchdog
@@ -783,24 +814,24 @@ module Legion
783
814
  prev = @consecutive_failures.value
784
815
  @consecutive_failures.value = 0
785
816
  if prev >= threshold
786
- Legion::Logging.info '[Watchdog] Network restored, triggering reload'
817
+ log.info '[Watchdog] Network restored, triggering reload'
787
818
  Thread.new { Legion.reload } unless @reloading
788
819
  end
789
820
  else
790
821
  count = @consecutive_failures.increment
791
- Legion::Logging.warn "[Watchdog] Network check failed (#{count}/#{threshold})"
822
+ log.warn "[Watchdog] Network check failed (#{count}/#{threshold})"
792
823
  if count == threshold
793
- Legion::Logging.error '[Watchdog] Network failure threshold reached, pausing actors'
824
+ log.error '[Watchdog] Network failure threshold reached, pausing actors'
794
825
  Legion::Extensions.pause_actors if Legion::Extensions.respond_to?(:pause_actors)
795
826
  end
796
827
  end
797
828
  rescue StandardError => e
798
- Legion::Logging.debug "[Watchdog] check error: #{e.message}"
829
+ handle_exception(e, level: :debug, operation: 'service.network_watchdog.check')
799
830
  end
800
831
  @network_watchdog.execute
801
- Legion::Logging.info "[Watchdog] Network watchdog started (interval=#{interval}s, threshold=#{threshold})"
832
+ log.info "[Watchdog] Network watchdog started (interval=#{interval}s, threshold=#{threshold})"
802
833
  rescue StandardError => e
803
- Legion::Logging.warn "Network watchdog setup failed: #{e.message}"
834
+ handle_exception(e, level: :warn, operation: 'service.setup_network_watchdog')
804
835
  end
805
836
 
806
837
  def shutdown_network_watchdog
@@ -820,12 +851,28 @@ module Legion
820
851
  return true if checks.empty?
821
852
 
822
853
  checks.any?
823
- rescue StandardError
854
+ rescue StandardError => e
855
+ handle_exception(e, level: :debug, operation: 'service.network_healthy?')
824
856
  false
825
857
  end
826
858
 
827
859
  private
828
860
 
861
+ def bootstrap_log_level(cli_level)
862
+ cli_level = nil if cli_level.respond_to?(:empty?) && cli_level.empty?
863
+ return cli_level if cli_level
864
+
865
+ raw_logging = (Legion::Settings[:logging] if defined?(Legion::Settings) && Legion::Settings.respond_to?(:[]))
866
+
867
+ level = raw_logging[:level] if raw_logging.is_a?(Hash)
868
+ level || Legion::Logging::Settings.default[:level] || 'info'
869
+ end
870
+
871
+ def resolve_logger_settings
872
+ raw_logging = (Legion::Settings[:logging] if defined?(Legion::Settings) && Legion::Settings.respond_to?(:[]))
873
+ raw_logging.is_a?(Hash) ? raw_logging : Legion::Logging::Settings.default
874
+ end
875
+
829
876
  def port_in_use?(bind, port)
830
877
  TCPServer.new(bind, port).close
831
878
  false
@@ -839,10 +886,10 @@ module Legion
839
886
  return nil unless tls[:enabled] == true
840
887
 
841
888
  cert = tls[:cert]
842
- key = tls[:key]
889
+ key = tls[:key]
843
890
 
844
891
  unless cert && !cert.to_s.empty? && key && !key.to_s.empty?
845
- Legion::Logging.warn 'api.tls enabled but cert or key is missing — falling back to plain HTTP'
892
+ log.warn 'api.tls enabled but cert or key is missing — falling back to plain HTTP'
846
893
  return nil
847
894
  end
848
895
 
@@ -862,9 +909,9 @@ module Legion
862
909
 
863
910
  def verify_mode_for(verify)
864
911
  case verify.to_s
865
- when 'none' then 'none'
912
+ when 'none' then 'none'
866
913
  when 'mutual' then 'force_peer'
867
- else 'peer'
914
+ else 'peer'
868
915
  end
869
916
  end
870
917
  end