legion-logging 1.2.6 → 1.2.8

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: d4a463a1d341069c5c5ed0f15e98dfc25a3572e440bbba3f09d63ffc825c7f0f
4
- data.tar.gz: 5e6a1dbbec201fb5fcd4e4c6d000ecba84f53bf78a5f25f74e5b75145ff11683
3
+ metadata.gz: 518026f9d08e58ddd2a6c1f0727a92be011321202d5cd788db339678e65338af
4
+ data.tar.gz: 0d08f35ebdc02e31399e5ab4579c9e71a7aa1691bb549cd79eeda355715f45c8
5
5
  SHA512:
6
- metadata.gz: dae39ad0842e8fdf132993d465a9ff6bedab0e6174bf42c9f9ab4ffad98377d3352b6ecc3aca218480fb2c6be9eb36c9efb80c0230e0c95a2dbe2011da6461ad
7
- data.tar.gz: 07c5e18d680c68e83bd03a3d20f64a2cf9ab62c513a00773d5a55a58e9f7c3fcf0f98268b8b3a63de8e18509fe0a237922e4eec6de64132a815e970da96d2a33
6
+ metadata.gz: 7dd496ca2596dfd8a5a4e35093752f88c1e37924ce1595e5c53c4c7c8ab4c53f3e872104dcc9fd35fb1c3fa268ed72d1619c38ed0878336bc6a2fadfa76e08be
7
+ data.tar.gz: d39e9736a4738b87bb07204008d7c46b21a504022421b4612e480fcd7b74998fe946e10748f0cc9653e60d77c56a6d9fadef811d11e51fc9c4852d3b2a9beeee
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Legion::Logging Changelog
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ## [1.2.8] - 2026-03-22
6
+
7
+ ### Changed
8
+ - Added `warn` output to all silent rescue blocks in builder.rb, event_builder.rb, hooks.rb, redactor.rb, and siem_exporter.rb
9
+
10
+ ## v1.2.7
11
+
12
+ ### Added
13
+ - `Legion::Logging::Hooks`: callback registry for fatal/error/warn log events
14
+ - `Legion::Logging::EventBuilder`: structured event payload builder with caller, exception, lex, and gem metadata
15
+ - `on_fatal`, `on_error`, `on_warn` registration methods on `Legion::Logging`
16
+ - `enable_hooks!`, `disable_hooks!`, `clear_hooks!` control methods
17
+ - Hook dispatch wired into `fatal`, `error`, `warn` methods in `Methods` module
18
+
3
19
  ## v1.2.6
4
20
 
5
21
  ### Added
data/CLAUDE.md CHANGED
@@ -17,10 +17,14 @@ Ruby logging class for the LegionIO framework. Provides colorized console output
17
17
  Legion::Logging (singleton module)
18
18
  ├── Methods # Log level methods: debug, info, warn, error, fatal, unknown
19
19
  ├── Builder # Output destination (stdout/file), log level, formatter
20
+ ├── Hooks # Callback registry for fatal/error/warn events (on_fatal, on_error, on_warn)
21
+ ├── EventBuilder # Structured event payload builder (caller, exception, lex, gem metadata)
20
22
  ├── Logger # Core logger configuration and setup
21
23
  ├── MultiIO # Write to multiple destinations simultaneously
22
24
  ├── SIEMExporter # PHI-redacting SIEM export (Splunk HEC, ELK/OpenSearch)
