legion-logging 1.4.2 → 1.5.0
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 +62 -1
- data/Gemfile +1 -0
- data/lib/legion/logging/async_writer.rb +46 -11
- data/lib/legion/logging/builder.rb +74 -34
- data/lib/legion/logging/event_builder.rb +92 -6
- data/lib/legion/logging/helper.rb +555 -26
- data/lib/legion/logging/hooks.rb +72 -0
- data/lib/legion/logging/methods.rb +121 -53
- data/lib/legion/logging/multi_io.rb +0 -1
- data/lib/legion/logging/redactor.rb +33 -2
- data/lib/legion/logging/settings.rb +16 -0
- data/lib/legion/logging/shipper/file_transport.rb +9 -1
- data/lib/legion/logging/shipper/http_transport.rb +7 -3
- data/lib/legion/logging/shipper.rb +16 -12
- data/lib/legion/logging/tagged_logger.rb +129 -0
- data/lib/legion/logging/version.rb +1 -1
- data/lib/legion/logging.rb +56 -6
- data/lib/legion/service.rb +28 -0
- metadata +5 -1
|
@@ -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
|
|
|
@@ -23,62 +30,36 @@ module Legion
|
|
|
23
30
|
return unless log.level < 1
|
|
24
31
|
|
|
25
32
|
message = yield if message.nil? && block_given?
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if writer&.alive?
|
|
30
|
-
writer.push(AsyncWriter::LogEntry.new(level: :debug, message: message, writer_context: nil))
|
|
31
|
-
else
|
|
32
|
-
log.debug(message)
|
|
33
|
-
end
|
|
33
|
+
raw = maybe_redact(message)
|
|
34
|
+
formatted = format_message_for_level(:debug, raw)
|
|
35
|
+
write_async_or_sync(:debug, formatted, raw)
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def info(message = nil)
|
|
37
39
|
return unless log.level < 2
|
|
38
40
|
|
|
39
41
|
message = yield if message.nil? && block_given?
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if writer&.alive?
|
|
44
|
-
writer.push(AsyncWriter::LogEntry.new(level: :info, message: message, writer_context: nil))
|
|
45
|
-
else
|
|
46
|
-
log.info(message)
|
|
47
|
-
end
|
|
42
|
+
raw = maybe_redact(message)
|
|
43
|
+
formatted = format_message_for_level(:info, raw)
|
|
44
|
+
write_async_or_sync(:info, formatted, raw)
|
|
48
45
|
end
|
|
49
46
|
|
|
50
47
|
def warn(message = nil)
|
|
51
48
|
return unless log.level < 3
|
|
52
49
|
|
|
53
50
|
message = yield if message.nil? && block_given?
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
writer = @async_writer
|
|
58
|
-
if writer&.alive?
|
|
59
|
-
ctx = build_writer_context(:warn, raw)
|
|
60
|
-
writer.push(AsyncWriter::LogEntry.new(level: :warn, message: message, writer_context: ctx))
|
|
61
|
-
else
|
|
62
|
-
log.warn(message)
|
|
63
|
-
fire_log_writer(:warn, raw)
|
|
64
|
-
end
|
|
51
|
+
raw = maybe_redact(message)
|
|
52
|
+
formatted = format_message_for_level(:warn, raw)
|
|
53
|
+
write_async_or_sync(:warn, formatted, raw, writer_context: build_writer_context(:warn, raw))
|
|
65
54
|
end
|
|
66
55
|
|
|
67
56
|
def error(message = nil)
|
|
68
57
|
return unless log.level < 4
|
|
69
58
|
|
|
70
59
|
message = yield if message.nil? && block_given?
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
writer = @async_writer
|
|
75
|
-
if writer&.alive?
|
|
76
|
-
ctx = build_writer_context(:error, raw)
|
|
77
|
-
writer.push(AsyncWriter::LogEntry.new(level: :error, message: message, writer_context: ctx))
|
|
78
|
-
else
|
|
79
|
-
log.error(message)
|
|
80
|
-
fire_log_writer(:error, raw)
|
|
81
|
-
end
|
|
60
|
+
raw = maybe_redact(message)
|
|
61
|
+
formatted = format_message_for_level(:error, raw)
|
|
62
|
+
write_async_or_sync(:error, formatted, raw, writer_context: build_writer_context(:error, raw))
|
|
82
63
|
end
|
|
83
64
|
|
|
84
65
|
def fatal(message = nil)
|
|
@@ -94,13 +75,22 @@ module Legion
|
|
|
94
75
|
|
|
95
76
|
def unknown(message = nil)
|
|
96
77
|
message = yield if message.nil? && block_given?
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
78
|
+
raw = maybe_redact(message)
|
|
79
|
+
formatted = format_message_for_level(:unknown, raw)
|
|
80
|
+
write_async_or_sync(:unknown, formatted, raw)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def emit_tagged(level, message = nil, segments: nil, method_ctx: nil)
|
|
84
|
+
level = level.to_sym
|
|
85
|
+
message = yield if message.nil? && block_given?
|
|
86
|
+
return if message.nil?
|
|
87
|
+
|
|
88
|
+
raw = maybe_redact(message)
|
|
89
|
+
formatted = format_message_for_level(level, raw)
|
|
90
|
+
|
|
91
|
+
with_tagged_context(segments, method_ctx) do
|
|
92
|
+
write_forced(level, formatted)
|
|
93
|
+
fire_log_writer(level, raw) if %i[warn error fatal].include?(level)
|
|
104
94
|
end
|
|
105
95
|
end
|
|
106
96
|
|
|
@@ -116,6 +106,7 @@ module Legion
|
|
|
116
106
|
level = level.to_sym if level.respond_to?(:to_sym)
|
|
117
107
|
# 1. Log human-readable line to stdout/file (bypass writer callbacks)
|
|
118
108
|
msg = exception.respond_to?(:message) ? exception.message : exception.to_s
|
|
109
|
+
msg = maybe_redact(msg)
|
|
119
110
|
log.public_send(level, msg) if respond_to?(:log) && log.respond_to?(level)
|
|
120
111
|
|
|
121
112
|
# 2. Build rich exception event
|
|
@@ -168,6 +159,81 @@ module Legion
|
|
|
168
159
|
message
|
|
169
160
|
end
|
|
170
161
|
|
|
162
|
+
def format_message_for_level(level, message)
|
|
163
|
+
return Rainbow(message).blue if level == :debug && @color
|
|
164
|
+
return Rainbow(message).green if level == :info && @color
|
|
165
|
+
return Rainbow(message).yellow if level == :warn && @color
|
|
166
|
+
return Rainbow(message).red if level == :error && @color
|
|
167
|
+
return Rainbow(message).darkred if level == :fatal && @color
|
|
168
|
+
return Rainbow(message).purple if level == :unknown && @color
|
|
169
|
+
|
|
170
|
+
message
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def with_tagged_context(segments, method_ctx)
|
|
174
|
+
prev_segments = Thread.current[:legion_log_segments]
|
|
175
|
+
prev_method_ctx = Thread.current[:legion_log_method]
|
|
176
|
+
|
|
177
|
+
Thread.current[:legion_log_segments] = segments unless segments.nil?
|
|
178
|
+
Thread.current[:legion_log_method] = method_ctx unless method_ctx.nil?
|
|
179
|
+
yield
|
|
180
|
+
ensure
|
|
181
|
+
Thread.current[:legion_log_segments] = prev_segments
|
|
182
|
+
Thread.current[:legion_log_method] = prev_method_ctx
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def write_forced(level, message)
|
|
186
|
+
logger = log
|
|
187
|
+
formatter = logger.formatter || ::Logger::Formatter.new
|
|
188
|
+
rendered = formatter.call(severity_label_for(level), Time.now, nil, message)
|
|
189
|
+
|
|
190
|
+
log_device = logger.instance_variable_get(:@logdev)
|
|
191
|
+
if log_device.respond_to?(:write)
|
|
192
|
+
log_device.write(rendered)
|
|
193
|
+
else
|
|
194
|
+
$stdout.write(rendered)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def severity_label_for(level)
|
|
199
|
+
return 'ANY' if level == :unknown
|
|
200
|
+
|
|
201
|
+
level.to_s.upcase
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def write_async_or_sync(level, formatted_message, raw_message, writer_context: nil)
|
|
205
|
+
writer = @async_writer
|
|
206
|
+
caller_trace = capture_runner_trace_for_async
|
|
207
|
+
if writer&.alive?
|
|
208
|
+
queued = writer.push(AsyncWriter::LogEntry.new(
|
|
209
|
+
level: level,
|
|
210
|
+
message: formatted_message,
|
|
211
|
+
writer_context: writer_context,
|
|
212
|
+
segments: Thread.current[:legion_log_segments],
|
|
213
|
+
method_ctx: Thread.current[:legion_log_method],
|
|
214
|
+
caller_trace: caller_trace
|
|
215
|
+
))
|
|
216
|
+
return if queued
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
with_caller_trace(caller_trace) do
|
|
220
|
+
log.public_send(level, formatted_message)
|
|
221
|
+
fire_log_writer(level, raw_message) if writer_context
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def capture_runner_trace_for_async
|
|
226
|
+
build_runner_trace(caller_locations(4, 1)&.first)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def with_caller_trace(caller_trace)
|
|
230
|
+
prev_caller_trace = Thread.current[:legion_log_caller]
|
|
231
|
+
Thread.current[:legion_log_caller] = caller_trace
|
|
232
|
+
yield
|
|
233
|
+
ensure
|
|
234
|
+
Thread.current[:legion_log_caller] = prev_caller_trace
|
|
235
|
+
end
|
|
236
|
+
|
|
171
237
|
def redaction_enabled?
|
|
172
238
|
return false unless defined?(Legion::Settings)
|
|
173
239
|
|
|
@@ -219,16 +285,18 @@ module Legion
|
|
|
219
285
|
timestamp: Time.now.to_i,
|
|
220
286
|
app_id: 'legionio',
|
|
221
287
|
type: 'exception_event',
|
|
222
|
-
priority:
|
|
288
|
+
priority: EXCEPTION_PRIORITY[level] || 5,
|
|
223
289
|
delivery_mode: 2
|
|
224
290
|
}
|
|
225
291
|
end
|
|
226
292
|
|
|
227
293
|
def build_writer_context(level, message)
|
|
228
|
-
|
|
294
|
+
has_writer = !Legion::Logging.instance_variable_get(:@log_writer).nil?
|
|
295
|
+
has_hooks = defined?(Legion::Logging::Hooks) && Legion::Logging::Hooks.enabled?
|
|
296
|
+
return nil unless has_writer || has_hooks
|
|
229
297
|
|
|
230
298
|
lex_val = instance_variable_defined?(:@lex) ? @lex : nil
|
|
231
|
-
lex_segs = instance_variable_defined?(:@lex_segments) ? @lex_segments : nil
|
|
299
|
+
lex_segs = Thread.current[:legion_log_segments] || (instance_variable_defined?(:@lex_segments) ? @lex_segments : nil)
|
|
232
300
|
|
|
233
301
|
event = Legion::Logging::EventBuilder.build(
|
|
234
302
|
level: level,
|
|
@@ -242,7 +310,7 @@ module Legion
|
|
|
242
310
|
|
|
243
311
|
def fire_log_writer(level, message)
|
|
244
312
|
lex_val = instance_variable_defined?(:@lex) ? @lex : nil
|
|
245
|
-
lex_segs = instance_variable_defined?(:@lex_segments) ? @lex_segments : nil
|
|
313
|
+
lex_segs = Thread.current[:legion_log_segments] || (instance_variable_defined?(:@lex_segments) ? @lex_segments : nil)
|
|
246
314
|
|
|
247
315
|
event = Legion::Logging::EventBuilder.build(
|
|
248
316
|
level: level,
|
|
@@ -252,13 +320,13 @@ module Legion
|
|
|
252
320
|
caller_offset: 4
|
|
253
321
|
)
|
|
254
322
|
lex_name = event[:lex] || 'core'
|
|
255
|
-
component = event.dig(:caller, :file).to_s[
|
|
323
|
+
component = event.dig(:caller, :file).to_s[COMPONENT_REGEX, 1] || 'unknown'
|
|
256
324
|
routing_key = "legion.logging.log.#{level}.#{lex_name}.#{component}"
|
|
257
325
|
Legion::Logging.log_writer.call(event, routing_key: routing_key)
|
|
326
|
+
Legion::Logging::Hooks.fire(level, message, event) if defined?(Legion::Logging::Hooks)
|
|
258
327
|
rescue StandardError => e
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
end
|
|
328
|
+
rk = defined?(routing_key) ? routing_key : 'unknown'
|
|
329
|
+
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
330
|
end
|
|
263
331
|
end
|
|
264
332
|
end
|
|
@@ -18,7 +18,18 @@ module Legion
|
|
|
18
18
|
bearer_token: %r{Bearer\s+[A-Za-z0-9._~+/=-]{20,}}i
|
|
19
19
|
}.freeze
|
|
20
20
|
|
|
21
|
-
SENSITIVE_FIELDS = %w[
|
|
21
|
+
SENSITIVE_FIELDS = %w[
|
|
22
|
+
password
|
|
23
|
+
secret
|
|
24
|
+
token
|
|
25
|
+
api_key
|
|
26
|
+
access_key
|
|
27
|
+
private_key
|
|
28
|
+
public_key
|
|
29
|
+
authorization
|
|
30
|
+
].freeze
|
|
31
|
+
SENSITIVE_SUFFIXES = %w[token secret password passphrase credential credentials].freeze
|
|
32
|
+
SAFE_KEY_FIELDS = %w[primary_key foreign_key sort_key partition_key routing_key].freeze
|
|
22
33
|
|
|
23
34
|
REDACTED = '[REDACTED]'
|
|
24
35
|
|
|
@@ -49,7 +60,17 @@ module Legion
|
|
|
49
60
|
private
|
|
50
61
|
|
|
51
62
|
def sensitive_field?(key)
|
|
52
|
-
|
|
63
|
+
normalized = normalize_key(key)
|
|
64
|
+
return false if SAFE_KEY_FIELDS.include?(normalized)
|
|
65
|
+
return true if SENSITIVE_FIELDS.include?(normalized)
|
|
66
|
+
return true if normalized.include?('authorization')
|
|
67
|
+
return true if normalized.start_with?('auth_') || normalized.end_with?('_auth')
|
|
68
|
+
return true if normalized.start_with?('bearer_') || normalized.end_with?('_bearer')
|
|
69
|
+
return true if SENSITIVE_SUFFIXES.any? { |suffix| normalized.end_with?("_#{suffix}") }
|
|
70
|
+
|
|
71
|
+
%w[api access client private public auth secret signing session].any? do |prefix|
|
|
72
|
+
normalized == "#{prefix}_key"
|
|
73
|
+
end
|
|
53
74
|
end
|
|
54
75
|
|
|
55
76
|
def all_patterns
|
|
@@ -79,6 +100,16 @@ module Legion
|
|
|
79
100
|
def reset_pattern_cache!
|
|
80
101
|
@all_patterns = nil
|
|
81
102
|
end
|
|
103
|
+
|
|
104
|
+
def refresh_patterns!
|
|
105
|
+
reset_pattern_cache!
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
public :refresh_patterns!
|
|
109
|
+
|
|
110
|
+
def normalize_key(key)
|
|
111
|
+
key.to_s.downcase.gsub(/[^a-z0-9]+/, '_').gsub(/\A_+|_+\z/, '')
|
|
112
|
+
end
|
|
82
113
|
end
|
|
83
114
|
end
|
|
84
115
|
end
|
|
@@ -11,10 +11,18 @@ module Legion
|
|
|
11
11
|
|
|
12
12
|
class << self
|
|
13
13
|
def ship(event)
|
|
14
|
+
ship_batch([event])
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def ship_batch(events)
|
|
18
|
+
batch = Array(events)
|
|
19
|
+
return true if batch.empty?
|
|
20
|
+
|
|
14
21
|
path = resolve_path
|
|
15
22
|
FileUtils.mkdir_p(File.dirname(path))
|
|
16
23
|
File.open(path, 'a') do |f|
|
|
17
|
-
f.
|
|
24
|
+
f.write(batch.map { |event| ::JSON.generate(event) }.join("\n"))
|
|
25
|
+
f.write("\n")
|
|
18
26
|
end
|
|
19
27
|
true
|
|
20
28
|
rescue StandardError => e
|
|
@@ -10,6 +10,10 @@ module Legion
|
|
|
10
10
|
module HttpTransport
|
|
11
11
|
class << self
|
|
12
12
|
def ship(events)
|
|
13
|
+
ship_batch(events)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def ship_batch(events)
|
|
13
17
|
endpoint = resolve_endpoint
|
|
14
18
|
return false unless endpoint
|
|
15
19
|
|
|
@@ -29,7 +33,7 @@ module Legion
|
|
|
29
33
|
def post(uri, body)
|
|
30
34
|
req = Net::HTTP::Post.new(uri)
|
|
31
35
|
req['Content-Type'] = 'application/json'
|
|
32
|
-
apply_auth(req)
|
|
36
|
+
apply_auth(req, uri)
|
|
33
37
|
|
|
34
38
|
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https',
|
|
35
39
|
open_timeout: 5, read_timeout: 10) do |http|
|
|
@@ -50,11 +54,11 @@ module Legion
|
|
|
50
54
|
uri.path.include?('/services/collector')
|
|
51
55
|
end
|
|
52
56
|
|
|
53
|
-
def apply_auth(req)
|
|
57
|
+
def apply_auth(req, uri)
|
|
54
58
|
token = auth_token
|
|
55
59
|
return unless token
|
|
56
60
|
|
|
57
|
-
req['Authorization'] = if splunk_hec?(
|
|
61
|
+
req['Authorization'] = if splunk_hec?(uri)
|
|
58
62
|
"Splunk #{token}"
|
|
59
63
|
else
|
|
60
64
|
"Bearer #{token}"
|
|
@@ -30,21 +30,25 @@ module Legion
|
|
|
30
30
|
transport = TRANSPORTS[transport_type]
|
|
31
31
|
return unless transport
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
@
|
|
35
|
-
batch =
|
|
36
|
-
@buffer.
|
|
33
|
+
@flush_mutex ||= Mutex.new
|
|
34
|
+
@flush_mutex.synchronize do
|
|
35
|
+
batch = nil
|
|
36
|
+
@mutex.synchronize { batch = @buffer.dup }
|
|
37
|
+
return if batch.empty?
|
|
38
|
+
|
|
39
|
+
delivered = deliver(transport, batch)
|
|
40
|
+
@mutex.synchronize { @buffer.shift(batch.size) if delivered }
|
|
41
|
+
delivered
|
|
37
42
|
end
|
|
38
|
-
|
|
39
|
-
deliver(transport, batch)
|
|
40
43
|
end
|
|
41
44
|
|
|
42
45
|
def start
|
|
43
46
|
return unless enabled?
|
|
44
47
|
return if @flush_thread&.alive?
|
|
45
48
|
|
|
46
|
-
@buffer
|
|
47
|
-
@mutex
|
|
49
|
+
@buffer ||= []
|
|
50
|
+
@mutex ||= Mutex.new
|
|
51
|
+
@flush_mutex ||= Mutex.new
|
|
48
52
|
interval = flush_interval
|
|
49
53
|
@flush_thread = Thread.new do
|
|
50
54
|
loop do
|
|
@@ -83,14 +87,14 @@ module Legion
|
|
|
83
87
|
end
|
|
84
88
|
|
|
85
89
|
def deliver(transport, batch)
|
|
86
|
-
if transport.
|
|
87
|
-
|
|
88
|
-
transport.ship(batch)
|
|
90
|
+
if transport.respond_to?(:ship_batch)
|
|
91
|
+
transport.ship_batch(batch)
|
|
89
92
|
else
|
|
90
|
-
batch.
|
|
93
|
+
batch.all? { |event| transport.ship(event) }
|
|
91
94
|
end
|
|
92
95
|
rescue StandardError => e
|
|
93
96
|
Legion::Logging.error("Shipper deliver failed: #{e.message}") if defined?(Legion::Logging)
|
|
97
|
+
false
|
|
94
98
|
end
|
|
95
99
|
|
|
96
100
|
def shippable_level?(level)
|
|
@@ -0,0 +1,129 @@
|
|
|
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(
|
|
11
|
+
segments:,
|
|
12
|
+
level: Legion::Logging::Settings.default[:level],
|
|
13
|
+
trace: Legion::Logging::Settings.default[:trace],
|
|
14
|
+
trace_size: Legion::Logging::Settings.default[:trace_size],
|
|
15
|
+
extended: Legion::Logging::Settings.default[:extended],
|
|
16
|
+
**_opts
|
|
17
|
+
)
|
|
18
|
+
@segments = segments
|
|
19
|
+
@level_value =
|
|
20
|
+
if level.is_a?(Integer)
|
|
21
|
+
level
|
|
22
|
+
else
|
|
23
|
+
default_level = Legion::Logging::Settings.default[:level].to_s.downcase.to_sym
|
|
24
|
+
LEVELS.fetch(level.to_s.downcase.to_sym, LEVELS.fetch(default_level, LEVELS[:info]))
|
|
25
|
+
end
|
|
26
|
+
@trace_enabled = trace
|
|
27
|
+
@trace_size = trace_size
|
|
28
|
+
@extended = extended
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def level
|
|
32
|
+
@level_value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def debug(message = nil)
|
|
36
|
+
return unless @level_value < 1
|
|
37
|
+
|
|
38
|
+
message = yield if message.nil? && block_given?
|
|
39
|
+
with_segments { dispatch(:debug, message) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def info(message = nil)
|
|
43
|
+
return unless @level_value < 2
|
|
44
|
+
|
|
45
|
+
message = yield if message.nil? && block_given?
|
|
46
|
+
with_segments { dispatch(:info, message) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def warn(message = nil)
|
|
50
|
+
return unless @level_value < 3
|
|
51
|
+
|
|
52
|
+
message = yield if message.nil? && block_given?
|
|
53
|
+
with_segments { dispatch(:warn, message) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def error(message = nil)
|
|
57
|
+
return unless @level_value < 4
|
|
58
|
+
|
|
59
|
+
message = yield if message.nil? && block_given?
|
|
60
|
+
with_segments { dispatch(:error, message) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def fatal(message = nil)
|
|
64
|
+
return unless @level_value < 5
|
|
65
|
+
|
|
66
|
+
message = yield if message.nil? && block_given?
|
|
67
|
+
with_segments { dispatch(:fatal, message) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def unknown(message = nil)
|
|
71
|
+
message = yield if message.nil? && block_given?
|
|
72
|
+
with_segments { dispatch(:unknown, message) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def trace(raw_message = nil, size: @trace_size, log_caller: true)
|
|
76
|
+
return unless @trace_enabled
|
|
77
|
+
|
|
78
|
+
raw_message = yield if raw_message.nil? && block_given?
|
|
79
|
+
message = "Tracing: #{raw_message} "
|
|
80
|
+
if log_caller
|
|
81
|
+
frames = size ? caller_locations(2, size) : caller_locations(2)
|
|
82
|
+
message.concat(frames&.join(', ').to_s)
|
|
83
|
+
end
|
|
84
|
+
with_segments { Legion::Logging.unknown(message) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def thread(kvl: false, **_opts)
|
|
88
|
+
if kvl
|
|
89
|
+
"thread=#{Thread.current.object_id}"
|
|
90
|
+
else
|
|
91
|
+
Thread.current.object_id.to_s
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def dispatch(level, message)
|
|
98
|
+
return unless defined?(Legion::Logging)
|
|
99
|
+
|
|
100
|
+
if Legion::Logging.respond_to?(:emit_tagged)
|
|
101
|
+
Legion::Logging.emit_tagged(level, message, segments: @segments)
|
|
102
|
+
return
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
if Legion::Logging.respond_to?(level)
|
|
106
|
+
Legion::Logging.public_send(level, message)
|
|
107
|
+
return
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
fallback = fallback_level(level)
|
|
111
|
+
Legion::Logging.public_send(fallback, message) if fallback && Legion::Logging.respond_to?(fallback)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def fallback_level(level)
|
|
115
|
+
return :debug if level == :unknown
|
|
116
|
+
|
|
117
|
+
nil
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def with_segments
|
|
121
|
+
prev = Thread.current[:legion_log_segments]
|
|
122
|
+
Thread.current[:legion_log_segments] = @segments
|
|
123
|
+
yield
|
|
124
|
+
ensure
|
|
125
|
+
Thread.current[:legion_log_segments] = prev
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
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
|
|
@@ -33,6 +36,14 @@ module Legion
|
|
|
33
36
|
@exception_writer || DEFAULT_EXCEPTION_WRITER
|
|
34
37
|
end
|
|
35
38
|
|
|
39
|
+
def current_settings
|
|
40
|
+
(@current_settings || {}).dup
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def configuration_generation
|
|
44
|
+
@configuration_generation || 0
|
|
45
|
+
end
|
|
46
|
+
|
|
36
47
|
def register_category(name, description: nil, expected_fields: [])
|
|
37
48
|
CategoryRegistry.register_category(name, description: description, expected_fields: expected_fields)
|
|
38
49
|
end
|
|
@@ -41,18 +52,57 @@ module Legion
|
|
|
41
52
|
CategoryRegistry.registered_categories
|
|
42
53
|
end
|
|
43
54
|
|
|
44
|
-
def
|
|
55
|
+
def on_fatal(&)
|
|
56
|
+
Hooks.on_fatal(&)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def on_error(&)
|
|
60
|
+
Hooks.on_error(&)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def on_warn(&)
|
|
64
|
+
Hooks.on_warn(&)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def enable_hooks!
|
|
68
|
+
Hooks.enable_hooks!
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def disable_hooks!
|
|
72
|
+
Hooks.disable_hooks!
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def clear_hooks!
|
|
76
|
+
Hooks.clear_hooks!
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def setup(level: 'debug', format: :text, async: true, **options)
|
|
45
80
|
output(**options)
|
|
46
81
|
log_level(level)
|
|
47
82
|
log_format(format: format, **options)
|
|
48
83
|
@color = options[:color]
|
|
49
84
|
@color = format != :json && (options[:color] || (options[:color].nil? && options[:log_file].nil?))
|
|
85
|
+
@current_settings = {
|
|
86
|
+
level: level,
|
|
87
|
+
format: format.to_sym,
|
|
88
|
+
async: async,
|
|
89
|
+
trace: options.fetch(:trace, true),
|
|
90
|
+
trace_size: options.fetch(:trace_size, 4),
|
|
91
|
+
extended: options.fetch(:extended, true),
|
|
92
|
+
log_file: options[:log_file],
|
|
93
|
+
log_stdout: options[:log_stdout],
|
|
94
|
+
include_pid: options.fetch(:include_pid, false),
|
|
95
|
+
color: @color
|
|
96
|
+
}.freeze
|
|
97
|
+
@configuration_generation = configuration_generation + 1
|
|
98
|
+
Legion::Logging::Redactor.refresh_patterns! if defined?(Legion::Logging::Redactor)
|
|
50
99
|
if async
|
|
51
|
-
buffer = if defined?(Legion::Settings)
|
|
52
|
-
Legion::Settings
|
|
53
|
-
|
|
54
|
-
|
|
100
|
+
buffer = if defined?(Legion::Settings) && Legion::Settings.respond_to?(:[])
|
|
101
|
+
logging_settings = Legion::Settings[:logging]
|
|
102
|
+
async_settings = logging_settings[:async] if logging_settings.is_a?(Hash)
|
|
103
|
+
async_settings[:buffer_size] if async_settings.is_a?(Hash)
|
|
55
104
|
end
|
|
105
|
+
buffer ||= 10_000
|
|
56
106
|
start_async_writer(buffer_size: buffer)
|
|
57
107
|
else
|
|
58
108
|
stop_async_writer
|