legion-logging 1.5.0 → 1.5.2

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: 62c3c14ff8e6f67a11941a869f4f4ea2b682686c85c41bb1568368b448f23d0c
4
- data.tar.gz: af9c1588f601a09f6b986d9dc47cc07b1c8e9b966e482b45d1a7242342e8d500
3
+ metadata.gz: 57e4eef04828ffa39cae5277822be4f2f5264d4010b05e724dbb179f0af5b771
4
+ data.tar.gz: 740663a6979cf6b5c5d90a12bc8c43e0fcd989b8d8253269d8a1f1bf9aff9d03
5
5
  SHA512:
6
- metadata.gz: 66a26f844b44432886b1d703d93dbb9547134ede3542610861e123e240b8ec89746d7bdc522ae8e831c93e212f9e1a9bff5fc9b2fa19a78dfb47007373c5e0ab
7
- data.tar.gz: add0a140cbe3c101dc83dcfb4b1c3eef385e4c778a4b6957be6d71b2ec95a9316c3b36d7b687906867046d482b2246ad0a44e8fd4cc98472ad1157994207146a
6
+ metadata.gz: c590642e775a8f6ed7f87f9b4525049033c3e3f42635ea2e460e5b4ddd89e646a97d8744b80755a82db340ba6e813d8e95e3a8b27ffc17a63f8f3aee99e5b5a6
7
+ data.tar.gz: b812f47d57057a466655f22ae1f3922b6326a22fd40297ce602850069092efaf20ff40a6689ecbfe22a7498429bb3266df70ec40efb69617d11da045695155bd
data/.gitignore CHANGED
@@ -9,7 +9,8 @@
9
9
  /tmp/
10
10
  /legion/.idea/
11
11
  /.idea/
12
+ *.gem
12
13
  *.key
13
14
  # rspec failure tracking
14
15
  .rspec_status
15
- legionio.key
16
+ legionio.key
data/CHANGELOG.md CHANGED
@@ -1,8 +1,16 @@
1
1
  # Legion::Logging Changelog
2
2
 
3
- ## [1.5.0] - 2026-04-02
3
+ ## [1.5.2] - 2026-04-27
4
+
5
+ ### Changed
6
+ - Exception stdout/file log lines now include the full backtrace instead of truncating after 10 frames with a `... N more` suffix.
7
+
8
+ ## [1.5.1] - 2026-04-08
4
9
 
5
10
  ### Added
11
+ - `Legion::Logging::MethodTracer` module: opt-in TracePoint-based method call tracing with indent-aware call/return output and parameter formatting
12
+ - `Helper.included` / `Helper.extended` hooks auto-attach `MethodTracer` when `MethodTracer::ENABLED` is true
13
+ - `log_exception` now formats backtrace (up to 10 frames + overflow count) inline in the stdout/file log line
6
14
  - `Legion::Logging.current_settings` and `.configuration_generation` so helper mixins can refresh memoized tagged loggers after runtime reconfiguration
7
15
  - Component logger overrides from local `settings`, top-level `Legion::Settings[component]`, and `Legion::Settings.dig(:extensions, component)` for `log_level`, `trace`, `trace_size`, and `extended`
8
16
  - `Methods#emit_tagged` / `TaggedLogger#dispatch` path so component-level loggers can emit with their own level while preserving tagged context
data/CLAUDE.md CHANGED
@@ -8,40 +8,50 @@
8
8
  Ruby logging class for the LegionIO framework. Provides colorized console output via Rainbow, structured JSON logging (`format: :json`), and a consistent logging interface across all Legion gems and extensions.
9
9
 
10
10
  **GitHub**: https://github.com/LegionIO/legion-logging
11
- **Version**: 1.3.2
11
+ **Version**: 1.5.0
12
12
  **License**: Apache-2.0
13
13
 
14
14
  ## Architecture
15
15
 
