legion-logging 1.2.6 → 1.2.7

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: e397ca81de1cb74f71dc9f220e59406a8f8e6cbf2d9aae6092918f428588bc60
4
+ data.tar.gz: e41480464a27679090cc3bbff835c9a9c5c5f2c52fb60edd1096d1c8e7a43031
5
5
  SHA512:
6
- metadata.gz: dae39ad0842e8fdf132993d465a9ff6bedab0e6174bf42c9f9ab4ffad98377d3352b6ecc3aca218480fb2c6be9eb36c9efb80c0230e0c95a2dbe2011da6461ad
7
- data.tar.gz: 07c5e18d680c68e83bd03a3d20f64a2cf9ab62c513a00773d5a55a58e9f7c3fcf0f98268b8b3a63de8e18509fe0a237922e4eec6de64132a815e970da96d2a33
6
+ metadata.gz: 355eaf1cfe6d31c27360aadc06bdd6a81b6fa07c380ca68c5fd645089fe8ba81521ffd7971c078ed5f124c9e28e73babcbb390fcffcca46f5648ad3ee2839d24
7
+ data.tar.gz: c062f3e91ccc3a322cbbf8a539321207505c7e663623027e7d59354c63ac3eb692b93389ced67be17c7614b1578021e07dc7956ad0f1469e6cd4adb57d262629
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Legion::Logging Changelog
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ## v1.2.7
6
+
7
+ ### Added
8
+ - `Legion::Logging::Hooks`: callback registry for fatal/error/warn log events
9
+ - `Legion::Logging::EventBuilder`: structured event payload builder with caller, exception, lex, and gem metadata
10
+ - `on_fatal`, `on_error`, `on_warn` registration methods on `Legion::Logging`
11
+ - `enable_hooks!`, `disable_hooks!`, `clear_hooks!` control methods
12
+ - Hook dispatch wired into `fatal`, `error`, `warn` methods in `Methods` module
13
+
3
14
  ## v1.2.6
4
15
 
5
16
  ### 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
@@ -0,0 +1,91 @@
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
44
+ nil
45
+ end
46
+ event[:node] = name if name
47
+ end
48
+
49
+ def add_caller_info(event, offset)
50
+ loc = caller_locations(offset + 1, 1)&.first
51
+ return unless loc
52
+
53
+ event[:caller] = {
54
+ file: loc.absolute_path || loc.path,
55
+ function: loc.base_label,
56
+ line: loc.lineno
57
+ }
58
+ end
59
+
60
+ def add_exception_info(event, message)
61
+ return unless message.is_a?(Exception)
62
+
63
+ event[:exception] = {
64
+ class: message.class.name,
65
+ message: message.message
66
+ }
67
+ event[:backtrace] = message.backtrace if message.backtrace
68
+ end
69
+
70
+ def add_gem_info(event, lex_source)
71
+ return unless lex_source
72
+
73
+ spec = Gem::Specification.find_by_name(lex_source)
74
+ event[:gem] = {
75
+ name: spec.name,
76
+ version: spec.version.to_s,
77
+ source_code_uri: spec.metadata['source_code_uri'],
78
+ homepage: spec.metadata['homepage_uri'] || spec.homepage,
79
+ path: spec.full_gem_path
80
+ }.compact
81
+ rescue Gem::MissingSpecError, ArgumentError
82
+ nil
83
+ end
84
+
85
+ def strip_ansi(str)
86
+ str.gsub(/\e\[[0-9;]*m/, '')
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,45 @@
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
39
+ nil
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ 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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Logging
5
- VERSION = '1.2.6'
5
+ VERSION = '1.2.7'
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.7
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