legion-logging 1.5.6 → 1.5.21
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/CHANGELOG.md +114 -0
- data/CLAUDE.md +20 -10
- data/README.md +109 -10
- data/lib/legion/logging/async_writer.rb +125 -36
- data/lib/legion/logging/builder.rb +107 -39
- data/lib/legion/logging/event_builder.rb +9 -6
- data/lib/legion/logging/helper.rb +11 -19
- data/lib/legion/logging/logger.rb +1 -1
- data/lib/legion/logging/method_tracer.rb +13 -2
- data/lib/legion/logging/methods.rb +152 -53
- data/lib/legion/logging/multi_io.rb +3 -1
- data/lib/legion/logging/output_logger.rb +60 -0
- data/lib/legion/logging/redactor.rb +3 -2
- data/lib/legion/logging/rotating_file.rb +239 -0
- data/lib/legion/logging/rotation.rb +143 -0
- data/lib/legion/logging/settings.rb +27 -5
- data/lib/legion/logging/shipper/file_transport.rb +2 -1
- data/lib/legion/logging/shipper/http_transport.rb +4 -2
- data/lib/legion/logging/shipper.rb +118 -69
- data/lib/legion/logging/siem_exporter.rb +1 -1
- data/lib/legion/logging/stdio_rotator.rb +492 -0
- data/lib/legion/logging/version.rb +1 -1
- data/lib/legion/logging.rb +35 -30
- metadata +5 -1
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Logging
|
|
5
|
+
# The descriptors themselves become pipe writers, including writes which do
|
|
6
|
+
# not pass through Ruby's Logger. Only the drain thread owns the file sink.
|
|
7
|
+
class StdioRotator
|
|
8
|
+
class << self
|
|
9
|
+
attr_reader :current
|
|
10
|
+
|
|
11
|
+
def shared(max_bytes:, max_files:)
|
|
12
|
+
inherited = @current
|
|
13
|
+
if inherited && !inherited.owned?
|
|
14
|
+
raise IOError, 'cannot change the policy of an inherited stdio capture' unless inherited.max_bytes == max_bytes && inherited.max_files == max_files
|
|
15
|
+
|
|
16
|
+
return inherited
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
@shared_mutex.synchronize do
|
|
20
|
+
return @current.configure(max_bytes: max_bytes, max_files: max_files) if @current
|
|
21
|
+
|
|
22
|
+
capture = new(max_bytes: max_bytes, max_files: max_files)
|
|
23
|
+
@current = capture if capture.capturing?
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def reset!(timeout: 2)
|
|
28
|
+
return false unless timeout.nil? ? @shared_mutex.lock : @shared_mutex.try_lock
|
|
29
|
+
|
|
30
|
+
begin
|
|
31
|
+
return true unless @current
|
|
32
|
+
return false unless @current.shutdown(timeout: timeout)
|
|
33
|
+
|
|
34
|
+
@current = nil
|
|
35
|
+
true
|
|
36
|
+
ensure
|
|
37
|
+
@shared_mutex.unlock
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
@shared_mutex = Mutex.new
|
|
42
|
+
|
|
43
|
+
attr_reader :max_bytes, :max_files
|
|
44
|
+
|
|
45
|
+
def initialize(max_bytes:, max_files:, stdout: $stdout, stderr: $stderr)
|
|
46
|
+
@stdout = stdout
|
|
47
|
+
@streams = [stdout, stderr].uniq(&:object_id)
|
|
48
|
+
@lifecycle = Mutex.new
|
|
49
|
+
@captures = []
|
|
50
|
+
@closed = false
|
|
51
|
+
@owner_pid = ::Process.pid
|
|
52
|
+
@max_bytes = max_bytes
|
|
53
|
+
@max_files = max_files
|
|
54
|
+
eligible = @streams.filter_map do |stream|
|
|
55
|
+
path = Rotation.regular_file_path(io: stream)
|
|
56
|
+
stream.flush if path
|
|
57
|
+
[stream, path, stream.stat] if path
|
|
58
|
+
end
|
|
59
|
+
eligible.group_by { |_, _, stat| [stat.dev, stat.ino] }.each_value do |group|
|
|
60
|
+
@captures << StdioCapture.new(path: group.first[1], streams: group.map(&:first), max_bytes: max_bytes, max_files: max_files)
|
|
61
|
+
end
|
|
62
|
+
@captures.each(&:attach)
|
|
63
|
+
@captures.each(&:release_backups)
|
|
64
|
+
rescue StandardError
|
|
65
|
+
@captures.reverse_each(&:rollback)
|
|
66
|
+
raise
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def capturing? = !@captures.empty? && !@closed
|
|
70
|
+
|
|
71
|
+
def owned? = @owner_pid == ::Process.pid
|
|
72
|
+
|
|
73
|
+
def configure(max_bytes:, max_files:)
|
|
74
|
+
raise IOError, 'cannot configure an inherited stdio capture' unless owned?
|
|
75
|
+
|
|
76
|
+
@lifecycle.synchronize do
|
|
77
|
+
raise IOError, 'closed stdio capture' if @closed
|
|
78
|
+
|
|
79
|
+
@captures.each { |capture| capture.command(type: :configure, arguments: { max_bytes: max_bytes, max_files: max_files }) }
|
|
80
|
+
@max_bytes = max_bytes
|
|
81
|
+
@max_files = max_files
|
|
82
|
+
end
|
|
83
|
+
self
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def write(*messages)
|
|
87
|
+
if @closed
|
|
88
|
+
current = self.class.current
|
|
89
|
+
return current.write(*messages) if current && !current.equal?(self) && current.stdout.equal?(@stdout)
|
|
90
|
+
|
|
91
|
+
return @stdout.write(*messages)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
@captures.each(&:check_failure!) if owned?
|
|
95
|
+
@stdout.write(*messages)
|
|
96
|
+
rescue IOError, SystemCallError
|
|
97
|
+
@captures.each(&:check_failure!) if owned? && !@closed
|
|
98
|
+
raise
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def flush(timeout: 2)
|
|
102
|
+
return false unless owned?
|
|
103
|
+
|
|
104
|
+
if @closed
|
|
105
|
+
current = self.class.current
|
|
106
|
+
return current.flush(timeout: timeout) if current && !current.equal?(self) && current.stdout.equal?(@stdout)
|
|
107
|
+
|
|
108
|
+
@stdout.flush
|
|
109
|
+
return self
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
deadline = monotonic_time + timeout if timeout
|
|
113
|
+
@captures.each { |capture| return false unless capture.flush(deadline: deadline) }
|
|
114
|
+
@stdout.flush unless @captures.any? { |capture| capture.includes?(stream: @stdout) }
|
|
115
|
+
self
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Logger borrows process stdio. Only explicit shutdown/reset! detaches it.
|
|
119
|
+
def close; end
|
|
120
|
+
|
|
121
|
+
def closed? = @closed
|
|
122
|
+
|
|
123
|
+
def sync = true # rubocop:disable Naming/PredicateMethod -- IO#sync interface
|
|
124
|
+
|
|
125
|
+
def sync=(_value); end
|
|
126
|
+
|
|
127
|
+
def rotate!
|
|
128
|
+
raise IOError, 'cannot rotate an inherited stdio capture' unless owned?
|
|
129
|
+
|
|
130
|
+
@lifecycle.synchronize do
|
|
131
|
+
raise IOError, 'closed stdio capture' if @closed
|
|
132
|
+
|
|
133
|
+
@captures.each { |capture| capture.command(type: :rotate!, arguments: {}) }
|
|
134
|
+
!@captures.empty?
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def shutdown(timeout: 2)
|
|
139
|
+
return false unless owned?
|
|
140
|
+
return false unless timeout.nil? ? @lifecycle.lock : @lifecycle.try_lock
|
|
141
|
+
|
|
142
|
+
deadline = monotonic_time + timeout if timeout
|
|
143
|
+
detached = false
|
|
144
|
+
begin
|
|
145
|
+
return true if @closed
|
|
146
|
+
return false unless @captures.all? { |capture| capture.pause(deadline: deadline) }
|
|
147
|
+
|
|
148
|
+
detached = true
|
|
149
|
+
@closed = drain_and_detach!(deadline: deadline)
|
|
150
|
+
ensure
|
|
151
|
+
begin
|
|
152
|
+
unless @closed
|
|
153
|
+
@captures.each(&:attach) if detached
|
|
154
|
+
@captures.each(&:resume)
|
|
155
|
+
end
|
|
156
|
+
ensure
|
|
157
|
+
@lifecycle.unlock
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
protected
|
|
163
|
+
|
|
164
|
+
attr_reader :stdout
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
def drain_and_detach!(deadline:)
|
|
169
|
+
@captures.each(&:restore)
|
|
170
|
+
@captures.each(&:resume)
|
|
171
|
+
# Include bytes written to the old descriptors during restoration.
|
|
172
|
+
return false unless @captures.all? { |capture| capture.pause(deadline: deadline) }
|
|
173
|
+
|
|
174
|
+
# A final drain may itself rotate: restore the current active inode.
|
|
175
|
+
@captures.each(&:restore)
|
|
176
|
+
@captures.all? { |capture| capture.finish!(deadline: deadline) }
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def monotonic_time
|
|
180
|
+
::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# One reader and one bounded control pipe per distinct inherited file.
|
|
185
|
+
# Commands occupy a single slot; bytes are never queued in Ruby.
|
|
186
|
+
class StdioCapture
|
|
187
|
+
READ_BYTES = 16 * 1024
|
|
188
|
+
|
|
189
|
+
def initialize(path:, streams:, max_bytes:, max_files:)
|
|
190
|
+
@path = path
|
|
191
|
+
@streams = streams
|
|
192
|
+
@state = Mutex.new
|
|
193
|
+
@changed = ConditionVariable.new
|
|
194
|
+
@requested_flush = 0
|
|
195
|
+
@completed_flush = 0
|
|
196
|
+
@backups = []
|
|
197
|
+
@properties = streams.map { |stream| [stream.sync, stream.close_on_exec?, stream.external_encoding, stream.internal_encoding] }
|
|
198
|
+
streams.each { |stream| @backups << stream.dup }
|
|
199
|
+
@sink = RotatingFile.new(path: path, max_bytes: max_bytes, max_files: max_files)
|
|
200
|
+
@reader, @writer = IO.pipe
|
|
201
|
+
@wake_reader, @wake_writer = IO.pipe
|
|
202
|
+
@writer.binmode
|
|
203
|
+
@writer.sync = true
|
|
204
|
+
@worker = Thread.new do
|
|
205
|
+
Thread.current.report_on_exception = false
|
|
206
|
+
drain
|
|
207
|
+
end
|
|
208
|
+
rescue StandardError
|
|
209
|
+
begin
|
|
210
|
+
close_handles
|
|
211
|
+
ensure
|
|
212
|
+
release_backups
|
|
213
|
+
end
|
|
214
|
+
raise
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def includes?(stream:)
|
|
218
|
+
@streams.any? { |candidate| candidate.equal?(stream) }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def attach
|
|
222
|
+
writer = @state.synchronize { @writer.dup unless @writer.closed? }
|
|
223
|
+
unless writer
|
|
224
|
+
# A failure during shutdown must not leave restored regular-file
|
|
225
|
+
# descriptors as an uncapped fallback. A readerless pipe fails closed.
|
|
226
|
+
reader, writer = IO.pipe
|
|
227
|
+
reader.close
|
|
228
|
+
end
|
|
229
|
+
@streams.each_with_index do |stream, index|
|
|
230
|
+
next if stream.closed?
|
|
231
|
+
|
|
232
|
+
stream.reopen(writer)
|
|
233
|
+
apply_properties(stream: stream, index: index, captured: true)
|
|
234
|
+
end
|
|
235
|
+
rescue StandardError
|
|
236
|
+
# Construction still has original descriptors available for rollback.
|
|
237
|
+
raise unless @backups.empty?
|
|
238
|
+
|
|
239
|
+
# If even dup/reopen fails, close the destinations rather than leave a
|
|
240
|
+
# restored regular-file descriptor able to bypass the retention cap.
|
|
241
|
+
failure = nil
|
|
242
|
+
@streams.each do |stream|
|
|
243
|
+
stream.close unless stream.closed?
|
|
244
|
+
rescue StandardError => e
|
|
245
|
+
failure ||= e
|
|
246
|
+
end
|
|
247
|
+
raise failure if failure
|
|
248
|
+
|
|
249
|
+
raise
|
|
250
|
+
ensure
|
|
251
|
+
writer.close if writer && !writer.closed?
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def restore
|
|
255
|
+
file = @state.synchronize do
|
|
256
|
+
prepared = @restore_file
|
|
257
|
+
@restore_file = nil
|
|
258
|
+
prepared
|
|
259
|
+
end
|
|
260
|
+
raise IOError, 'stdio restoration was not prepared' unless file
|
|
261
|
+
|
|
262
|
+
@streams.each_with_index do |stream, index|
|
|
263
|
+
next if stream.closed?
|
|
264
|
+
|
|
265
|
+
stream.reopen(file)
|
|
266
|
+
apply_properties(stream: stream, index: index, captured: false)
|
|
267
|
+
end
|
|
268
|
+
ensure
|
|
269
|
+
file&.close
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def release_backups
|
|
273
|
+
failure = nil
|
|
274
|
+
@backups.each do |backup|
|
|
275
|
+
backup.close unless backup.closed?
|
|
276
|
+
rescue StandardError => e
|
|
277
|
+
failure ||= e
|
|
278
|
+
end
|
|
279
|
+
@backups.clear
|
|
280
|
+
raise failure if failure
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def rollback
|
|
284
|
+
@streams.each_with_index do |stream, index|
|
|
285
|
+
stream.reopen(@backups[index])
|
|
286
|
+
apply_properties(stream: stream, index: index, captured: false)
|
|
287
|
+
end
|
|
288
|
+
ensure
|
|
289
|
+
begin
|
|
290
|
+
finish!
|
|
291
|
+
ensure
|
|
292
|
+
release_backups
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def check_failure!
|
|
297
|
+
@state.synchronize do
|
|
298
|
+
raise @failure if @failure
|
|
299
|
+
raise IOError, 'stdio capture is stopping' if @stopping
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def flush(deadline:)
|
|
304
|
+
wait_for_flush(deadline: deadline, pause: false)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def pause(deadline:)
|
|
308
|
+
wait_for_flush(deadline: deadline, pause: true)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def resume
|
|
312
|
+
file = @state.synchronize do
|
|
313
|
+
@pause_target = nil
|
|
314
|
+
prepared = @restore_file
|
|
315
|
+
@restore_file = nil
|
|
316
|
+
prepared
|
|
317
|
+
end
|
|
318
|
+
file&.close
|
|
319
|
+
@state.synchronize do
|
|
320
|
+
@paused = false
|
|
321
|
+
wake unless @failure || @stopping
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def finish!(deadline: nil) # rubocop:disable Naming/PredicateMethod -- Stops and joins the worker; false signals its deadline.
|
|
326
|
+
@state.synchronize do
|
|
327
|
+
unless @stopping
|
|
328
|
+
@stopping = true
|
|
329
|
+
wake unless @failure
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
remaining = [deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC), 0].max if deadline
|
|
333
|
+
!@worker.join(remaining).nil?
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def command(type:, arguments:)
|
|
337
|
+
@state.synchronize do
|
|
338
|
+
raise @failure if @failure
|
|
339
|
+
raise IOError, 'stdio capture is stopping' if @stopping
|
|
340
|
+
|
|
341
|
+
wait_until { !@command }
|
|
342
|
+
|
|
343
|
+
command = { type: type, arguments: arguments, complete: false }
|
|
344
|
+
@command = command
|
|
345
|
+
wake
|
|
346
|
+
wait_until { command[:complete] }
|
|
347
|
+
command[:result]
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
private
|
|
352
|
+
|
|
353
|
+
def apply_properties(stream:, index:, captured:)
|
|
354
|
+
sync, close_on_exec, external, internal = @properties[index]
|
|
355
|
+
stream.sync = captured || sync
|
|
356
|
+
stream.close_on_exec = close_on_exec
|
|
357
|
+
stream.set_encoding(external, internal) if external
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def wait_for_flush(deadline:, pause:)
|
|
361
|
+
@state.synchronize do
|
|
362
|
+
raise @failure if @failure
|
|
363
|
+
raise IOError, 'stdio capture is stopping' if @stopping
|
|
364
|
+
|
|
365
|
+
target = @requested_flush += 1
|
|
366
|
+
@pause_target = target if pause
|
|
367
|
+
wake
|
|
368
|
+
wait_until(deadline: deadline) { @completed_flush >= target && (!pause || @paused) }
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def wait_until(deadline: nil)
|
|
373
|
+
loop do
|
|
374
|
+
raise @failure if @failure
|
|
375
|
+
return true if yield
|
|
376
|
+
|
|
377
|
+
if deadline
|
|
378
|
+
remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
379
|
+
return false unless remaining.positive?
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
@changed.wait(@state, remaining)
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def wake
|
|
387
|
+
return if @wake_pending
|
|
388
|
+
|
|
389
|
+
@wake_writer.write_nonblock('.')
|
|
390
|
+
@wake_pending = true
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def next_action
|
|
394
|
+
@state.synchronize do
|
|
395
|
+
return [:stop] if @stopping
|
|
396
|
+
return [:wait] if @paused
|
|
397
|
+
return [:command, @command] if @command
|
|
398
|
+
|
|
399
|
+
# Only this thread completes flushes, after both the read and its
|
|
400
|
+
# corresponding sink write. A consumed chunk cannot hide from flush.
|
|
401
|
+
bytes = @reader.read_nonblock(READ_BYTES, exception: false)
|
|
402
|
+
return [:write, bytes] if bytes.is_a?(String)
|
|
403
|
+
return [:flush, @requested_flush] if @requested_flush > @completed_flush
|
|
404
|
+
|
|
405
|
+
[:wait]
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def drain
|
|
410
|
+
loop do
|
|
411
|
+
action, payload = next_action
|
|
412
|
+
case action
|
|
413
|
+
when :stop then break
|
|
414
|
+
when :wait then wait_for_input
|
|
415
|
+
when :write then @sink.write(payload)
|
|
416
|
+
when :command then execute_command(command: payload)
|
|
417
|
+
when :flush then complete_flush(target: payload)
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
rescue StandardError => e
|
|
421
|
+
# Closing the read end wakes blocked producers with EPIPE. Never log a
|
|
422
|
+
# drain failure to stderr: stderr may be the pipe which just failed.
|
|
423
|
+
begin
|
|
424
|
+
@reader.close unless @reader.closed?
|
|
425
|
+
ensure
|
|
426
|
+
@state.synchronize do
|
|
427
|
+
@failure = e
|
|
428
|
+
@changed.broadcast
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
raise
|
|
432
|
+
ensure
|
|
433
|
+
close_handles
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def wait_for_input
|
|
437
|
+
IO.select(@paused ? [@wake_reader] : [@reader, @wake_reader])
|
|
438
|
+
@state.synchronize do
|
|
439
|
+
@wake_reader.read_nonblock(READ_BYTES, exception: false)
|
|
440
|
+
@wake_pending = false
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def execute_command(command:)
|
|
445
|
+
result = @sink.public_send(command[:type], **command[:arguments])
|
|
446
|
+
@state.synchronize do
|
|
447
|
+
command[:result] = result
|
|
448
|
+
command[:complete] = true
|
|
449
|
+
@command = nil
|
|
450
|
+
@changed.broadcast
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def complete_flush(target:)
|
|
455
|
+
@sink.flush
|
|
456
|
+
prepare = @state.synchronize { @pause_target && target >= @pause_target }
|
|
457
|
+
file = File.new(@path, 'ab') if prepare
|
|
458
|
+
retained = false
|
|
459
|
+
@state.synchronize do
|
|
460
|
+
@completed_flush = target
|
|
461
|
+
if file && @pause_target && target >= @pause_target
|
|
462
|
+
@restore_file = file
|
|
463
|
+
@paused = true
|
|
464
|
+
retained = true
|
|
465
|
+
end
|
|
466
|
+
@changed.broadcast
|
|
467
|
+
end
|
|
468
|
+
ensure
|
|
469
|
+
file.close if file && !retained
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def close_handles
|
|
473
|
+
failure = nil
|
|
474
|
+
[@reader, @writer, @wake_reader, @wake_writer, @restore_file, @sink].compact.each do |handle|
|
|
475
|
+
if handle.equal?(@writer)
|
|
476
|
+
@state.synchronize { handle.close unless handle.closed? }
|
|
477
|
+
else
|
|
478
|
+
handle.close unless handle.closed?
|
|
479
|
+
end
|
|
480
|
+
rescue StandardError => e
|
|
481
|
+
failure ||= e
|
|
482
|
+
@state.synchronize do
|
|
483
|
+
@failure ||= e
|
|
484
|
+
@changed.broadcast
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
raise failure if failure
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
private_constant :StdioCapture
|
|
491
|
+
end
|
|
492
|
+
end
|
data/lib/legion/logging.rb
CHANGED
|
@@ -76,36 +76,41 @@ module Legion
|
|
|
76
76
|
Hooks.clear_hooks!
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
-
def setup(level: 'debug', format: :text, async: true, **options)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
79
|
+
def setup(level: 'debug', format: :text, async: true, shutdown_timeout: 2, **options)
|
|
80
|
+
with_lifecycle_lock do
|
|
81
|
+
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + shutdown_timeout if shutdown_timeout
|
|
82
|
+
return false if @async_writer && stop_async_writer(timeout: shutdown_timeout) == false
|
|
83
|
+
|
|
84
|
+
remaining = [deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC), 0].max if deadline
|
|
85
|
+
return false if output(shutdown_timeout: remaining, **options) == false
|
|
86
|
+
|
|
87
|
+
log_level(level)
|
|
88
|
+
log_format(format: format, **options)
|
|
89
|
+
@color = options[:color]
|
|
90
|
+
@color = format != :json && (options[:color] || (options[:color].nil? && options[:log_file].nil?))
|
|
91
|
+
@current_settings = {
|
|
92
|
+
level: level,
|
|
93
|
+
format: format.to_sym,
|
|
94
|
+
async: async,
|
|
95
|
+
trace: options.fetch(:trace, true),
|
|
96
|
+
trace_size: options.fetch(:trace_size, 4),
|
|
97
|
+
extended: options.fetch(:extended, true),
|
|
98
|
+
log_file: options[:log_file],
|
|
99
|
+
log_stdout: options[:log_stdout],
|
|
100
|
+
rotation: options[:rotation],
|
|
101
|
+
include_pid: options.fetch(:include_pid, false),
|
|
102
|
+
color: @color
|
|
103
|
+
}.freeze
|
|
104
|
+
@configuration_generation = configuration_generation + 1
|
|
105
|
+
Legion::Logging::Redactor.refresh_patterns! if defined?(Legion::Logging::Redactor)
|
|
106
|
+
if async
|
|
107
|
+
start_async_writer(buffer_size: Legion::Logging::Settings.async_buffer_size)
|
|
108
|
+
else
|
|
109
|
+
@async_writer = nil
|
|
110
|
+
@async = false
|
|
111
|
+
log.async_writer = nil
|
|
112
|
+
true
|
|
113
|
+
end
|
|
109
114
|
end
|
|
110
115
|
end
|
|
111
116
|
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.
|
|
4
|
+
version: 1.5.21
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -70,12 +70,16 @@ files:
|
|
|
70
70
|
- lib/legion/logging/method_tracer.rb
|
|
71
71
|
- lib/legion/logging/methods.rb
|
|
72
72
|
- lib/legion/logging/multi_io.rb
|
|
73
|
+
- lib/legion/logging/output_logger.rb
|
|
73
74
|
- lib/legion/logging/redactor.rb
|
|
75
|
+
- lib/legion/logging/rotating_file.rb
|
|
76
|
+
- lib/legion/logging/rotation.rb
|
|
74
77
|
- lib/legion/logging/settings.rb
|
|
75
78
|
- lib/legion/logging/shipper.rb
|
|
76
79
|
- lib/legion/logging/shipper/file_transport.rb
|
|
77
80
|
- lib/legion/logging/shipper/http_transport.rb
|
|
78
81
|
- lib/legion/logging/siem_exporter.rb
|
|
82
|
+
- lib/legion/logging/stdio_rotator.rb
|
|
79
83
|
- lib/legion/logging/tagged_logger.rb
|
|
80
84
|
- lib/legion/logging/version.rb
|
|
81
85
|
- lib/legion/service.rb
|