16
16
  ```
17
17
  Legion::Logging (singleton module)
18
- ├── Methods # Log level methods: debug, info, warn, error, fatal, unknown
19
- ├── Builder # Output destination (stdout/file), log level, formatter, async: keyword
20
- ├── AsyncWriter # Non-blocking SizedQueue-backed writer thread; fatal calls bypass queue
21
- ├── Hooks # Callback registry for fatal/error/warn events (on_fatal, on_error, on_warn)
22
- ├── EventBuilder # Structured event payload builder (caller, exception, lex, gem metadata)
23
- ├── Helper # Injectable log mixin for LEX extensions (derives logger tags from segments/class)
24
- ├── Logger # Core logger configuration and setup
25
- ├── MultiIO # Write to multiple destinations simultaneously
26
- ├── SIEMExporter # PHI-redacting SIEM export (Splunk HEC, ELK/OpenSearch)
27
- ├── Shipper # Buffered log event forwarding (file/http transports)
28
- ├── Redactor # PII/PHI pattern redaction
29
- └── Version # VERSION constant
18
+ ├── Methods # Log level methods: debug, info, warn, error, fatal, unknown; log_exception helper
19
+ ├── Builder # Output destination (stdout/file), log level, formatter, async: keyword
20
+ ├── AsyncWriter # Non-blocking SizedQueue-backed writer thread; fatal calls bypass queue
21
+ ├── Hooks # Callback registry for fatal/error/warn events (on_fatal, on_error, on_warn)
22
+ ├── EventBuilder # Structured event payload builder (caller, exception, lex, gem metadata); fingerprint for dedup
23
+ ├── Helper # Injectable log mixin for LEX extensions (derives logger tags from segments/class)
24
+ ├── Logger # Core logger configuration and setup
25
+ ├── MultiIO # Write to multiple destinations simultaneously
26
+ ├── TaggedLogger # Logger wrapper that prepends structured tags to each message
27
+ ├── CategoryRegistry # Registry of named log categories with description and expected_fields
28
+ ├── MethodTracer # Tracing module for instrumenting method calls (call/return, formatted args)
29
+ ├── SIEMExporter # PHI-redacting SIEM export (Splunk HEC, ELK/OpenSearch)
30
+ ├── Shipper # Buffered log event forwarding; sub-transports: FileTransport, HttpTransport
31
+ ├── Redactor # PII/PHI + secret pattern redaction; opt-in via logging.redaction.enabled
32
+ └── Version # VERSION constant
33
+
34
+ # Module-level writers (pluggable lambda slots replacing old Hooks for AMQP forwarding)
35
+ Legion::Logging.log_writer # -> lambda(->(event, routing_key:) {})
36
+ Legion::Logging.exception_writer # -> lambda(->(event, routing_key:, headers:, properties:) {})
30
37
  ```
31
38
 
32
39
  ### Key Design Patterns
33
40
 
34
- - **Singleton Module**: `Legion::Logging` uses `class << self` - called directly: `Legion::Logging.info("msg")`
35
- - **Rainbow Colorization**: Console output uses Rainbow gem for colored terminal output
36
- - **Setup Method**: `Legion::Logging.setup(log_file:, level:, async: true)` configures output destination, level, and async mode
37
- - **Async by Default**: `setup` enables async logging — calls return immediately. Fatal calls always bypass the queue. `stop_async_writer` flushes and stops on shutdown. Buffer size configurable via `Legion::Settings.dig(:logging, :async, :buffer_size)` (default 10,000). Back-pressure: callers block when buffer is full.
38
- - **Structured JSON**: `format: :json` in settings outputs machine-parseable JSON log lines
41
+ - **Singleton Module**: `Legion::Logging` uses `class << self` called directly: `Legion::Logging.info("msg")`
42
+ - **Rainbow Colorization**: Console output uses Rainbow gem for colored terminal output. Color auto-disabled in JSON format and when writing to a log file.
43
+ - **Setup Method**: `Legion::Logging.setup(level:, format:, async:, **options)` configures output, level, format, and async mode. Increments `configuration_generation` on each call.
44
+ - **Async by Default**: `setup` enables async logging — calls return immediately. Fatal calls always bypass the queue. `stop_async_writer` flushes and stops on shutdown. Buffer size configurable via `Legion::Settings[:logging][:async][:buffer_size]` (default 10,000). Back-pressure: callers block when buffer is full.
45
+ - **Structured JSON**: `format: :json` in settings outputs machine-parseable JSON log lines (disables color)
39
46
  - **Shared Interface**: Same method signature (`info`, `warn`, `error`, etc.) across all Legion components
40
47
  - **MultiIO**: Splits writes to stdout and a log file simultaneously (used by Builder when `log_file` is set)
41
48
  - **SIEMExporter**: PHI redaction (SSN, phone, MRN, DOB patterns), `export_to_splunk` (HEC), `format_for_elk`
