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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -1
- data/CHANGELOG.md +25 -0
- data/legionio.gemspec +8 -8
- data/lib/legion/api/audit.rb +1 -1
- data/lib/legion/api/events.rb +38 -15
- data/lib/legion/api/helpers.rb +29 -7
- data/lib/legion/api/library_routes.rb +3 -0
- data/lib/legion/api/tenants.rb +5 -6
- data/lib/legion/api/workers.rb +1 -14
- data/lib/legion/api.rb +21 -2
- data/lib/legion/cli/chat/chat_logger.rb +19 -12
- data/lib/legion/cli/config_command.rb +1 -1
- data/lib/legion/cli/error_handler.rb +8 -2
- data/lib/legion/cli/start.rb +3 -2
- data/lib/legion/cli.rb +13 -2
- data/lib/legion/extensions/catalog.rb +77 -11
- data/lib/legion/extensions/core.rb +23 -6
- data/lib/legion/extensions/helpers/secret.rb +2 -0
- data/lib/legion/extensions/transport.rb +15 -2
- data/lib/legion/region.rb +36 -1
- data/lib/legion/service.rb +174 -127
- data/lib/legion/task_outcome_observer.rb +32 -8
- data/lib/legion/telemetry.rb +19 -11
- data/lib/legion/version.rb +1 -1
- data/lib/legion/webhooks.rb +169 -40
- metadata +17 -17
data/lib/legion/service.rb
CHANGED
|
@@ -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:
|
|
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
|
|
21
|
-
cache
|
|
22
|
-
data
|
|
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
|
|
26
|
-
api
|
|
27
|
-
llm
|
|
28
|
-
gaia
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
76
|
+
handle_exception(e, level: :warn, operation: 'service.initialize.cache', fallback: 'cache_local')
|
|
63
77
|
begin
|
|
64
78
|
Legion::Cache::Local.setup
|
|
65
|
-
|
|
79
|
+
log.info 'Legion::Cache::Local connected (fallback)'
|
|
66
80
|
rescue StandardError => e2
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
+
log.info 'Legion::Data::Local connected (fallback)'
|
|
83
97
|
rescue StandardError => e2
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
189
|
-
rescue LoadError
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
200
|
-
rescue LoadError
|
|
201
|
-
|
|
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
|
-
|
|
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
|
-
|
|
234
|
+
log.info('Cluster leader election started')
|
|
216
235
|
rescue StandardError => e
|
|
217
|
-
|
|
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
|
-
|
|
225
|
-
existing.each { |d|
|
|
226
|
-
Legion::Settings.
|
|
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
|
-
|
|
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
|
-
|
|
259
|
+
handle_exception(e, level: :debug, operation: 'service.setup_compliance', availability: 'missing')
|
|
237
260
|
rescue StandardError => e
|
|
238
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
361
|
+
handle_exception(e, level: :warn, operation: 'service.setup_api', dependency: 'api')
|
|
333
362
|
rescue StandardError => e
|
|
334
|
-
|
|
363
|
+
handle_exception(e, level: :warn, operation: 'service.setup_api')
|
|
335
364
|
end
|
|
336
365
|
|
|
337
366
|
def setup_llm
|
|
338
|
-
|
|
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
|
-
|
|
343
|
-
rescue LoadError
|
|
344
|
-
|
|
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
|
-
|
|
376
|
+
handle_exception(e, level: :warn, operation: 'service.setup_llm')
|
|
347
377
|
end
|
|
348
378
|
|
|
349
379
|
def setup_gaia
|
|
350
|
-
|
|
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
|
-
|
|
355
|
-
rescue LoadError
|
|
356
|
-
|
|
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
|
-
|
|
389
|
+
handle_exception(e, level: :warn, operation: 'service.setup_gaia')
|
|
359
390
|
end
|
|
360
391
|
|
|
361
392
|
def setup_apollo
|
|
362
|
-
|
|
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
|
-
|
|
367
|
-
rescue LoadError
|
|
368
|
-
|
|
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
|
-
|
|
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
|
-
|
|
408
|
+
log.info "[Service] Dispatch started (strategy: #{Legion::Dispatch.dispatcher.class.name})"
|
|
377
409
|
end
|
|
378
410
|
|
|
379
411
|
def setup_transport
|
|
380
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
473
|
+
log.info("Logging transport wired: #{modes.join(' + ')} (dedicated session)")
|
|
441
474
|
rescue StandardError => e
|
|
442
|
-
|
|
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
|
-
|
|
458
|
-
|
|
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
|
-
|
|
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
|
-
|
|
504
|
+
log.debug 'Legion::Metrics initialized'
|
|
475
505
|
rescue StandardError => e
|
|
476
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
514
|
-
rescue LoadError
|
|
515
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
565
|
+
log.info 'Audit archiver actor started'
|
|
535
566
|
rescue StandardError => e
|
|
536
|
-
|
|
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
|
-
|
|
579
|
+
handle_exception(e, level: :debug, operation: 'service.setup_safety_metrics', availability: 'missing')
|
|
549
580
|
rescue StandardError => e
|
|
550
|
-
|
|
581
|
+
handle_exception(e, level: :debug, operation: 'service.setup_safety_metrics')
|
|
551
582
|
end
|
|
552
583
|
|
|
553
584
|
def setup_supervision
|
|
554
|
-
|
|
585
|
+
log.info 'Setting up Legion::Supervision'
|
|
555
586
|
require 'legion/supervision'
|
|
556
587
|
@supervision = Legion::Supervision.setup
|
|
557
|
-
|
|
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
|
-
|
|
599
|
+
handle_exception(e, level: :warn, operation: 'service.shutdown_api')
|
|
569
600
|
end
|
|
570
601
|
|
|
571
602
|
def shutdown
|
|
572
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
741
|
+
log.info("Loaded #{loaded} generated functions") if loaded.to_i.positive?
|
|
711
742
|
rescue StandardError => e
|
|
712
|
-
|
|
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
|
-
|
|
758
|
+
log.info '[mTLS] CertRotation started'
|
|
728
759
|
rescue LoadError => e
|
|
729
|
-
|
|
760
|
+
handle_exception(e, level: :warn, operation: 'service.setup_mtls_rotation', availability: 'missing')
|
|
730
761
|
rescue StandardError => e
|
|
731
|
-
|
|
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
|
-
|
|
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
|
-
|
|
788
|
+
log.info(message)
|
|
758
789
|
else
|
|
759
790
|
$stdout.puts "[Legion] #{message}"
|
|
760
791
|
end
|
|
761
792
|
rescue StandardError => e
|
|
762
|
-
|
|
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
|
-
|
|
800
|
+
log.warn "#{name} shutdown timed out after #{timeout}s, forcing"
|
|
770
801
|
rescue StandardError => e
|
|
771
|
-
|
|
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
|
-
|
|
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
|
-
|
|
822
|
+
log.warn "[Watchdog] Network check failed (#{count}/#{threshold})"
|
|
792
823
|
if count == threshold
|
|
793
|
-
|
|
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
|
-
|
|
829
|
+
handle_exception(e, level: :debug, operation: 'service.network_watchdog.check')
|
|
799
830
|
end
|
|
800
831
|
@network_watchdog.execute
|
|
801
|
-
|
|
832
|
+
log.info "[Watchdog] Network watchdog started (interval=#{interval}s, threshold=#{threshold})"
|
|
802
833
|
rescue StandardError => e
|
|
803
|
-
|
|
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
|
|
889
|
+
key = tls[:key]
|
|
843
890
|
|
|
844
891
|
unless cert && !cert.to_s.empty? && key && !key.to_s.empty?
|
|
845
|
-
|
|
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'
|
|
912
|
+
when 'none' then 'none'
|
|
866
913
|
when 'mutual' then 'force_peer'
|
|
867
|
-
else
|
|
914
|
+
else 'peer'
|
|
868
915
|
end
|
|
869
916
|
end
|
|
870
917
|
end
|