23
- └── Version # VERSION constant (1.2.5)
25
+ ├── Shipper # Buffered log event forwarding (file/http transports)
26
+ ├── Redactor # PII/PHI pattern redaction
27
+ └── Version # VERSION constant
24
28
  ```
25
29
 
26
30
  ### Key Design Patterns
@@ -32,6 +36,8 @@ Legion::Logging (singleton module)
32
36
  - **Shared Interface**: Same method signature (`info`, `warn`, `error`, etc.) across all Legion components
33
37
  - **MultiIO**: Splits writes to stdout and a log file simultaneously (used by Builder when `log_file` is set)
34
38
  - **SIEMExporter**: PHI redaction (SSN, phone, MRN, DOB patterns), `export_to_splunk` (HEC), `format_for_elk`
39
+ - **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.
40
+ - **EventBuilder**: Builds structured event hashes from log context (caller location, exception info, lex identity, gem metadata). All from in-memory data, zero IO.
35
41
 
36
42
  ## Dependencies
37
43
 
@@ -49,6 +55,8 @@ Legion::Logging (singleton module)
49
55
  | `lib/legion/logging/logger.rb` | Core logger setup |
50
56
  | `lib/legion/logging/multi_io.rb` | Multi-output IO (write to multiple destinations simultaneously) |
51
57
  | `lib/legion/logging/siem_exporter.rb` | PHI-redacting SIEM export helpers (Splunk HEC, ELK format) |
58
+ | `lib/legion/logging/hooks.rb` | Callback registry (fatal/error/warn hook arrays, enable/disable/clear) |
59
+ | `lib/legion/logging/event_builder.rb` | Structured event payload builder |
52
60
  | `lib/legion/logging/version.rb` | VERSION constant |
53
61
 
54
62
  ## Role in LegionIO
@@ -28,7 +28,8 @@ module Legion
28
28
  }
29
29
  entry[:pid] = ::Process.pid if include_pid
30
30
  "#{::JSON.generate(entry)}\n"
31
- rescue StandardError
31
+ rescue StandardError => e
32
+ warn("Legion::Logging::Builder#json_format formatter failed: #{e.message}")
32
33
  "{\"timestamp\":\"#{datetime}\",\"level\":\"#{severity}\",\"message\":#{msg.to_s.dump}}\n"
33
34
  end
34
35
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Logging
5
+ module EventBuilder
6
+ class << self
7
+ def build(level:, message:, lex: nil, lex_segments: nil, context: nil, caller_offset: 2) # rubocop:disable Metrics/ParameterLists
8
+ event = base_fields(level, message)
9
+ event[:lex] = derive_lex_source(lex, lex_segments)
10
+ add_node(event)
11
+ add_caller_info(event, caller_offset)
12
+ add_exception_info(event, message)
13
+ add_gem_info(event, event[:lex])
14
+ event[:context] = context if context
15
+ event.compact
16
+ end
17
+
18
+ private
19
+
20
+ def base_fields(level, message)
21
+ {
22
+ timestamp: Time.now.utc.iso8601(3),
23
+ level: level,
24
+ message: message.is_a?(Exception) ? message.message : strip_ansi(message.to_s),
25
+ pid: ::Process.pid,
26
+ thread: Thread.current.object_id
27
+ }
28
+ end
29
+
30
+ def derive_lex_source(lex, lex_segments)
31
+ if lex_segments.is_a?(Array) && !lex_segments.empty?
32
+ "lex-#{lex_segments.join('-')}"
33
+ elsif lex && !lex.to_s.empty?
34
+ "lex-#{lex}"
35
+ end
36
+ end
37
+
38
+ def add_node(event)
39
+ return unless defined?(Legion::Settings)
40
+
41
+ name = begin
42
+ Legion::Settings[:client][:name]
43
+ rescue StandardError => e
44
+ warn("Legion::Logging::EventBuilder#add_node failed: #{e.message}")
45
+ nil
46
+ end
47
+ event[:node] = name if name
48
+ end
49
+
50
+ def add_caller_info(event, offset)
51
+ loc = caller_locations(offset + 1, 1)&.first
52
+ return unless loc
53
+
54
+ event[:caller] = {
55
+ file: loc.absolute_path || loc.path,
56
+ function: loc.base_label,
57
+ line: loc.lineno
58
+ }
59
+ end
60
+
61
+ def add_exception_info(event, message)
62
+ return unless message.is_a?(Exception)
63
+
64
+ event[:exception] = {
65
+ class: message.class.name,
66
+ message: message.message
67
+ }
68
+ event[:backtrace] = message.backtrace if message.backtrace
69
+ end
70
+
71
+ def add_gem_info(event, lex_source)
72
+ return unless lex_source
73
+
74
+ spec = Gem::Specification.find_by_name(lex_source)
75
+ event[:gem] = {
76
+ name: spec.name,
77
+ version: spec.version.to_s,
78
+ source_code_uri: spec.metadata['source_code_uri'],
79
+ homepage: spec.metadata['homepage_uri'] || spec.homepage,
80
+ path: spec.full_gem_path
81
+ }.compact
82
+ rescue Gem::MissingSpecError, ArgumentError => e
83
+ warn("Legion::Logging::EventBuilder#add_gem_info failed for #{lex_source}: #{e.message}")
84
+ nil
85
+ end
86
+
87
+ def strip_ansi(str)
88
+ str.gsub(/\e\[[0-9;]*m/, '')
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Logging
5
+ module Hooks
6
+ @hooks = { fatal: [], error: [], warn: [] }
7
+ @enabled = false
8
+
9
+ class << self
10
+ attr_reader :hooks
11
+
12
+ def enabled?
13
+ @enabled
14
+ end
15
+
16
+ def enable!
17
+ @enabled = true
18
+ end
19
+
20
+ def disable!
21
+ @enabled = false
22
+ end
23
+
24
+ def clear!
25
+ @hooks.each_value(&:clear)
26
+ end
27
+
28
+ def register(level, &block)
29
+ @hooks[level] << block
30
+ end
31
+
32
+ def fire(level, event)
33
+ return unless @enabled
34
+ return if @hooks[level].empty?
35
+
36
+ @hooks[level].each do |hook|
37
+ hook.call(event)
38
+ rescue StandardError => e
39
+ warn("Legion::Logging::Hooks#fire hook failed at level=#{level}: #{e.message}")
40
+ nil
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -12,6 +12,7 @@ module Legion
12
12
  include Legion::Logging::Builder
13
13
 
14
14
  def initialize(level: 'info', log_file: nil, log_stdout: nil, lex: nil, trace: false, extended: false, trace_size: 4, format: :text, **opts) # rubocop:disable Metrics/ParameterLists
15
+ @lex = lex
15
16
  set_log(logfile: log_file, log_stdout: log_stdout)
16
17
  log_level(level)
17
18
  log_format(format: format, lex: lex, extended: extended, **opts)
@@ -37,24 +37,30 @@ module Legion
37
37
  return unless log.level < 3
38
38
 
39
39
  message = yield if message.nil? && block_given?
40
+ raw = message
40
41
  message = Rainbow(message).yellow if @color
41
42
  log.warn(message)
43
+ fire_hooks(:warn, raw)
42
44
  end
43
45
 
44
46
  def error(message = nil)
45
47
  return unless log.level < 4
46
48
 
47
49
  message = yield if message.nil? && block_given?
50
+ raw = message
48
51
  message = Rainbow(message).red if @color
49
52
  log.error(message)
53
+ fire_hooks(:error, raw)
50
54
  end
51
55
 
52
56
  def fatal(message = nil)
53
57
  return unless log.level < 5
54
58
 
55
59
  message = yield if message.nil? && block_given?
60
+ raw = message
56
61
  message = Rainbow(message).darkred if @color
57
62
  log.fatal(message)
63
+ fire_hooks(:fatal, raw)
58
64
  end
59
65
 
60
66
  def unknown(message = nil)
@@ -77,6 +83,25 @@ module Legion
77
83
  Thread.current.object_id.to_s
78
84
  end
79
85
  end
86
+
87
+ private
88
+
89
+ def fire_hooks(level, message)
90
+ return unless Legion::Logging::Hooks.enabled?
91
+ return if Legion::Logging::Hooks.hooks[level].empty?
92
+
93
+ lex_val = instance_variable_defined?(:@lex) ? @lex : nil
94
+ lex_segs = instance_variable_defined?(:@lex_segments) ? @lex_segments : nil
95
+
96
+ event = Legion::Logging::EventBuilder.build(
97
+ level: level,
98
+ message: message,
99
+ lex: lex_val,
100
+ lex_segments: lex_segs,
101
+ caller_offset: 4
102
+ )
103
+ Legion::Logging::Hooks.fire(level, event)
104
+ end
80
105
  end
81
106
  end
82
107
  end
@@ -65,8 +65,8 @@ module Legion
65
65
 
66
66
  raw.each_with_object({}) do |(name, pattern_str), acc|
67
67
  acc[name] = Regexp.new(pattern_str)
68
- rescue RegexpError
69
- # skip invalid patterns
68
+ rescue RegexpError => e
69
+ warn("Legion::Logging::Redactor#custom_patterns skipping invalid pattern #{name}: #{e.message}")
70
70
  end
71
71
  end
72
72
 
@@ -32,6 +32,7 @@ module Legion
32
32
  http.request(req)
33
33
  end
34
34
  rescue StandardError => e
35
+ warn("Legion::Logging::SIEMExporter#export_to_splunk failed: #{e.message}")
35
36
  { error: e.message }
36
37
  end
37
38
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Logging
5
- VERSION = '1.2.6'
5
+ VERSION = '1.2.8'
6
6
  end
7
7
  end
@@ -4,6 +4,8 @@ require 'legion/logging/version'
4
4
  require 'legion/logging/logger'
5
5
  require 'legion/logging/methods'
6
6
  require 'legion/logging/builder'
7
+ require 'legion/logging/hooks'
8
+ require 'legion/logging/event_builder'
7
9
 
8
10
  require 'json'
9
11
  require 'logger'
@@ -15,6 +17,13 @@ module Legion
15
17
  include Legion::Logging::Methods
16
18
  include Legion::Logging::Builder
17
19
 
20
+ def on_fatal(&) = Hooks.register(:fatal, &)
21
+ def on_error(&) = Hooks.register(:error, &)
22
+ def on_warn(&) = Hooks.register(:warn, &)
23
+ def enable_hooks! = Hooks.enable!
24
+ def disable_hooks! = Hooks.disable!
25
+ def clear_hooks! = Hooks.clear!
26
+
18
27
  attr_reader :color
19
28
 
20
29
  def setup(level: 'info', format: :text, **options)
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.2.6
4
+ version: 1.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -58,6 +58,8 @@ files:
58
58
  - legion-logging.gemspec
59
59
  - lib/legion/logging.rb
60
60
  - lib/legion/logging/builder.rb
61
+ - lib/legion/logging/event_builder.rb
62
+ - lib/legion/logging/hooks.rb
61
63
  - lib/legion/logging/logger.rb
62
64
  - lib/legion/logging/methods.rb
63
65
  - lib/legion/logging/multi_io.rb