42
- - **Hook Callbacks**: `on_fatal`, `on_error`, `on_warn` register procs called after each log at those levels. Hooks are gated by `enable_hooks!`/`disable_hooks!`. Hook failures are silently rescued — never impact the logger. Hooks fire on the async writer thread; event context captured on caller thread.
43
- - **EventBuilder**: Builds structured event hashes from log context (caller location, exception info, lex identity, gem metadata). All from in-memory data, zero IO.
49
+ - **Hook Callbacks**: `on_fatal`, `on_error`, `on_warn` register procs called after each log at those levels. Hooks are gated by `enable_hooks!`/`disable_hooks!`. Hook failures are silently rescued. Hooks fire on the async writer thread; event context captured on caller thread.
50
+ - **EventBuilder**: Builds structured event hashes from log context (caller location, exception info, lex identity, gem metadata). All from in-memory data, zero IO. `fingerprint` produces MD5 for dedup in log aggregation.
44
51
  - **Helper mixin**: `Legion::Logging::Helper` is injectable into LEX extensions. Derives logger tags from `segments`, `lex_filename`, or class name. Passes through `settings[:logger]` config when available.
52
+ - **Writer Lambdas**: `log_writer` and `exception_writer` are module-level lambda slots for forwarding to external systems (AMQP, etc.). Default implementations are no-ops. Set via `Legion::Logging.log_writer = lambda`.
53
+ - **CategoryRegistry**: Named log categories with description and expected_fields. Register via `Legion::Logging.register_category`. Used for structured log validation.
54
+ - **Redactor**: Opt-in PII/PHI redaction (`logging.redaction.enabled: true`). Guards against Settings recursive init via `@loader` ivar check. Patterns: SSN, phone, MRN, DOB, Vault tokens, JWTs, bearer tokens, lease IDs.
45
55
 
46
56
  ## Dependencies
47
57
 
@@ -62,7 +72,15 @@ Legion::Logging (singleton module)
62
72
  | `lib/legion/logging/multi_io.rb` | Multi-output IO (write to multiple destinations simultaneously) |
63
73
  | `lib/legion/logging/siem_exporter.rb` | PHI-redacting SIEM export helpers (Splunk HEC, ELK format) |
64
74
  | `lib/legion/logging/hooks.rb` | Callback registry (fatal/error/warn hook arrays, enable/disable/clear) |
65
- | `lib/legion/logging/event_builder.rb` | Structured event payload builder |
75
+ | `lib/legion/logging/event_builder.rb` | Structured event payload builder; `fingerprint` for MD5 dedup |
76
+ | `lib/legion/logging/tagged_logger.rb` | Logger wrapper that prepends structured tags to each message |
77
+ | `lib/legion/logging/category_registry.rb` | Named log category registration and lookup |
78
+ | `lib/legion/logging/method_tracer.rb` | Method call tracing instrumentation (call/return, formatted args) |
79
+ | `lib/legion/logging/shipper.rb` | Buffered log event forwarding to external systems |
80
+ | `lib/legion/logging/shipper/file_transport.rb` | File-based log shipper transport |
81
+ | `lib/legion/logging/shipper/http_transport.rb` | HTTP-based log shipper transport |
82
+ | `lib/legion/logging/siem_exporter.rb` | PHI-redacting SIEM export (Splunk HEC, ELK format) |
83
+ | `lib/legion/logging/redactor.rb` | PII/PHI + secret pattern redaction (opt-in) |
66
84
  | `lib/legion/logging/version.rb` | VERSION constant |
67
85
 
