legion-logging 1.5.3 → 1.5.5
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 +14 -0
- data/lib/legion/logging/async_writer.rb +3 -1
- data/lib/legion/logging/helper.rb +56 -7
- data/lib/legion/logging/logger.rb +1 -1
- data/lib/legion/logging/methods.rb +97 -17
- data/lib/legion/logging/version.rb +1 -1
- data/lib/legion/logging.rb +1 -1
- 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: cd5e1bffbdc433aedb24653ea515080063cb456de0c5a0fd5a57cdbff91d371d
|
|
4
|
+
data.tar.gz: 0b015f1b0c5d12511f231bfa32325e2ebcc41ca902b92d8293bdcf6359d8a7ac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0bad8e3a31fb86de2b1a2749457b506e7042606e7e060813a0adcaf81f2d0afdf8998e3ba57692fabe5814ab7efcb3dcadfb9c8e985a77b6f3c4e44aedcccf35
|
|
7
|
+
data.tar.gz: 83475ff76aae03a25c24e1b464695f68ef6b4514309e6290485e513066f3d9f4ff9367e2b87e087f89dd1f1b6b3fafa4176ca5fcae226224859356d23189a80f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Legion::Logging Changelog
|
|
2
2
|
|
|
3
|
+
## [1.5.5] - 2026-05-27
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- `emit_tagged` (used by all Legion::Logging::Helper consumers) now routes through the async writer instead of doing synchronous IO#write — eliminates IO mutex contention across all consumer threads
|
|
7
|
+
- `fatal` level now routes through async writer consistently with all other levels
|
|
8
|
+
- `log_exception` now writes through async writer instead of direct `log.public_send`
|
|
9
|
+
- `Legion::Logging::Logger` defaults to `async: true`, ensuring all per-extension logger instances use non-blocking writes
|
|
10
|
+
|
|
11
|
+
## [1.5.4] - 2026-05-22
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- Structured log and exception AMQP writer headers now include `legion_protocol_version`, best-effort `x-legion-version`, and transport-standard `x-legion-identity-*` headers when process identity is resolved.
|
|
15
|
+
- `log_writer` now receives the same `headers:` and `properties:` envelope metadata shape as `exception_writer`.
|
|
16
|
+
|
|
3
17
|
## [1.5.3] - 2026-05-13
|
|
4
18
|
|
|
5
19
|
### Added
|
|
@@ -110,7 +110,9 @@ module Legion
|
|
|
110
110
|
lex_name = event[:lex] || 'core'
|
|
111
111
|
component = event.dig(:caller, :file).to_s[Legion::Logging::Methods::COMPONENT_REGEX, 1] || 'unknown'
|
|
112
112
|
routing_key = "legion.logging.log.#{level}.#{lex_name}.#{component}"
|
|
113
|
-
Legion::Logging.
|
|
113
|
+
headers = Legion::Logging.send(:build_log_headers, event, component, level)
|
|
114
|
+
properties = Legion::Logging.send(:build_log_properties, level)
|
|
115
|
+
Legion::Logging.log_writer.call(event, routing_key: routing_key, headers: headers, properties: properties)
|
|
114
116
|
Legion::Logging::Hooks.fire(level, entry.message, event) if defined?(Legion::Logging::Hooks)
|
|
115
117
|
rescue StandardError => e
|
|
116
118
|
warn("legion-log-writer writer error: #{e.message}")
|
|
@@ -559,21 +559,70 @@ module Legion
|
|
|
559
559
|
|
|
560
560
|
def build_exception_headers(event, comp, level)
|
|
561
561
|
headers = {
|
|
562
|
-
'
|
|
563
|
-
'x-
|
|
564
|
-
'x-
|
|
565
|
-
'x-
|
|
566
|
-
'x-
|
|
567
|
-
'x-
|
|
568
|
-
'x-
|
|
562
|
+
'legion_protocol_version' => '2.0',
|
|
563
|
+
'x-error-fingerprint' => event[:error_fingerprint],
|
|
564
|
+
'x-exception-class' => event[:exception_class],
|
|
565
|
+
'x-handled' => event[:handled].to_s,
|
|
566
|
+
'x-gem-name' => event[:gem_name].to_s,
|
|
567
|
+
'x-lex-version' => event[:lex_version].to_s,
|
|
568
|
+
'x-component-type' => comp.to_s,
|
|
569
|
+
'x-level' => level.to_s
|
|
569
570
|
}
|
|
571
|
+
append_legion_version_header(headers)
|
|
570
572
|
headers['x-task-id'] = event[:task_id].to_s if event[:task_id]
|
|
571
573
|
headers['x-conversation-id'] = event[:conversation_id].to_s if event[:conversation_id]
|
|
572
574
|
headers['x-chain-id'] = event[:chain_id].to_s if event[:chain_id]
|
|
573
575
|
headers['x-user'] = event[:user].to_s if event[:user]
|
|
576
|
+
append_identity_headers(headers)
|
|
574
577
|
headers
|
|
575
578
|
end
|
|
576
579
|
|
|
580
|
+
def append_identity_headers(headers)
|
|
581
|
+
return unless defined?(Legion::Identity::Process)
|
|
582
|
+
return if Legion::Identity::Process.respond_to?(:resolved?) && !Legion::Identity::Process.resolved?
|
|
583
|
+
|
|
584
|
+
id = identity_hash
|
|
585
|
+
append_optional_header(headers, 'x-legion-identity-canonical-name', id[:canonical_name])
|
|
586
|
+
append_optional_header(headers, 'x-legion-identity-trust', id[:trust])
|
|
587
|
+
append_optional_header(headers, 'x-legion-identity-id', id[:id])
|
|
588
|
+
append_optional_header(headers, 'x-legion-identity-kind', id[:kind])
|
|
589
|
+
append_optional_header(headers, 'x-legion-identity-mode', id[:mode])
|
|
590
|
+
append_optional_header(headers, 'x-legion-identity-source', id[:source])
|
|
591
|
+
headers['x-legion-identity-db-principal-id'] = id[:db_principal_id] if id[:db_principal_id]
|
|
592
|
+
headers['x-legion-identity-db-identity-id'] = id[:db_identity_id] if id[:db_identity_id]
|
|
593
|
+
rescue StandardError
|
|
594
|
+
nil
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
def append_optional_header(headers, key, value)
|
|
598
|
+
return if value.nil?
|
|
599
|
+
return if value.respond_to?(:empty?) && value.empty?
|
|
600
|
+
|
|
601
|
+
headers[key] = value.to_s
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
def append_legion_version_header(headers)
|
|
605
|
+
append_optional_header(headers, 'x-legion-version', Legion::VERSION) if defined?(Legion::VERSION)
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
def identity_hash
|
|
609
|
+
process = Legion::Identity::Process
|
|
610
|
+
return process.identity_hash if process.respond_to?(:identity_hash)
|
|
611
|
+
|
|
612
|
+
{
|
|
613
|
+
canonical_name: identity_value(process, :canonical_name),
|
|
614
|
+
id: identity_value(process, :id),
|
|
615
|
+
kind: identity_value(process, :kind),
|
|
616
|
+
mode: identity_value(process, :mode),
|
|
617
|
+
source: identity_value(process, :source),
|
|
618
|
+
trust: identity_value(process, :trust)
|
|
619
|
+
}
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
def identity_value(process, method_name)
|
|
623
|
+
process.public_send(method_name) if process.respond_to?(method_name)
|
|
624
|
+
end
|
|
625
|
+
|
|
577
626
|
def build_exception_properties(event, level)
|
|
578
627
|
{
|
|
579
628
|
content_type: 'application/json',
|
|
@@ -11,7 +11,7 @@ module Legion
|
|
|
11
11
|
include Legion::Logging::Methods
|
|
12
12
|
include Legion::Logging::Builder
|
|
13
13
|
|
|
14
|
-
def initialize(level: 'info', log_file: nil, log_stdout: nil, lex: nil, trace: false, extended: false, trace_size: 4, format: :text, async:
|
|
14
|
+
def initialize(level: 'info', log_file: nil, log_stdout: nil, lex: nil, trace: false, extended: false, trace_size: 4, format: :text, async: true, **opts)
|
|
15
15
|
@lex = lex
|
|
16
16
|
set_log(logfile: log_file, log_stdout: log_stdout)
|
|
17
17
|
log_level(level)
|
|
@@ -66,11 +66,9 @@ module Legion
|
|
|
66
66
|
return unless log.level < 5
|
|
67
67
|
|
|
68
68
|
message = yield if message.nil? && block_given?
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
log.fatal(message)
|
|
73
|
-
fire_log_writer(:fatal, raw)
|
|
69
|
+
raw = maybe_redact(message)
|
|
70
|
+
formatted = format_message_for_level(:fatal, raw)
|
|
71
|
+
write_async_or_sync(:fatal, formatted, raw, writer_context: build_writer_context(:fatal, raw))
|
|
74
72
|
end
|
|
75
73
|
|
|
76
74
|
def unknown(message = nil)
|
|
@@ -89,8 +87,20 @@ module Legion
|
|
|
89
87
|
formatted = format_message_for_level(level, raw)
|
|
90
88
|
|
|
91
89
|
with_tagged_context(segments, method_ctx) do
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
ctx = %i[warn error fatal].include?(level) ? build_writer_context(level, raw) : nil
|
|
91
|
+
writer = @async_writer
|
|
92
|
+
caller_trace = capture_runner_trace_for_async
|
|
93
|
+
if writer&.alive?
|
|
94
|
+
writer.push(AsyncWriter::LogEntry.new(
|
|
95
|
+
level: level, message: formatted, writer_context: ctx,
|
|
96
|
+
segments: Thread.current[:legion_log_segments],
|
|
97
|
+
method_ctx: Thread.current[:legion_log_method],
|
|
98
|
+
caller_trace: caller_trace
|
|
99
|
+
))
|
|
100
|
+
else
|
|
101
|
+
with_caller_trace(caller_trace) { write_forced(level, formatted) }
|
|
102
|
+
fire_log_writer(level, raw) if ctx
|
|
103
|
+
end
|
|
94
104
|
end
|
|
95
105
|
end
|
|
96
106
|
|
|
@@ -104,11 +114,12 @@ module Legion
|
|
|
104
114
|
source_code_uri: nil, handled: false, payload_summary: nil,
|
|
105
115
|
task_id: nil, backtrace_limit: nil, **extra)
|
|
106
116
|
level = level.to_sym if level.respond_to?(:to_sym)
|
|
107
|
-
# 1. Log human-readable line + backtrace
|
|
117
|
+
# 1. Log human-readable line + backtrace via async writer
|
|
108
118
|
msg = exception.respond_to?(:message) ? exception.message : exception.to_s
|
|
109
119
|
msg = maybe_redact(msg)
|
|
110
120
|
msg = build_exception_log_message(exception, msg, backtrace_limit)
|
|
111
|
-
|
|
121
|
+
formatted = format_message_for_level(level, msg)
|
|
122
|
+
write_async_or_sync(level, formatted, msg)
|
|
112
123
|
|
|
113
124
|
# 2. Build rich exception event
|
|
114
125
|
event = Legion::Logging::EventBuilder.build_exception(
|
|
@@ -270,6 +281,48 @@ module Legion
|
|
|
270
281
|
false
|
|
271
282
|
end
|
|
272
283
|
|
|
284
|
+
def build_log_headers(event, component, level)
|
|
285
|
+
headers = {
|
|
286
|
+
'legion_protocol_version' => '2.0',
|
|
287
|
+
'x-component-type' => component.to_s,
|
|
288
|
+
'x-level' => level.to_s
|
|
289
|
+
}
|
|
290
|
+
append_legion_version_header(headers)
|
|
291
|
+
append_optional_header(headers, 'x-lex', event[:lex])
|
|
292
|
+
append_optional_header(headers, 'x-node', event[:node])
|
|
293
|
+
append_identity_headers(headers)
|
|
294
|
+
headers
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def build_log_properties(level)
|
|
298
|
+
{
|
|
299
|
+
content_type: 'application/json',
|
|
300
|
+
message_id: SecureRandom.uuid,
|
|
301
|
+
timestamp: Time.now.to_i,
|
|
302
|
+
app_id: 'legionio',
|
|
303
|
+
type: 'log_event',
|
|
304
|
+
priority: EXCEPTION_PRIORITY[level] || 0,
|
|
305
|
+
delivery_mode: 2
|
|
306
|
+
}
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def append_identity_headers(headers)
|
|
310
|
+
return unless defined?(Legion::Identity::Process)
|
|
311
|
+
return if Legion::Identity::Process.respond_to?(:resolved?) && !Legion::Identity::Process.resolved?
|
|
312
|
+
|
|
313
|
+
id = identity_hash
|
|
314
|
+
append_optional_header(headers, 'x-legion-identity-canonical-name', id[:canonical_name])
|
|
315
|
+
append_optional_header(headers, 'x-legion-identity-trust', id[:trust])
|
|
316
|
+
append_optional_header(headers, 'x-legion-identity-id', id[:id])
|
|
317
|
+
append_optional_header(headers, 'x-legion-identity-kind', id[:kind])
|
|
318
|
+
append_optional_header(headers, 'x-legion-identity-mode', id[:mode])
|
|
319
|
+
append_optional_header(headers, 'x-legion-identity-source', id[:source])
|
|
320
|
+
headers['x-legion-identity-db-principal-id'] = id[:db_principal_id] if id[:db_principal_id]
|
|
321
|
+
headers['x-legion-identity-db-identity-id'] = id[:db_identity_id] if id[:db_identity_id]
|
|
322
|
+
rescue StandardError
|
|
323
|
+
nil
|
|
324
|
+
end
|
|
325
|
+
|
|
273
326
|
def publish_exception_event(event, level)
|
|
274
327
|
lex_name = event[:lex] || 'core'
|
|
275
328
|
comp = event[:component_type] || :unknown
|
|
@@ -281,17 +334,20 @@ module Legion
|
|
|
281
334
|
|
|
282
335
|
def build_exception_headers(event, comp, level)
|
|
283
336
|
headers = {
|
|
284
|
-
'
|
|
285
|
-
'x-
|
|
286
|
-
'x-
|
|
287
|
-
'x-
|
|
288
|
-
'x-
|
|
289
|
-
'x-
|
|
290
|
-
'x-
|
|
337
|
+
'legion_protocol_version' => '2.0',
|
|
338
|
+
'x-error-fingerprint' => event[:error_fingerprint],
|
|
339
|
+
'x-exception-class' => event[:exception_class],
|
|
340
|
+
'x-handled' => event[:handled].to_s,
|
|
341
|
+
'x-gem-name' => event[:gem_name].to_s,
|
|
342
|
+
'x-lex-version' => event[:lex_version].to_s,
|
|
343
|
+
'x-component-type' => comp.to_s,
|
|
344
|
+
'x-level' => level.to_s
|
|
291
345
|
}
|
|
346
|
+
append_legion_version_header(headers)
|
|
292
347
|
append_optional_header(headers, 'x-task-id', event[:task_id])
|
|
293
348
|
append_optional_header(headers, 'x-conversation-id', event[:conversation_id])
|
|
294
349
|
append_optional_header(headers, 'x-user', event[:user])
|
|
350
|
+
append_identity_headers(headers)
|
|
295
351
|
headers
|
|
296
352
|
end
|
|
297
353
|
|
|
@@ -302,6 +358,28 @@ module Legion
|
|
|
302
358
|
headers[key] = value.to_s
|
|
303
359
|
end
|
|
304
360
|
|
|
361
|
+
def append_legion_version_header(headers)
|
|
362
|
+
append_optional_header(headers, 'x-legion-version', Legion::VERSION) if defined?(Legion::VERSION)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def identity_hash
|
|
366
|
+
process = Legion::Identity::Process
|
|
367
|
+
return process.identity_hash if process.respond_to?(:identity_hash)
|
|
368
|
+
|
|
369
|
+
{
|
|
370
|
+
canonical_name: identity_value(process, :canonical_name),
|
|
371
|
+
id: identity_value(process, :id),
|
|
372
|
+
kind: identity_value(process, :kind),
|
|
373
|
+
mode: identity_value(process, :mode),
|
|
374
|
+
source: identity_value(process, :source),
|
|
375
|
+
trust: identity_value(process, :trust)
|
|
376
|
+
}
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def identity_value(process, method_name)
|
|
380
|
+
process.public_send(method_name) if process.respond_to?(method_name)
|
|
381
|
+
end
|
|
382
|
+
|
|
305
383
|
def build_exception_properties(event, level)
|
|
306
384
|
{
|
|
307
385
|
content_type: 'application/json',
|
|
@@ -347,7 +425,9 @@ module Legion
|
|
|
347
425
|
lex_name = event[:lex] || 'core'
|
|
348
426
|
component = event.dig(:caller, :file).to_s[COMPONENT_REGEX, 1] || 'unknown'
|
|
349
427
|
routing_key = "legion.logging.log.#{level}.#{lex_name}.#{component}"
|
|
350
|
-
|
|
428
|
+
headers = build_log_headers(event, component, level)
|
|
429
|
+
properties = build_log_properties(level)
|
|
430
|
+
Legion::Logging.log_writer.call(event, routing_key: routing_key, headers: headers, properties: properties)
|
|
351
431
|
Legion::Logging::Hooks.fire(level, message, event) if defined?(Legion::Logging::Hooks)
|
|
352
432
|
rescue StandardError => e
|
|
353
433
|
rk = defined?(routing_key) ? routing_key : 'unknown'
|
data/lib/legion/logging.rb
CHANGED
|
@@ -25,7 +25,7 @@ module Legion
|
|
|
25
25
|
attr_reader :color
|
|
26
26
|
attr_writer :log_writer, :exception_writer
|
|
27
27
|
|
|
28
|
-
DEFAULT_LOG_WRITER = ->(_event, routing_key:) {}
|
|
28
|
+
DEFAULT_LOG_WRITER = ->(_event, routing_key:, headers: nil, properties: nil) {}
|
|
29
29
|
DEFAULT_EXCEPTION_WRITER = ->(_event, routing_key:, headers:, properties:) {}
|
|
30
30
|
|
|
31
31
|
def log_writer
|