legion-logging 1.4.2 → 1.4.3
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/.github/workflows/ci.yml +19 -1
- data/CHANGELOG.md +41 -0
- data/Gemfile +1 -0
- data/lib/legion/logging/async_writer.rb +12 -2
- data/lib/legion/logging/builder.rb +38 -28
- data/lib/legion/logging/event_builder.rb +19 -6
- data/lib/legion/logging/helper.rb +277 -28
- data/lib/legion/logging/hooks.rb +72 -0
- data/lib/legion/logging/methods.rb +42 -13
- data/lib/legion/logging/settings.rb +16 -0
- data/lib/legion/logging/shipper/http_transport.rb +3 -3
- data/lib/legion/logging/tagged_logger.rb +98 -0
- data/lib/legion/logging/version.rb +1 -1
- data/lib/legion/logging.rb +28 -1
- data/lib/legion/service.rb +28 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 707d2460ca4e016ca62cc8ef83bf2d646819439ed67ef5549a164518ba2d3bf5
|
|
4
|
+
data.tar.gz: a013c59d975f901f25b153cbbfe4ed288dbedb262098627aed6d71baeb70031a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f5f3d87c609bca88c501e32d241fabc796e48b8aae8912fed39ac3615e0565f2a7d4672b66f12b0fdf606c6d3e0b40abb2b04b9b38d554edca4e9fa4b063a40a
|
|
7
|
+
data.tar.gz: 741697e72078cb1cbede684a8729d8a9a0b79c25a8beb4ed465aa53ae89711e11f482b9e93f434f51dae0778dfdf3bf49855282e80a7df17f98054d980d3e037
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -3,13 +3,31 @@ on:
|
|
|
3
3
|
push:
|
|
4
4
|
branches: [main]
|
|
5
5
|
pull_request:
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: '0 9 * * 1'
|
|
6
8
|
|
|
7
9
|
jobs:
|
|
8
10
|
ci:
|
|
9
11
|
uses: LegionIO/.github/.github/workflows/ci.yml@main
|
|
10
12
|
|
|
13
|
+
lint:
|
|
14
|
+
uses: LegionIO/.github/.github/workflows/lint-patterns.yml@main
|
|
15
|
+
|
|
16
|
+
security:
|
|
17
|
+
uses: LegionIO/.github/.github/workflows/security-scan.yml@main
|
|
18
|
+
|
|
19
|
+
version-changelog:
|
|
20
|
+
uses: LegionIO/.github/.github/workflows/version-changelog.yml@main
|
|
21
|
+
|
|
22
|
+
dependency-review:
|
|
23
|
+
uses: LegionIO/.github/.github/workflows/dependency-review.yml@main
|
|
24
|
+
|
|
25
|
+
stale:
|
|
26
|
+
if: github.event_name == 'schedule'
|
|
27
|
+
uses: LegionIO/.github/.github/workflows/stale.yml@main
|
|
28
|
+
|
|
11
29
|
release:
|
|
12
|
-
needs: ci
|
|
30
|
+
needs: [ci, lint]
|
|
13
31
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
14
32
|
uses: LegionIO/.github/.github/workflows/release.yml@main
|
|
15
33
|
secrets:
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Legion::Logging Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.3] - 2026-04-01
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `TaggedLogger` lightweight proxy: delegates to singleton for shared stdout/file/async output
|
|
7
|
+
- `Helper#derive_log_segments` with class-level `SEGMENT_CACHE` — auto-derives `[llm][router]` from namespace
|
|
8
|
+
- `Helper#with_log_context` for block-scoped method name thread-locals (`{dispatch}` in log output)
|
|
9
|
+
- `Helper#handle_exception` with direct EventBuilder calls, per-line Rainbow coloring, structured AMQP publish
|
|
10
|
+
- `Helper.current_log_method`, `.current_log_segments`, `.current_context` thread-local readers
|
|
11
|
+
- `Legion::Logging::Settings` module with logger defaults
|
|
12
|
+
- `COMPONENT_MAP` with 18 component types (runners, actors, hooks, absorbers, tools, adapters, middleware, etc.)
|
|
13
|
+
- `EXCEPTION_COLORS` map for per-level exception coloring (bold first line, faint backtrace)
|
|
14
|
+
- `Thread.current[:legion_context]` support for wire protocol fields (task_id, conversation_id, chain_id)
|
|
15
|
+
- Redaction applied to exception stdout output when redaction is enabled
|
|
16
|
+
- Method context (`legion_log_method`) included in structured exception events
|
|
17
|
+
- `AsyncWriter::LogEntry` carries `segments` and `method_ctx` for thread-local propagation to writer thread
|
|
18
|
+
- `Builder#resolve_lex_tag` and `#build_runner_trace` extracted from `text_format`
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- `Helper#log` returns `TaggedLogger` instead of `Logger.new` (shared output, one async thread)
|
|
22
|
+
- `Helper#log_name`/`gem_name`/`gem_spec` replace `log_lex_name`/`lex_gem_name`/`gem_spec_for_lex` with multi-prefix resolution
|
|
23
|
+
- `gem_name` and `gem_spec` memoized per instance
|
|
24
|
+
- `COMPONENT_REGEX` in Methods expanded from 5 to 18 component types
|
|
25
|
+
- `build_writer_context` reads `Thread.current[:legion_log_segments]` instead of stale `@lex_segments` ivar
|
|
26
|
+
- `Builder#output` delegates to `set_log` (was parallel implementation)
|
|
27
|
+
- `Builder#caller_locations` allocates single frame instead of full stack
|
|
28
|
+
- Unknown log level strings default to INFO instead of DEBUG
|
|
29
|
+
- `EventBuilder#legion_versions` and `#resolve_gem_spec` memoized
|
|
30
|
+
- `EXCEPTION_PRIORITY` extracted to frozen constant in Methods (was inline hash allocation per call)
|
|
31
|
+
- `text_format` and `json_format` in Builder read thread-locals for segments and method context
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- `fire_log_writer` rescue no longer references undefined `routing_key` variable
|
|
35
|
+
- Splunk auth header in `http_transport` — `apply_auth` receives actual URI instead of always evaluating against `URI('/')`
|
|
36
|
+
- `TaggedLogger#initialize` accepts `**_opts` splat for unexpected settings keys
|
|
37
|
+
- `TaggedLogger#trace` guards nil `size` to prevent `TypeError` on `caller_locations`
|
|
38
|
+
|
|
39
|
+
### Removed
|
|
40
|
+
- `TaggedLogger#runner_exception` (runner business logic, not logging concern)
|
|
41
|
+
- `TaggedLogger#log_exception` (use `Helper#handle_exception` instead)
|
|
42
|
+
- `Builder#log_level` no-op `@log = log` self-assignment
|
|
43
|
+
|
|
3
44
|
## [1.4.2] - 2026-03-28
|
|
4
45
|
|
|
5
46
|
### Added
|
data/Gemfile
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'methods'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module Logging
|
|
5
7
|
class AsyncWriter
|
|
6
|
-
LogEntry = ::Data.define(:level, :message, :writer_context)
|
|
8
|
+
LogEntry = ::Data.define(:level, :message, :writer_context, :segments, :method_ctx)
|
|
7
9
|
SHUTDOWN = :shutdown
|
|
8
10
|
|
|
9
11
|
def initialize(logger, buffer_size: 10_000)
|
|
@@ -54,10 +56,17 @@ module Legion
|
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
def write_entry(entry)
|
|
59
|
+
prev_segments = Thread.current[:legion_log_segments]
|
|
60
|
+
prev_method_ctx = Thread.current[:legion_log_method]
|
|
61
|
+
Thread.current[:legion_log_segments] = entry.segments
|
|
62
|
+
Thread.current[:legion_log_method] = entry.method_ctx
|
|
57
63
|
@logger.send(entry.level, entry.message)
|
|
58
64
|
fire_writer(entry) if entry.writer_context
|
|
59
65
|
rescue StandardError => e
|
|
60
66
|
warn("legion-log-writer error: #{e.message} (#{e.backtrace&.first})")
|
|
67
|
+
ensure
|
|
68
|
+
Thread.current[:legion_log_segments] = prev_segments
|
|
69
|
+
Thread.current[:legion_log_method] = prev_method_ctx
|
|
61
70
|
end
|
|
62
71
|
|
|
63
72
|
def drain
|
|
@@ -74,9 +83,10 @@ module Legion
|
|
|
74
83
|
event = ctx[:event]
|
|
75
84
|
level = ctx[:level]
|
|
76
85
|
lex_name = event[:lex] || 'core'
|
|
77
|
-
component = event.dig(:caller, :file).to_s[
|
|
86
|
+
component = event.dig(:caller, :file).to_s[Legion::Logging::Methods::COMPONENT_REGEX, 1] || 'unknown'
|
|
78
87
|
routing_key = "legion.logging.log.#{level}.#{lex_name}.#{component}"
|
|
79
88
|
Legion::Logging.log_writer.call(event, routing_key: routing_key)
|
|
89
|
+
Legion::Logging::Hooks.fire(level, entry.message, event) if defined?(Legion::Logging::Hooks)
|
|
80
90
|
rescue StandardError => e
|
|
81
91
|
warn("legion-log-writer writer error: #{e.message}")
|
|
82
92
|
end
|
|
@@ -27,6 +27,10 @@ module Legion
|
|
|
27
27
|
thread: Thread.current.object_id
|
|
28
28
|
}
|
|
29
29
|
entry[:pid] = ::Process.pid if include_pid
|
|
30
|
+
segments = Thread.current[:legion_log_segments]
|
|
31
|
+
entry[:segments] = segments if segments
|
|
32
|
+
method_ctx = Thread.current[:legion_log_method]
|
|
33
|
+
entry[:method] = method_ctx if method_ctx
|
|
30
34
|
"#{::JSON.generate(entry)}\n"
|
|
31
35
|
rescue StandardError => e
|
|
32
36
|
warn("Legion::Logging::Builder#json_format formatter failed: #{e.message}")
|
|
@@ -36,24 +40,12 @@ module Legion
|
|
|
36
40
|
|
|
37
41
|
def text_format(include_pid: false, **options)
|
|
38
42
|
log.formatter = proc do |severity, datetime, _progname, msg|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"[#{options[:lex]}]"
|
|
43
|
-
end
|
|
44
|
-
unless options[:lex_name].nil?
|
|
45
|
-
loc = caller_locations[4]
|
|
46
|
-
path = loc.to_s.split('/').last(2)
|
|
47
|
-
runner_trace = {
|
|
48
|
-
type: path[0],
|
|
49
|
-
file: File.basename(loc.path, '.*'),
|
|
50
|
-
function: loc.base_label,
|
|
51
|
-
line_number: loc.lineno
|
|
52
|
-
}
|
|
53
|
-
end
|
|
43
|
+
lex_name = resolve_lex_tag(options)
|
|
44
|
+
runner_trace = build_runner_trace if lex_name
|
|
45
|
+
|
|
54
46
|
string = "[#{datetime}]"
|
|
55
47
|
string.concat("[#{::Process.pid}]") if include_pid
|
|
56
|
-
string.concat(
|
|
48
|
+
string.concat(lex_name) if lex_name
|
|
57
49
|
if runner_trace.is_a?(Hash) && (options[:extended] || severity == 'debug')
|
|
58
50
|
string.concat("[#{runner_trace[:type]}:#{runner_trace[:file]}:#{runner_trace[:function]}:#{runner_trace[:line_number]}]")
|
|
59
51
|
end
|
|
@@ -62,17 +54,36 @@ module Legion
|
|
|
62
54
|
end
|
|
63
55
|
end
|
|
64
56
|
|
|
57
|
+
def resolve_lex_tag(options)
|
|
58
|
+
segments = Thread.current[:legion_log_segments]
|
|
59
|
+
tag = if segments
|
|
60
|
+
segments.map { |s| "[#{s}]" }.join
|
|
61
|
+
elsif options.key?(:lex_segments)
|
|
62
|
+
options[:lex_segments].map { |s| "[#{s}]" }.join
|
|
63
|
+
elsif options.key?(:lex) && !options[:lex].nil?
|
|
64
|
+
"[#{options[:lex]}]"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
method_ctx = Thread.current[:legion_log_method]
|
|
68
|
+
tag = "#{tag}{#{method_ctx}}" if tag && method_ctx
|
|
69
|
+
tag
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def build_runner_trace
|
|
73
|
+
loc = caller_locations(6, 1)&.first
|
|
74
|
+
return unless loc
|
|
75
|
+
|
|
76
|
+
path = loc.to_s.split('/').last(2)
|
|
77
|
+
{
|
|
78
|
+
type: path[0],
|
|
79
|
+
file: File.basename(loc.path, '.*'),
|
|
80
|
+
function: loc.base_label,
|
|
81
|
+
line_number: loc.lineno
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
65
85
|
def output(**options)
|
|
66
|
-
|
|
67
|
-
path = prepare_log_path(options[:log_file])
|
|
68
|
-
require_relative 'multi_io'
|
|
69
|
-
io = MultiIO.new($stdout, File.open(path, 'a'))
|
|
70
|
-
@log = ::Logger.new(io)
|
|
71
|
-
elsif options[:log_file]
|
|
72
|
-
@log = ::Logger.new(prepare_log_path(options[:log_file]))
|
|
73
|
-
else
|
|
74
|
-
@log = ::Logger.new($stdout)
|
|
75
|
-
end
|
|
86
|
+
set_log(logfile: options[:log_file], log_stdout: options[:log_stdout])
|
|
76
87
|
end
|
|
77
88
|
|
|
78
89
|
def log
|
|
@@ -120,10 +131,9 @@ module Legion
|
|
|
120
131
|
if level.is_a? Integer
|
|
121
132
|
level
|
|
122
133
|
else
|
|
123
|
-
|
|
134
|
+
1
|
|
124
135
|
end
|
|
125
136
|
end
|
|
126
|
-
@log = log
|
|
127
137
|
end
|
|
128
138
|
|
|
129
139
|
def async?
|
|
@@ -12,6 +12,9 @@ module Legion
|
|
|
12
12
|
MAX_TOTAL_BYTES = 65_536
|
|
13
13
|
BACKTRACE_FALLBACK_FRAMES = 20
|
|
14
14
|
|
|
15
|
+
GEM_SPEC_CACHE_MUTEX = Mutex.new
|
|
16
|
+
private_constant :GEM_SPEC_CACHE_MUTEX
|
|
17
|
+
|
|
15
18
|
class << self
|
|
16
19
|
def build(level:, message:, lex: nil, lex_segments: nil, context: nil, category: nil, caller_offset: 2)
|
|
17
20
|
event = base_fields(level, message)
|
|
@@ -187,12 +190,18 @@ module Legion
|
|
|
187
190
|
end
|
|
188
191
|
|
|
189
192
|
def resolve_gem_spec(name)
|
|
190
|
-
|
|
191
|
-
|
|
193
|
+
cache = (@gem_spec_cache ||= {})
|
|
194
|
+
return cache[name] if cache.key?(name)
|
|
195
|
+
|
|
196
|
+
spec = nil
|
|
197
|
+
["lex-#{name}", "legion-#{name}", name].each do |candidate|
|
|
198
|
+
spec = Gem::Specification.find_by_name(candidate)
|
|
199
|
+
break
|
|
192
200
|
rescue Gem::MissingSpecError
|
|
193
201
|
next
|
|
194
202
|
end
|
|
195
|
-
|
|
203
|
+
|
|
204
|
+
GEM_SPEC_CACHE_MUTEX.synchronize { cache[name] = spec }
|
|
196
205
|
end
|
|
197
206
|
|
|
198
207
|
def strip_ansi(str)
|
|
@@ -254,9 +263,13 @@ module Legion
|
|
|
254
263
|
end
|
|
255
264
|
|
|
256
265
|
def legion_versions
|
|
257
|
-
Gem::Specification
|
|
258
|
-
|
|
259
|
-
|
|
266
|
+
@legion_versions ||= Gem::Specification
|
|
267
|
+
.select { |s| s.name.start_with?('legion-', 'lex-') }
|
|
268
|
+
.to_h do |s|
|
|
269
|
+
[s.name,
|
|
270
|
+
s.version.to_s]
|
|
271
|
+
end
|
|
272
|
+
.freeze
|
|
260
273
|
end
|
|
261
274
|
|
|
262
275
|
def truncate_bytes(str, max)
|
|
@@ -1,47 +1,296 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
require_relative 'tagged_logger'
|
|
5
|
+
|
|
3
6
|
module Legion
|
|
4
7
|
module Logging
|
|
5
8
|
module Helper
|
|
9
|
+
SEGMENT_CACHE = {} # rubocop:disable Style/MutableConstant
|
|
10
|
+
SEGMENT_CACHE_MUTEX = Mutex.new
|
|
11
|
+
private_constant :SEGMENT_CACHE_MUTEX
|
|
12
|
+
COMPONENT_MAP = {
|
|
13
|
+
'runners' => :runner,
|
|
14
|
+
'actors' => :actor,
|
|
15
|
+
'actor' => :actor,
|
|
16
|
+
'helpers' => :helper,
|
|
17
|
+
'hooks' => :hook,
|
|
18
|
+
'absorbers' => :absorber,
|
|
19
|
+
'matchers' => :matcher,
|
|
20
|
+
'transport' => :transport,
|
|
21
|
+
'exchanges' => :exchange,
|
|
22
|
+
'queues' => :queue,
|
|
23
|
+
'messages' => :message,
|
|
24
|
+
'data' => :data,
|
|
25
|
+
'builders' => :builder,
|
|
26
|
+
'tools' => :tool,
|
|
27
|
+
'adapters' => :adapter,
|
|
28
|
+
'engines' => :engine,
|
|
29
|
+
'formatters' => :formatter,
|
|
30
|
+
'parsers' => :parser,
|
|
31
|
+
'middleware' => :middleware
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
EXCEPTION_BACKTRACE_LIMIT = 10
|
|
35
|
+
EXCEPTION_PRIORITY = { warn: 0, error: 5, fatal: 9 }.freeze
|
|
36
|
+
EXCEPTION_COLORS = {
|
|
37
|
+
fatal: :darkred,
|
|
38
|
+
error: :red,
|
|
39
|
+
warn: :yellow,
|
|
40
|
+
debug: :aqua,
|
|
41
|
+
unknown: :magenta
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
def self.current_log_method
|
|
45
|
+
Thread.current[:legion_log_method]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.current_log_segments
|
|
49
|
+
Thread.current[:legion_log_segments]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.current_context
|
|
53
|
+
Thread.current[:legion_context]
|
|
54
|
+
end
|
|
55
|
+
|
|
6
56
|
def log
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
logger_hash = if respond_to?(:segments)
|
|
10
|
-
{ lex_segments: Array(segments) }
|
|
11
|
-
else
|
|
12
|
-
{ lex: derive_log_tag }
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
if respond_to?(:settings) && settings.is_a?(Hash) && settings.key?(:logger)
|
|
16
|
-
ls = settings[:logger]
|
|
17
|
-
logger_hash[:level] = ls[:level] if ls.key?(:level)
|
|
18
|
-
logger_hash[:log_file] = ls[:log_file] if ls.key?(:log_file)
|
|
19
|
-
logger_hash[:trace] = ls[:trace] if ls.key?(:trace)
|
|
20
|
-
logger_hash[:extended] = ls[:extended] if ls.key?(:extended)
|
|
21
|
-
end
|
|
57
|
+
@log ||= Legion::Logging::TaggedLogger.new(segments: derive_log_segments, **resolve_logger_settings)
|
|
58
|
+
end
|
|
22
59
|
|
|
23
|
-
|
|
60
|
+
def with_log_context(method_name)
|
|
61
|
+
prev = Thread.current[:legion_log_method]
|
|
62
|
+
Thread.current[:legion_log_method] = method_name.to_s
|
|
63
|
+
yield
|
|
64
|
+
ensure
|
|
65
|
+
Thread.current[:legion_log_method] = prev
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def handle_exception(exception, task_id: nil, level: :error, handled: true, **opts)
|
|
69
|
+
segments = derive_log_segments
|
|
70
|
+
spec = gem_spec
|
|
71
|
+
ctx = Thread.current[:legion_context] || {}
|
|
72
|
+
|
|
73
|
+
event = Legion::Logging::EventBuilder.build_exception(
|
|
74
|
+
exception: exception,
|
|
75
|
+
level: level,
|
|
76
|
+
lex: log_name,
|
|
77
|
+
component_type: derive_component_type,
|
|
78
|
+
gem_name: gem_name,
|
|
79
|
+
lex_version: spec&.version&.to_s,
|
|
80
|
+
gem_path: spec&.full_gem_path,
|
|
81
|
+
source_code_uri: spec&.metadata&.[]('source_code_uri'),
|
|
82
|
+
handled: handled,
|
|
83
|
+
task_id: task_id || ctx[:task_id],
|
|
84
|
+
payload_summary: opts.empty? ? nil : opts,
|
|
85
|
+
caller_offset: 3
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
event[:conversation_id] ||= ctx[:conversation_id]
|
|
89
|
+
event[:chain_id] ||= ctx[:chain_id]
|
|
90
|
+
event[:log_segments] = segments
|
|
91
|
+
event[:method] = Thread.current[:legion_log_method]
|
|
92
|
+
|
|
93
|
+
event = Legion::Logging::Redactor.redact(event) if defined?(Legion::Logging::Redactor)
|
|
94
|
+
|
|
95
|
+
write_exception_to_log(exception, event, level, segments)
|
|
96
|
+
publish_exception(event, level)
|
|
24
97
|
end
|
|
25
98
|
|
|
26
99
|
private
|
|
27
100
|
|
|
28
|
-
def
|
|
101
|
+
def derive_log_segments
|
|
102
|
+
key = respond_to?(:ancestors) ? ancestors.first : self.class
|
|
103
|
+
return SEGMENT_CACHE[key] if SEGMENT_CACHE.key?(key)
|
|
104
|
+
|
|
105
|
+
segments = begin
|
|
106
|
+
parts = key.to_s.split('::')
|
|
107
|
+
parts.shift if parts.first == 'Legion'
|
|
108
|
+
parts.shift if parts.first == 'Extensions'
|
|
109
|
+
parts.map! do |p|
|
|
110
|
+
p.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
111
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
112
|
+
.downcase
|
|
113
|
+
end
|
|
114
|
+
parts.freeze
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
SEGMENT_CACHE_MUTEX.synchronize { SEGMENT_CACHE[key] ||= segments }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def derive_component_type
|
|
121
|
+
segments = derive_log_segments
|
|
122
|
+
match = segments.find { |s| COMPONENT_MAP.key?(s) }
|
|
123
|
+
return COMPONENT_MAP[match] if match
|
|
124
|
+
|
|
125
|
+
segments.last&.to_sym || :unknown
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def log_name
|
|
29
129
|
if respond_to?(:lex_filename)
|
|
30
130
|
fname = lex_filename
|
|
31
131
|
return fname.is_a?(Array) ? fname.first : fname
|
|
32
132
|
end
|
|
33
133
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
134
|
+
derive_log_segments.first
|
|
135
|
+
rescue StandardError
|
|
136
|
+
nil
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def gem_name
|
|
140
|
+
@gem_name_resolved ? @gem_name_value : resolve_gem_name
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def gem_spec
|
|
144
|
+
@gem_spec_resolved ? @gem_spec_value : resolve_gem_spec
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def resolve_gem_name
|
|
148
|
+
@gem_name_resolved = true
|
|
149
|
+
base = log_name
|
|
150
|
+
@gem_name_value = if base
|
|
151
|
+
%W[lex-#{base} legion-#{base} #{base}].find do |candidate|
|
|
152
|
+
Gem::Specification.find_by_name(candidate)
|
|
153
|
+
candidate
|
|
154
|
+
rescue Gem::MissingSpecError
|
|
155
|
+
nil
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
rescue StandardError
|
|
159
|
+
@gem_name_value = nil
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def resolve_gem_spec
|
|
163
|
+
@gem_spec_resolved = true
|
|
164
|
+
name = gem_name
|
|
165
|
+
@gem_spec_value = name ? Gem::Specification.find_by_name(name) : nil
|
|
166
|
+
rescue Gem::MissingSpecError
|
|
167
|
+
@gem_spec_value = nil
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def settings
|
|
171
|
+
{ logger: logger_settings }
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def logger_settings
|
|
175
|
+
return Legion::Settings[:logging] if defined?(Legion::Settings) && Legion::Settings[:logging].is_a?(Hash)
|
|
176
|
+
|
|
177
|
+
Legion::Logging::Settings.default
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def resolve_logger_settings
|
|
181
|
+
s = settings
|
|
182
|
+
return Legion::Logging::Settings.default unless s.is_a?(Hash)
|
|
183
|
+
|
|
184
|
+
raw = s[:logger]
|
|
185
|
+
raw.is_a?(Hash) ? raw : Legion::Logging::Settings.default
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# -- Exception stdout/file output --
|
|
189
|
+
|
|
190
|
+
def write_exception_to_log(exception, event, level, segments)
|
|
191
|
+
prev_segs = Thread.current[:legion_log_segments]
|
|
192
|
+
Thread.current[:legion_log_segments] = segments
|
|
193
|
+
|
|
194
|
+
message = format_exception_output(exception, event)
|
|
195
|
+
message = Legion::Logging::Redactor.redact_string(message) if defined?(Legion::Logging::Redactor) && redaction_enabled?
|
|
196
|
+
message = colorize_exception(message, level) if Legion::Logging.color
|
|
197
|
+
|
|
198
|
+
Legion::Logging.log.public_send(level, message)
|
|
199
|
+
ensure
|
|
200
|
+
Thread.current[:legion_log_segments] = prev_segs
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def format_exception_output(exception, event)
|
|
204
|
+
lines = ["#{exception.class}: #{exception.message}"]
|
|
205
|
+
|
|
206
|
+
context_line = build_context_line(event)
|
|
207
|
+
lines << " #{context_line}" unless context_line.empty?
|
|
208
|
+
|
|
209
|
+
bt = exception.backtrace
|
|
210
|
+
if bt&.any?
|
|
211
|
+
bt.first(EXCEPTION_BACKTRACE_LIMIT).each { |frame| lines << " #{frame}" }
|
|
212
|
+
remaining = bt.length - EXCEPTION_BACKTRACE_LIMIT
|
|
213
|
+
lines << " ... #{remaining} more" if remaining.positive?
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
lines.join("\n")
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def colorize_exception(message, level)
|
|
220
|
+
color = EXCEPTION_COLORS[level] || :red
|
|
221
|
+
lines = message.split("\n")
|
|
222
|
+
lines[0] = Rainbow(lines[0]).color(color).bright
|
|
223
|
+
lines[1..].each_with_index do |line, i|
|
|
224
|
+
lines[i + 1] = Rainbow(line).color(color).faint
|
|
225
|
+
end
|
|
226
|
+
lines.join("\n")
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def build_context_line(event)
|
|
230
|
+
parts = []
|
|
231
|
+
gn = event[:gem_name]
|
|
232
|
+
gv = event[:lex_version]
|
|
233
|
+
parts << (gv ? "#{gn}@#{gv}" : gn.to_s) if gn
|
|
234
|
+
parts << "task:#{event[:task_id]}" if event[:task_id]
|
|
235
|
+
parts << "conversation:#{event[:conversation_id]}" if event[:conversation_id]
|
|
236
|
+
parts << "chain:#{event[:chain_id]}" if event[:chain_id]
|
|
237
|
+
parts.join(' | ')
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def redaction_enabled?
|
|
241
|
+
return false unless defined?(Legion::Settings)
|
|
242
|
+
|
|
243
|
+
loader = Legion::Settings.instance_variable_get(:@loader)
|
|
244
|
+
return false unless loader
|
|
245
|
+
|
|
246
|
+
loader.dig(:logging, :redaction, :enabled) == true
|
|
247
|
+
rescue StandardError
|
|
248
|
+
false
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# -- Exception structured publish --
|
|
252
|
+
|
|
253
|
+
def publish_exception(event, level)
|
|
254
|
+
lex_name = event[:lex] || 'core'
|
|
255
|
+
comp = event[:component_type] || :unknown
|
|
256
|
+
routing_key = "legion.logging.exception.#{level}.#{lex_name}.#{comp}"
|
|
257
|
+
|
|
258
|
+
headers = build_exception_headers(event, comp, level)
|
|
259
|
+
properties = build_exception_properties(event, level)
|
|
260
|
+
|
|
261
|
+
Legion::Logging.exception_writer.call(event, routing_key: routing_key, headers: headers, properties: properties)
|
|
262
|
+
rescue StandardError => e
|
|
263
|
+
Legion::Logging.warn("Failed to publish exception event: #{e.class}: #{e.message}")
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def build_exception_headers(event, comp, level)
|
|
267
|
+
headers = {
|
|
268
|
+
'x-error-fingerprint' => event[:error_fingerprint],
|
|
269
|
+
'x-exception-class' => event[:exception_class],
|
|
270
|
+
'x-handled' => event[:handled].to_s,
|
|
271
|
+
'x-gem-name' => event[:gem_name].to_s,
|
|
272
|
+
'x-lex-version' => event[:lex_version].to_s,
|
|
273
|
+
'x-component-type' => comp.to_s,
|
|
274
|
+
'x-level' => level.to_s
|
|
275
|
+
}
|
|
276
|
+
headers['x-task-id'] = event[:task_id].to_s if event[:task_id]
|
|
277
|
+
headers['x-conversation-id'] = event[:conversation_id].to_s if event[:conversation_id]
|
|
278
|
+
headers['x-chain-id'] = event[:chain_id].to_s if event[:chain_id]
|
|
279
|
+
headers['x-user'] = event[:user].to_s if event[:user]
|
|
280
|
+
headers
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def build_exception_properties(event, level)
|
|
284
|
+
{
|
|
285
|
+
content_type: 'application/json',
|
|
286
|
+
message_id: SecureRandom.uuid,
|
|
287
|
+
correlation_id: event[:error_fingerprint],
|
|
288
|
+
timestamp: Time.now.to_i,
|
|
289
|
+
app_id: 'legionio',
|
|
290
|
+
type: 'exception_event',
|
|
291
|
+
priority: EXCEPTION_PRIORITY[level] || 5,
|
|
292
|
+
delivery_mode: 2
|
|
293
|
+
}
|
|
45
294
|
end
|
|
46
295
|
end
|
|
47
296
|
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Logging
|
|
5
|
+
module Hooks
|
|
6
|
+
class << self
|
|
7
|
+
def on_fatal(&block)
|
|
8
|
+
fatal_hooks << block
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def on_error(&block)
|
|
12
|
+
error_hooks << block
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def on_warn(&block)
|
|
16
|
+
warn_hooks << block
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def fire(level, message, event)
|
|
20
|
+
return unless @enabled
|
|
21
|
+
|
|
22
|
+
hooks_for(level).each do |hook|
|
|
23
|
+
hook.call(message, event)
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
warn("Legion::Logging::Hooks#fire callback failed: #{e.message}")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def enable_hooks!
|
|
30
|
+
@enabled = true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def disable_hooks!
|
|
34
|
+
@enabled = false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def enabled?
|
|
38
|
+
@enabled || false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def clear_hooks!
|
|
42
|
+
@fatal_hooks = []
|
|
43
|
+
@error_hooks = []
|
|
44
|
+
@warn_hooks = []
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def hooks_for(level)
|
|
50
|
+
case level.to_sym
|
|
51
|
+
when :fatal then fatal_hooks
|
|
52
|
+
when :error then error_hooks
|
|
53
|
+
when :warn then warn_hooks
|
|
54
|
+
else []
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def fatal_hooks
|
|
59
|
+
@fatal_hooks ||= []
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def error_hooks
|
|
63
|
+
@error_hooks ||= []
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def warn_hooks
|
|
67
|
+
@warn_hooks ||= []
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -5,6 +5,13 @@ require 'securerandom'
|
|
|
5
5
|
module Legion
|
|
6
6
|
module Logging
|
|
7
7
|
module Methods
|
|
8
|
+
COMPONENT_REGEX = %r{
|
|
9
|
+
/(runners|actors|actor|helpers|hooks|absorbers|matchers|transport|
|
|
10
|
+
exchanges|queues|messages|data|builders|tools|adapters|engines|
|
|
11
|
+
formatters|parsers|middleware)/
|
|
12
|
+
}x
|
|
13
|
+
EXCEPTION_PRIORITY = { warn: 0, error: 5, fatal: 9 }.freeze
|
|
14
|
+
|
|
8
15
|
def trace(raw_message = nil, size: @trace_size, log_caller: true)
|
|
9
16
|
return unless @trace_enabled
|
|
10
17
|
|
|
@@ -27,7 +34,11 @@ module Legion
|
|
|
27
34
|
message = Rainbow(message).blue if @color
|
|
28
35
|
writer = @async_writer
|
|
29
36
|
if writer&.alive?
|
|
30
|
-
writer.push(AsyncWriter::LogEntry.new(
|
|
37
|
+
writer.push(AsyncWriter::LogEntry.new(
|
|
38
|
+
level: :debug, message: message, writer_context: nil,
|
|
39
|
+
segments: Thread.current[:legion_log_segments],
|
|
40
|
+
method_ctx: Thread.current[:legion_log_method]
|
|
41
|
+
))
|
|
31
42
|
else
|
|
32
43
|
log.debug(message)
|
|
33
44
|
end
|
|
@@ -41,7 +52,11 @@ module Legion
|
|
|
41
52
|
message = Rainbow(message).green if @color
|
|
42
53
|
writer = @async_writer
|
|
43
54
|
if writer&.alive?
|
|
44
|
-
writer.push(AsyncWriter::LogEntry.new(
|
|
55
|
+
writer.push(AsyncWriter::LogEntry.new(
|
|
56
|
+
level: :info, message: message, writer_context: nil,
|
|
57
|
+
segments: Thread.current[:legion_log_segments],
|
|
58
|
+
method_ctx: Thread.current[:legion_log_method]
|
|
59
|
+
))
|
|
45
60
|
else
|
|
46
61
|
log.info(message)
|
|
47
62
|
end
|
|
@@ -57,7 +72,11 @@ module Legion
|
|
|
57
72
|
writer = @async_writer
|
|
58
73
|
if writer&.alive?
|
|
59
74
|
ctx = build_writer_context(:warn, raw)
|
|
60
|
-
writer.push(AsyncWriter::LogEntry.new(
|
|
75
|
+
writer.push(AsyncWriter::LogEntry.new(
|
|
76
|
+
level: :warn, message: message, writer_context: ctx,
|
|
77
|
+
segments: Thread.current[:legion_log_segments],
|
|
78
|
+
method_ctx: Thread.current[:legion_log_method]
|
|
79
|
+
))
|
|
61
80
|
else
|
|
62
81
|
log.warn(message)
|
|
63
82
|
fire_log_writer(:warn, raw)
|
|
@@ -74,7 +93,11 @@ module Legion
|
|
|
74
93
|
writer = @async_writer
|
|
75
94
|
if writer&.alive?
|
|
76
95
|
ctx = build_writer_context(:error, raw)
|
|
77
|
-
writer.push(AsyncWriter::LogEntry.new(
|
|
96
|
+
writer.push(AsyncWriter::LogEntry.new(
|
|
97
|
+
level: :error, message: message, writer_context: ctx,
|
|
98
|
+
segments: Thread.current[:legion_log_segments],
|
|
99
|
+
method_ctx: Thread.current[:legion_log_method]
|
|
100
|
+
))
|
|
78
101
|
else
|
|
79
102
|
log.error(message)
|
|
80
103
|
fire_log_writer(:error, raw)
|
|
@@ -98,7 +121,11 @@ module Legion
|
|
|
98
121
|
message = Rainbow(message).purple if @color
|
|
99
122
|
writer = @async_writer
|
|
100
123
|
if writer&.alive?
|
|
101
|
-
writer.push(AsyncWriter::LogEntry.new(
|
|
124
|
+
writer.push(AsyncWriter::LogEntry.new(
|
|
125
|
+
level: :unknown, message: message, writer_context: nil,
|
|
126
|
+
segments: Thread.current[:legion_log_segments],
|
|
127
|
+
method_ctx: Thread.current[:legion_log_method]
|
|
128
|
+
))
|
|
102
129
|
else
|
|
103
130
|
log.unknown(message)
|
|
104
131
|
end
|
|
@@ -219,16 +246,18 @@ module Legion
|
|
|
219
246
|
timestamp: Time.now.to_i,
|
|
220
247
|
app_id: 'legionio',
|
|
221
248
|
type: 'exception_event',
|
|
222
|
-
priority:
|
|
249
|
+
priority: EXCEPTION_PRIORITY[level] || 5,
|
|
223
250
|
delivery_mode: 2
|
|
224
251
|
}
|
|
225
252
|
end
|
|
226
253
|
|
|
227
254
|
def build_writer_context(level, message)
|
|
228
|
-
|
|
255
|
+
has_writer = !Legion::Logging.instance_variable_get(:@log_writer).nil?
|
|
256
|
+
has_hooks = defined?(Legion::Logging::Hooks) && Legion::Logging::Hooks.enabled?
|
|
257
|
+
return nil unless has_writer || has_hooks
|
|
229
258
|
|
|
230
259
|
lex_val = instance_variable_defined?(:@lex) ? @lex : nil
|
|
231
|
-
lex_segs = instance_variable_defined?(:@lex_segments) ? @lex_segments : nil
|
|
260
|
+
lex_segs = Thread.current[:legion_log_segments] || (instance_variable_defined?(:@lex_segments) ? @lex_segments : nil)
|
|
232
261
|
|
|
233
262
|
event = Legion::Logging::EventBuilder.build(
|
|
234
263
|
level: level,
|
|
@@ -242,7 +271,7 @@ module Legion
|
|
|
242
271
|
|
|
243
272
|
def fire_log_writer(level, message)
|
|
244
273
|
lex_val = instance_variable_defined?(:@lex) ? @lex : nil
|
|
245
|
-
lex_segs = instance_variable_defined?(:@lex_segments) ? @lex_segments : nil
|
|
274
|
+
lex_segs = Thread.current[:legion_log_segments] || (instance_variable_defined?(:@lex_segments) ? @lex_segments : nil)
|
|
246
275
|
|
|
247
276
|
event = Legion::Logging::EventBuilder.build(
|
|
248
277
|
level: level,
|
|
@@ -252,13 +281,13 @@ module Legion
|
|
|
252
281
|
caller_offset: 4
|
|
253
282
|
)
|
|
254
283
|
lex_name = event[:lex] || 'core'
|
|
255
|
-
component = event.dig(:caller, :file).to_s[
|
|
284
|
+
component = event.dig(:caller, :file).to_s[COMPONENT_REGEX, 1] || 'unknown'
|
|
256
285
|
routing_key = "legion.logging.log.#{level}.#{lex_name}.#{component}"
|
|
257
286
|
Legion::Logging.log_writer.call(event, routing_key: routing_key)
|
|
287
|
+
Legion::Logging::Hooks.fire(level, message, event) if defined?(Legion::Logging::Hooks)
|
|
258
288
|
rescue StandardError => e
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
end
|
|
289
|
+
rk = defined?(routing_key) ? routing_key : 'unknown'
|
|
290
|
+
log.warn("fire_log_writer failed for level=#{level}, routing_key=#{rk}: #{e.class}: #{e.message}") if respond_to?(:log) && log.respond_to?(:warn)
|
|
262
291
|
end
|
|
263
292
|
end
|
|
264
293
|
end
|
|
@@ -29,7 +29,7 @@ module Legion
|
|
|
29
29
|
def post(uri, body)
|
|
30
30
|
req = Net::HTTP::Post.new(uri)
|
|
31
31
|
req['Content-Type'] = 'application/json'
|
|
32
|
-
apply_auth(req)
|
|
32
|
+
apply_auth(req, uri)
|
|
33
33
|
|
|
34
34
|
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https',
|
|
35
35
|
open_timeout: 5, read_timeout: 10) do |http|
|
|
@@ -50,11 +50,11 @@ module Legion
|
|
|
50
50
|
uri.path.include?('/services/collector')
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
def apply_auth(req)
|
|
53
|
+
def apply_auth(req, uri)
|
|
54
54
|
token = auth_token
|
|
55
55
|
return unless token
|
|
56
56
|
|
|
57
|
-
req['Authorization'] = if splunk_hec?(
|
|
57
|
+
req['Authorization'] = if splunk_hec?(uri)
|
|
58
58
|
"Splunk #{token}"
|
|
59
59
|
else
|
|
60
60
|
"Bearer #{token}"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Logging
|
|
5
|
+
class TaggedLogger
|
|
6
|
+
LEVELS = { debug: 0, info: 1, warn: 2, error: 3, fatal: 4, unknown: 5 }.freeze
|
|
7
|
+
|
|
8
|
+
attr_reader :segments, :trace_enabled, :extended
|
|
9
|
+
|
|
10
|
+
def initialize(segments:, level: :info, trace: false, trace_size: 4, extended: false, **_opts)
|
|
11
|
+
@segments = segments
|
|
12
|
+
@level_value =
|
|
13
|
+
if level.is_a?(Integer)
|
|
14
|
+
level
|
|
15
|
+
else
|
|
16
|
+
LEVELS.fetch(level.to_s.downcase.to_sym, LEVELS[:info])
|
|
17
|
+
end
|
|
18
|
+
@trace_enabled = trace
|
|
19
|
+
@trace_size = trace_size
|
|
20
|
+
@extended = extended
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def level
|
|
24
|
+
@level_value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def debug(message = nil)
|
|
28
|
+
return unless @level_value < 1
|
|
29
|
+
|
|
30
|
+
message = yield if message.nil? && block_given?
|
|
31
|
+
with_segments { Legion::Logging.debug(message) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def info(message = nil)
|
|
35
|
+
return unless @level_value < 2
|
|
36
|
+
|
|
37
|
+
message = yield if message.nil? && block_given?
|
|
38
|
+
with_segments { Legion::Logging.info(message) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def warn(message = nil)
|
|
42
|
+
return unless @level_value < 3
|
|
43
|
+
|
|
44
|
+
message = yield if message.nil? && block_given?
|
|
45
|
+
with_segments { Legion::Logging.warn(message) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def error(message = nil)
|
|
49
|
+
return unless @level_value < 4
|
|
50
|
+
|
|
51
|
+
message = yield if message.nil? && block_given?
|
|
52
|
+
with_segments { Legion::Logging.error(message) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def fatal(message = nil)
|
|
56
|
+
return unless @level_value < 5
|
|
57
|
+
|
|
58
|
+
message = yield if message.nil? && block_given?
|
|
59
|
+
with_segments { Legion::Logging.fatal(message) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def unknown(message = nil)
|
|
63
|
+
message = yield if message.nil? && block_given?
|
|
64
|
+
with_segments { Legion::Logging.unknown(message) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def trace(raw_message = nil, size: @trace_size, log_caller: true)
|
|
68
|
+
return unless @trace_enabled
|
|
69
|
+
|
|
70
|
+
raw_message = yield if raw_message.nil? && block_given?
|
|
71
|
+
message = "Tracing: #{raw_message} "
|
|
72
|
+
if log_caller
|
|
73
|
+
frames = size ? caller_locations(2, size) : caller_locations(2)
|
|
74
|
+
message.concat(frames&.join(', ').to_s)
|
|
75
|
+
end
|
|
76
|
+
with_segments { Legion::Logging.unknown(message) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def thread(kvl: false, **_opts)
|
|
80
|
+
if kvl
|
|
81
|
+
"thread=#{Thread.current.object_id}"
|
|
82
|
+
else
|
|
83
|
+
Thread.current.object_id.to_s
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def with_segments
|
|
90
|
+
prev = Thread.current[:legion_log_segments]
|
|
91
|
+
Thread.current[:legion_log_segments] = @segments
|
|
92
|
+
yield
|
|
93
|
+
ensure
|
|
94
|
+
Thread.current[:legion_log_segments] = prev
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
data/lib/legion/logging.rb
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'legion/logging/version'
|
|
4
|
+
require 'legion/logging/settings'
|
|
4
5
|
require 'legion/logging/logger'
|
|
5
6
|
require 'legion/logging/methods'
|
|
6
7
|
require 'legion/logging/builder'
|
|
7
8
|
require 'legion/logging/event_builder'
|
|
8
9
|
require 'legion/logging/async_writer'
|
|
10
|
+
require 'legion/logging/tagged_logger'
|
|
9
11
|
require 'legion/logging/helper'
|
|
10
12
|
require 'legion/logging/category_registry'
|
|
13
|
+
require 'legion/logging/hooks'
|
|
11
14
|
|
|
12
15
|
require 'json'
|
|
13
16
|
require 'logger'
|
|
@@ -22,7 +25,7 @@ module Legion
|
|
|
22
25
|
attr_reader :color
|
|
23
26
|
attr_writer :log_writer, :exception_writer
|
|
24
27
|
|
|
25
|
-
DEFAULT_LOG_WRITER
|
|
28
|
+
DEFAULT_LOG_WRITER = ->(_event, routing_key:) {}
|
|
26
29
|
DEFAULT_EXCEPTION_WRITER = ->(_event, routing_key:, headers:, properties:) {}
|
|
27
30
|
|
|
28
31
|
def log_writer
|
|
@@ -41,6 +44,30 @@ module Legion
|
|
|
41
44
|
CategoryRegistry.registered_categories
|
|
42
45
|
end
|
|
43
46
|
|
|
47
|
+
def on_fatal(&)
|
|
48
|
+
Hooks.on_fatal(&)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def on_error(&)
|
|
52
|
+
Hooks.on_error(&)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def on_warn(&)
|
|
56
|
+
Hooks.on_warn(&)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def enable_hooks!
|
|
60
|
+
Hooks.enable_hooks!
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def disable_hooks!
|
|
64
|
+
Hooks.disable_hooks!
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def clear_hooks!
|
|
68
|
+
Hooks.clear_hooks!
|
|
69
|
+
end
|
|
70
|
+
|
|
44
71
|
def setup(level: 'info', format: :text, async: true, **options)
|
|
45
72
|
output(**options)
|
|
46
73
|
log_level(level)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Service
|
|
5
|
+
def self.register_logging_hooks
|
|
6
|
+
return unless defined?(Legion::Logging::Hooks)
|
|
7
|
+
return unless defined?(Legion::Transport)
|
|
8
|
+
|
|
9
|
+
Legion::Logging::Hooks.on_warn do |message, event|
|
|
10
|
+
Legion::Transport::Exchanges::Logging.publish(event.merge(level: :warn, message: message))
|
|
11
|
+
rescue StandardError => e
|
|
12
|
+
Kernel.warn("register_logging_hooks on_warn publish failed: #{e.message}")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Legion::Logging::Hooks.on_error do |message, event|
|
|
16
|
+
Legion::Transport::Exchanges::Logging.publish(event.merge(level: :error, message: message))
|
|
17
|
+
rescue StandardError => e
|
|
18
|
+
Kernel.warn("register_logging_hooks on_error publish failed: #{e.message}")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Legion::Logging::Hooks.on_fatal do |message, event|
|
|
22
|
+
Legion::Transport::Exchanges::Logging.publish(event.merge(level: :fatal, message: message))
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
Kernel.warn("register_logging_hooks on_fatal publish failed: #{e.message}")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
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.4.
|
|
4
|
+
version: 1.4.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -64,15 +64,19 @@ files:
|
|
|
64
64
|
- lib/legion/logging/category_registry.rb
|
|
65
65
|
- lib/legion/logging/event_builder.rb
|
|
66
66
|
- lib/legion/logging/helper.rb
|
|
67
|
+
- lib/legion/logging/hooks.rb
|
|
67
68
|
- lib/legion/logging/logger.rb
|
|
68
69
|
- lib/legion/logging/methods.rb
|
|
69
70
|
- lib/legion/logging/multi_io.rb
|
|
70
71
|
- lib/legion/logging/redactor.rb
|
|
72
|
+
- lib/legion/logging/settings.rb
|
|
71
73
|
- lib/legion/logging/shipper.rb
|
|
72
74
|
- lib/legion/logging/shipper/file_transport.rb
|
|
73
75
|
- lib/legion/logging/shipper/http_transport.rb
|
|
74
76
|
- lib/legion/logging/siem_exporter.rb
|
|
77
|
+
- lib/legion/logging/tagged_logger.rb
|
|
75
78
|
- lib/legion/logging/version.rb
|
|
79
|
+
- lib/legion/service.rb
|
|
76
80
|
- sonar-project.properties
|
|
77
81
|
homepage: https://github.com/LegionIO/legion-logging
|
|
78
82
|
licenses:
|