68
86
  ## Role in LegionIO
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Logging module for the [LegionIO](https://github.com/LegionIO/LegionIO) framework. Provides colorized console output via Rainbow, structured JSON logging, multi-output IO, and a consistent logging interface across all Legion gems and extensions.
4
4
 
5
- **Version**: 1.4.1
5
+ **Version**: 1.5.2
6
6
 
7
7
  ## Installation
8
8
 
@@ -96,23 +96,46 @@ end
96
96
 
97
97
  ### Exception Logging
98
98
 
99
- `log_exception` provides a single call for complete structured exception events with component context:
99
+ `log_exception` provides a single call for complete exception logging with component context. It writes a human-readable exception line through the configured logger and publishes a structured exception event when an `exception_writer` is configured.
100
100
 
101
101
  ```ruby
102
- Legion::Logging.log_exception(exception,
103
- handled: true,
104
- component_type: :runner,
105
- lex: 'my_extension',
106
- task_id: 'abc-123')
102
+ begin
103
+ runner.call
104
+ rescue StandardError => e
105
+ Legion::Logging.log_exception(
106
+ e,
107
+ handled: true,
108
+ component_type: :runner,
109
+ lex: 'my_extension',
110
+ task_id: 'abc-123'
111
+ )
112
+ end
107
113
  ```
108
114
 
115
+ The synchronous log line includes the full Ruby backtrace. Legion does not truncate it to a fixed frame count or replace the tail with `... N more`, because the missing frames are often the useful part of production failures.
116
+
117
+ Structured exception events include:
118
+
119
+ - exception class and message
120
+ - full backtrace array
121
+ - caller file, line, and function where available
122
+ - log level and handled/unhandled status
123
+ - component type, lex name, gem name, version, and source path metadata
124
+ - task and thread context
125
+ - stable error fingerprint for deduplication
126
+
109
127
  ### Writer Lambdas
110
128
 
111
129
  `log_writer` and `exception_writer` are pluggable lambda slots that replace the old Hooks system. Assign them to forward events to external systems:
112
130
 
113
131
  ```ruby
114
- Legion::Logging.exception_writer = ->(payload, routing_key:, headers:, properties:) { publish_to_amqp(payload) }
115
- Legion::Logging.log_writer = ->(context, routing_key:) { publish_log(context) }
132
+ Legion::Logging.exception_writer = lambda do |payload, routing_key:, headers:, properties:|
133
+ publish_to_amqp(payload, routing_key:, headers:, properties:)
134
+ end
135
+
136
+ Legion::Logging.log_writer = lambda do |context, routing_key:|
137
+ publish_log(context, routing_key:)
138
+ end
116
139
  ```
117
140
 
118
141
  ### EventBuilder
@@ -121,7 +144,9 @@ Legion::Logging.log_writer = ->(context, routing_key:) { publish_log(context) }
121
144
 
122
145
  ### Redactor
123
146
 
124
- `Legion::Logging::Redactor` redacts PII/PHI patterns (SSN, phone, MRN, DOB) plus Vault tokens, JWTs, bearer tokens, and lease IDs from log messages. Redaction is opt-in: load the module (for example via `require 'legion/logging/redactor'`) and enable it with `logging.redaction.enabled: true`. When loaded and enabled, it is wired into all log methods in the write path.
147
+ `Legion::Logging::Redactor` redacts PII/PHI patterns (SSN, email, phone, MRN, DOB, credit card) plus Vault tokens, JWTs, bearer tokens, `vault://` URIs, `lease://` URIs, and lease IDs from log messages. Redaction is opt-in for text log lines: load the module (for example via `require 'legion/logging/redactor'`) and enable it with `logging.redaction.enabled: true`. When loaded and enabled, it is wired into all log methods in the write path.
148
+
149
+ Structured exception events are redacted before publishing when the redactor is loaded. This includes event identity fields such as `user`, so email-shaped local usernames are not forwarded raw.
125
150
 
126
151
  ## Requirements
127
152
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'securerandom'
4
4
  require_relative 'tagged_logger'
5
+ require_relative 'method_tracer'
5
6
 
6
7
  module Legion
7
8
  module Logging
@@ -31,7 +32,6 @@ module Legion
31
32
  'middleware' => :middleware
32
33
  }.freeze
33
34
 
34
- EXCEPTION_BACKTRACE_LIMIT = 10
35
35
  EXCEPTION_PRIORITY = { warn: 0, error: 5, fatal: 9 }.freeze
36
36
  EXCEPTION_COLORS = {
37
37
  fatal: :darkred,
@@ -102,6 +102,14 @@ module Legion
102
102
  publish_exception(event, level) if structured_exception_support?
103
103
  end
104
104
 
105
+ def self.included(base)
106
+ MethodTracer.attach(base) if defined?(MethodTracer) && MethodTracer::ENABLED
107
+ end
108
+
109
+ def self.extended(base)
110
+ MethodTracer.attach(base, match_singleton: true) if defined?(MethodTracer) && MethodTracer::ENABLED
111
+ end
112
+
105
113
  private
106
114
 
107
115
  def build_exception_event(exception:, level:, spec:, handled:, task_id:, payload_summary:)
@@ -480,11 +488,7 @@ module Legion
480
488
  lines << " #{context_line}" unless context_line.empty?
481
489
 
482
490
  bt = exception.backtrace
483
- if bt&.any?
484
- bt.first(EXCEPTION_BACKTRACE_LIMIT).each { |frame| lines << " #{frame}" }
485
- remaining = bt.length - EXCEPTION_BACKTRACE_LIMIT
486
- lines << " ... #{remaining} more" if remaining.positive?
487
- end
491
+ bt.each { |frame| lines << " #{frame}" } if bt&.any?
488
492
 
489
493
  lines.join("\n")
490
494
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Logging
5
+ module MethodTracer
6
+ ENABLED = false
7
+ ATTACHED = {} # rubocop:disable Style/MutableConstant
8
+ ATTACHED_MUTEX = Mutex.new
9
+ private_constant :ATTACHED_MUTEX
10
+
11
+ def self.attach(base, match_singleton: false)
12
+ return unless ENABLED
13
+
14
+ ATTACHED_MUTEX.synchronize do
15
+ return if ATTACHED.key?(base)
16
+
17
+ base_name = base.to_s
18
+ tp = TracePoint.new(:call, :return) do |trace|
19
+ next unless trace.defined_class == base || (match_singleton && trace.defined_class == base.singleton_class)
20
+
21
+ stack = (Thread.current[:_legion_trace_stack] ||= [])
22
+
23
+ case trace.event
24
+ when :call
25
+ params = format_params(trace)
26
+ params_segment = params.empty? ? '' : ", #{params.join(', ')}"
27
+ indent = ' ' * stack.size
28
+ puts "#{indent}-> #{trace.method_id}, #{base_name}#{params_segment}"
29
+ stack.push(trace.method_id)
30
+ when :return
31
+ stack.pop
32
+ indent = ' ' * stack.size
33
+ puts "#{indent}<- #{trace.method_id}, #{base_name}"
34
+ end
35
+ end
36
+ tp.enable
37
+ ATTACHED[base] = tp
38
+ end
39
+ end
40
+
41
+ def self.detach(base)
42
+ ATTACHED_MUTEX.synchronize do
43
+ tp = ATTACHED.delete(base)
44
+ tp&.disable
45
+ end
46
+ end
47
+
48
+ def self.detach_all
49
+ ATTACHED_MUTEX.synchronize do
50
+ ATTACHED.each_value(&:disable)
51
+ ATTACHED.clear
52
+ end
53
+ end
54
+
55
+ def self.format_params(trace_point)
56
+ trace_point.parameters.filter_map do |type, name|
57
+ next unless name
58
+
59
+ val = begin
60
+ trace_point.binding.local_variable_get(name)
61
+ rescue StandardError
62
+ '?'
63
+ end
64
+ case type
65
+ when :req, :opt then "#{name}=#{val.inspect}"
66
+ when :keyreq, :key then "#{name}: #{val.inspect}"
67
+ when :rest then "*#{name}"
68
+ when :keyrest then "**#{name}"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -104,9 +104,15 @@ module Legion
104
104
  source_code_uri: nil, handled: false, payload_summary: nil,
105
105
  task_id: nil, **extra)
106
106
  level = level.to_sym if level.respond_to?(:to_sym)
107
- # 1. Log human-readable line to stdout/file (bypass writer callbacks)
107
+ # 1. Log human-readable line + backtrace to stdout/file (bypass writer callbacks)
108
108
  msg = exception.respond_to?(:message) ? exception.message : exception.to_s
109
109
  msg = maybe_redact(msg)
110
+ bt = Array(exception.backtrace)
111
+ if bt.any?
112
+ lines = ["#{exception.class}: #{msg}"]
113
+ bt.each { |frame| lines << " #{frame}" }
114
+ msg = lines.join("\n")
115
+ end
110
116
  log.public_send(level, msg) if respond_to?(:log) && log.respond_to?(level)
111
117
 
112
118
  # 2. Build rich exception event
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Logging
5
- VERSION = '1.5.0'
5
+ VERSION = '1.5.2'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legion-logging
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -66,6 +66,7 @@ files:
66
66
  - lib/legion/logging/helper.rb
67
67
  - lib/legion/logging/hooks.rb
68
68
  - lib/legion/logging/logger.rb
69
+ - lib/legion/logging/method_tracer.rb
69
70
  - lib/legion/logging/methods.rb
70
71
  - lib/legion/logging/multi_io.rb
71
72
  - lib/legion/logging/redactor.rb