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,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Logging
|
|
7
|
+
# Settings and descriptor detection shared by the byte-capped file and stdio sinks.
|
|
8
|
+
module Rotation
|
|
9
|
+
DEFAULTS = {
|
|
10
|
+
enabled: true,
|
|
11
|
+
max_size_mb: 100,
|
|
12
|
+
max_files: 5,
|
|
13
|
+
stdio: true
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
F_GETPATH = 50 # macOS fcntl(2) command; not exposed by the Fcntl module
|
|
17
|
+
MAXPATHLEN = 1024
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
# Normalizes rotation settings. Accepts nil, false (disabled), or a Hash
|
|
21
|
+
# with symbol or string keys. Missing keys fall back to DEFAULTS.
|
|
22
|
+
def resolve(raw:)
|
|
23
|
+
overrides = if raw == false
|
|
24
|
+
{ enabled: false }
|
|
25
|
+
elsif raw.is_a?(Hash)
|
|
26
|
+
raw.transform_keys(&:to_sym)
|
|
27
|
+
else
|
|
28
|
+
{}
|
|
29
|
+
end
|
|
30
|
+
merged = DEFAULTS.merge(overrides.slice(*DEFAULTS.keys))
|
|
31
|
+
merged[:enabled] = merged[:enabled] != false
|
|
32
|
+
merged[:stdio] = merged[:stdio] != false
|
|
33
|
+
merged[:max_size_mb] = positive_number(merged[:max_size_mb], DEFAULTS[:max_size_mb])
|
|
34
|
+
merged[:max_files] = positive_number(merged[:max_files], DEFAULTS[:max_files]).to_i
|
|
35
|
+
merged[:max_files] = DEFAULTS[:max_files] unless merged[:max_files].positive?
|
|
36
|
+
bytes = merged[:max_size_mb] * 1024 * 1024
|
|
37
|
+
unless bytes.finite?
|
|
38
|
+
merged[:max_size_mb] = DEFAULTS[:max_size_mb]
|
|
39
|
+
bytes = merged[:max_size_mb] * 1024 * 1024
|
|
40
|
+
end
|
|
41
|
+
merged[:max_bytes] = [bytes.to_i, 1].max
|
|
42
|
+
merged
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Reads rotation settings from Legion::Settings once it has loaded.
|
|
46
|
+
# Guarded against re-entry because Settings may log while loading.
|
|
47
|
+
def from_settings
|
|
48
|
+
return nil unless defined?(Legion::Settings) && Legion::Settings.respond_to?(:[])
|
|
49
|
+
return nil if Thread.current[:legion_logging_rotation_lookup]
|
|
50
|
+
|
|
51
|
+
lookup_started = true
|
|
52
|
+
Thread.current[:legion_logging_rotation_lookup] = true
|
|
53
|
+
return nil if Legion::Settings.respond_to?(:loaded?) && !Legion::Settings.loaded?
|
|
54
|
+
|
|
55
|
+
logging = Legion::Settings[:logging]
|
|
56
|
+
logging[:rotation] if logging.is_a?(Hash)
|
|
57
|
+
rescue StandardError => e
|
|
58
|
+
warn("Legion::Logging::Rotation.from_settings failed: #{e.class}: #{e.message}")
|
|
59
|
+
nil
|
|
60
|
+
ensure
|
|
61
|
+
Thread.current[:legion_logging_rotation_lookup] = nil if lookup_started
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Shifts path.(n-1) -> path.n ... path -> path.1, dropping the oldest.
|
|
65
|
+
def shift_files(path:, max_files:)
|
|
66
|
+
oldest = "#{path}.#{max_files}"
|
|
67
|
+
FileUtils.rm(oldest, force: false) if File.exist?(oldest)
|
|
68
|
+
(max_files - 1).downto(1) do |i|
|
|
69
|
+
src = "#{path}.#{i}"
|
|
70
|
+
File.rename(src, "#{path}.#{i + 1}") if File.exist?(src)
|
|
71
|
+
end
|
|
72
|
+
File.rename(path, "#{path}.1") if File.exist?(path)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Returns the filesystem path behind an IO if it is a regular file opened
|
|
76
|
+
# in append mode (launchd StandardOutPath, systemd append:, shell >>), or nil.
|
|
77
|
+
# Non-append redirects (shell >) are left alone.
|
|
78
|
+
def regular_file_path(io:)
|
|
79
|
+
return nil unless io.respond_to?(:stat) && io.stat.file?
|
|
80
|
+
return nil unless append_mode?(io: io)
|
|
81
|
+
|
|
82
|
+
path = fd_path(io)
|
|
83
|
+
return nil unless path && File.file?(path)
|
|
84
|
+
return nil unless same_file?(io: io, path: path)
|
|
85
|
+
|
|
86
|
+
path
|
|
87
|
+
rescue StandardError => e
|
|
88
|
+
warn("Legion::Logging::Rotation.regular_file_path failed: #{e.class}: #{e.message}")
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def same_file?(io:, path:)
|
|
93
|
+
return false unless File.exist?(path)
|
|
94
|
+
|
|
95
|
+
io_stat = io.stat
|
|
96
|
+
path_stat = File.stat(path)
|
|
97
|
+
io_stat.ino == path_stat.ino && io_stat.dev == path_stat.dev
|
|
98
|
+
rescue SystemCallError, IOError => e
|
|
99
|
+
warn("Legion::Logging::Rotation.same_file? path=#{path} failed: #{e.class}: #{e.message}")
|
|
100
|
+
false
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def append_mode?(io:)
|
|
104
|
+
require 'fcntl'
|
|
105
|
+
io.fcntl(Fcntl::F_GETFL).anybits?(File::APPEND)
|
|
106
|
+
rescue NotImplementedError, SystemCallError, IOError => e
|
|
107
|
+
warn("Legion::Logging::Rotation.append_mode? failed: #{e.class}: #{e.message}")
|
|
108
|
+
false
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def fd_path(io)
|
|
114
|
+
if io.respond_to?(:path)
|
|
115
|
+
path = File.expand_path(io.path)
|
|
116
|
+
return path if same_file?(io: io, path: path)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
if RUBY_PLATFORM.include?('darwin')
|
|
120
|
+
require 'fcntl'
|
|
121
|
+
buf = "\0" * MAXPATHLEN
|
|
122
|
+
io.fcntl(F_GETPATH, buf)
|
|
123
|
+
buf.unpack1('Z*')
|
|
124
|
+
else
|
|
125
|
+
proc_link = "/proc/self/fd/#{io.fileno}"
|
|
126
|
+
File.readlink(proc_link) if File.symlink?(proc_link)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def positive_number(value, default)
|
|
131
|
+
number = Float(value)
|
|
132
|
+
number.positive? && number.finite? ? number : default
|
|
133
|
+
rescue ArgumentError, TypeError => e
|
|
134
|
+
warn("Legion::Logging::Rotation invalid numeric limit: #{e.class}: #{e.message}")
|
|
135
|
+
default
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
require_relative 'rotating_file'
|
|
143
|
+
require_relative 'stdio_rotator'
|
|
@@ -3,15 +3,37 @@
|
|
|
3
3
|
module Legion
|
|
4
4
|
module Logging
|
|
5
5
|
module Settings
|
|
6
|
+
# Log levels forwarded to the transport bus (and in-process Hooks) by
|
|
7
|
+
# default. Preserves historic behavior: only warn/error/fatal logs build a
|
|
8
|
+
# structured event. Override at runtime via logging.transport.log_levels
|
|
9
|
+
# (e.g. add :debug/:info) — see Methods#forwardable_log_levels.
|
|
10
|
+
DEFAULT_FORWARD_LOG_LEVELS = %i[warn error fatal].freeze
|
|
11
|
+
|
|
6
12
|
def self.default
|
|
7
13
|
{
|
|
8
|
-
level:
|
|
9
|
-
trace:
|
|
10
|
-
trace_size:
|
|
11
|
-
extended:
|
|
12
|
-
backtrace_limit:
|
|
14
|
+
level: :info,
|
|
15
|
+
trace: true,
|
|
16
|
+
trace_size: 4,
|
|
17
|
+
extended: true,
|
|
18
|
+
backtrace_limit: nil,
|
|
19
|
+
forward_log_levels: DEFAULT_FORWARD_LOG_LEVELS,
|
|
20
|
+
rotation: {
|
|
21
|
+
enabled: true,
|
|
22
|
+
max_size_mb: 100,
|
|
23
|
+
max_files: 5,
|
|
24
|
+
stdio: true
|
|
25
|
+
}
|
|
13
26
|
}
|
|
14
27
|
end
|
|
28
|
+
|
|
29
|
+
def self.async_buffer_size
|
|
30
|
+
return 10_000 unless defined?(Legion::Settings) && Legion::Settings.respond_to?(:[])
|
|
31
|
+
return 10_000 unless Legion::Settings.respond_to?(:loaded?) ? Legion::Settings.loaded? : true
|
|
32
|
+
|
|
33
|
+
logging = Legion::Settings[:logging]
|
|
34
|
+
async = logging[:async] if logging.is_a?(Hash)
|
|
35
|
+
async.is_a?(Hash) ? (async[:buffer_size] || 10_000) : 10_000
|
|
36
|
+
end
|
|
15
37
|
end
|
|
16
38
|
end
|
|
17
39
|
end
|
|
@@ -26,7 +26,7 @@ module Legion
|
|
|
26
26
|
end
|
|
27
27
|
true
|
|
28
28
|
rescue StandardError => e
|
|
29
|
-
|
|
29
|
+
Kernel.warn("FileTransport ship failed: #{e.message}")
|
|
30
30
|
false
|
|
31
31
|
end
|
|
32
32
|
|
|
@@ -40,6 +40,7 @@ module Legion
|
|
|
40
40
|
|
|
41
41
|
def settings_path
|
|
42
42
|
return nil unless defined?(Legion::Settings)
|
|
43
|
+
return nil unless Legion::Settings.respond_to?(:loaded?) ? Legion::Settings.loaded? : true
|
|
43
44
|
|
|
44
45
|
Legion::Settings.dig(:logging, :shipper, :file, :path)
|
|
45
46
|
end
|
|
@@ -24,7 +24,7 @@ module Legion
|
|
|
24
24
|
response = post(uri, body)
|
|
25
25
|
response.is_a?(Net::HTTPSuccess)
|
|
26
26
|
rescue StandardError => e
|
|
27
|
-
|
|
27
|
+
Kernel.warn("HttpTransport ship failed: #{e.message}")
|
|
28
28
|
false
|
|
29
29
|
end
|
|
30
30
|
|
|
@@ -36,7 +36,7 @@ module Legion
|
|
|
36
36
|
apply_auth(req, uri)
|
|
37
37
|
|
|
38
38
|
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https',
|
|
39
|
-
open_timeout: 5, read_timeout: 10) do |http|
|
|
39
|
+
open_timeout: 5, read_timeout: 10, write_timeout: 10) do |http|
|
|
40
40
|
http.request(req, body)
|
|
41
41
|
end
|
|
42
42
|
end
|
|
@@ -67,12 +67,14 @@ module Legion
|
|
|
67
67
|
|
|
68
68
|
def auth_token
|
|
69
69
|
return nil unless defined?(Legion::Settings)
|
|
70
|
+
return nil unless Legion::Settings.respond_to?(:loaded?) ? Legion::Settings.loaded? : true
|
|
70
71
|
|
|
71
72
|
Legion::Settings.dig(:logging, :shipper, :auth_token)
|
|
72
73
|
end
|
|
73
74
|
|
|
74
75
|
def resolve_endpoint
|
|
75
76
|
return nil unless defined?(Legion::Settings)
|
|
77
|
+
return nil unless Legion::Settings.respond_to?(:loaded?) ? Legion::Settings.loaded? : true
|
|
76
78
|
|
|
77
79
|
Legion::Settings.dig(:logging, :shipper, :endpoint)
|
|
78
80
|
end
|
|
@@ -8,134 +8,183 @@ module Legion
|
|
|
8
8
|
module Logging
|
|
9
9
|
module Shipper
|
|
10
10
|
LEVEL_ORDER = %w[debug info warn error fatal].freeze
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
TRANSPORTS = { file: FileTransport, http: HttpTransport }.freeze
|
|
12
|
+
MUTEX = Mutex.new
|
|
13
|
+
WAKE = ConditionVariable.new
|
|
14
|
+
CHANGED = ConditionVariable.new
|
|
15
|
+
private_constant :MUTEX, :WAKE, :CHANGED
|
|
16
|
+
|
|
17
|
+
@buffer = []
|
|
18
|
+
@accepted = 0
|
|
19
|
+
@delivered = 0
|
|
20
|
+
@attempt = 0
|
|
21
|
+
@flush_target = 0
|
|
16
22
|
|
|
17
23
|
class << self
|
|
18
24
|
def ship(event)
|
|
19
25
|
return unless enabled?
|
|
20
26
|
return unless shippable_level?(event[:level] || event['level'])
|
|
27
|
+
return unless TRANSPORTS.key?(transport_type)
|
|
21
28
|
|
|
22
|
-
redacted
|
|
23
|
-
|
|
24
|
-
buffer_event(redacted) if transport
|
|
25
|
-
end
|
|
29
|
+
redacted = Redactor.redact(event)
|
|
30
|
+
raise ClosedQueueError, 'log shipper is stopping' unless start
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
batch = @mutex.synchronize do
|
|
30
|
-
return true if @buffer.nil? || @buffer.empty?
|
|
32
|
+
MUTEX.synchronize do
|
|
33
|
+
raise ThreadError, 'log shipper cannot enqueue from its delivery thread' if Thread.current.equal?(@flush_thread)
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
@
|
|
34
|
-
|
|
35
|
+
CHANGED.wait(MUTEX) while !@stopping && @accepted - @delivered >= @capacity
|
|
36
|
+
raise ClosedQueueError, 'log shipper is stopping' if @stopping
|
|
37
|
+
|
|
38
|
+
@buffer << redacted
|
|
39
|
+
@accepted += 1
|
|
40
|
+
@flush_requested = true if @buffer.size >= @batch_size
|
|
41
|
+
WAKE.signal if @flush_requested
|
|
35
42
|
end
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def flush(timeout: 5)
|
|
47
|
+
return true if MUTEX.synchronize { @accepted == @delivered }
|
|
48
|
+
return false unless start
|
|
49
|
+
|
|
50
|
+
deadline = timeout && (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + timeout)
|
|
51
|
+
MUTEX.synchronize do
|
|
52
|
+
return false if Thread.current.equal?(@flush_thread)
|
|
53
|
+
|
|
54
|
+
target = @accepted
|
|
55
|
+
attempt = @attempt
|
|
56
|
+
@flush_target = [@flush_target, target].max
|
|
57
|
+
@flush_requested = true
|
|
58
|
+
WAKE.signal
|
|
59
|
+
while @delivered < target
|
|
60
|
+
return false if @attempt > attempt && !@last_success
|
|
61
|
+
return false unless @flush_thread&.alive?
|
|
36
62
|
|
|
37
|
-
|
|
38
|
-
|
|
63
|
+
remaining = deadline && (deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC))
|
|
64
|
+
return false if remaining && remaining <= 0
|
|
39
65
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
66
|
+
CHANGED.wait(MUTEX, remaining)
|
|
67
|
+
end
|
|
68
|
+
true
|
|
69
|
+
end
|
|
43
70
|
end
|
|
44
71
|
|
|
45
72
|
def start
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return if @flush_thread&.alive?
|
|
50
|
-
|
|
51
|
-
@
|
|
52
|
-
@
|
|
53
|
-
@
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
sleep interval
|
|
58
|
-
flush
|
|
59
|
-
end
|
|
73
|
+
return false unless enabled?
|
|
74
|
+
|
|
75
|
+
MUTEX.synchronize do
|
|
76
|
+
return !@stopping if @flush_thread&.alive?
|
|
77
|
+
|
|
78
|
+
@capacity = Legion::Settings.dig(:logging, :shipper, :buffer_size) || 10_000
|
|
79
|
+
@batch_size = [batch_size, @capacity].min
|
|
80
|
+
@interval = flush_interval
|
|
81
|
+
unless @capacity.is_a?(Integer) && @capacity.positive? && @batch_size.is_a?(Integer) && @batch_size.positive? &&
|
|
82
|
+
@interval.is_a?(Numeric) && @interval.positive?
|
|
83
|
+
raise ArgumentError, 'shipper buffer_size, batch_size, and flush_interval must be positive'
|
|
60
84
|
end
|
|
85
|
+
|
|
86
|
+
@transport = TRANSPORTS[transport_type]
|
|
87
|
+
return false unless @transport
|
|
88
|
+
|
|
89
|
+
@stopping = false
|
|
90
|
+
@flush_requested = @buffer.size >= @batch_size
|
|
91
|
+
@flush_thread = Thread.new { consume }
|
|
61
92
|
@flush_thread.name = 'legion-log-shipper'
|
|
62
93
|
@flush_thread.abort_on_exception = false
|
|
94
|
+
true
|
|
63
95
|
end
|
|
64
96
|
end
|
|
65
97
|
|
|
66
|
-
def stop
|
|
67
|
-
|
|
68
|
-
thread =
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
98
|
+
def stop(timeout: 5)
|
|
99
|
+
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + timeout if timeout
|
|
100
|
+
thread = MUTEX.synchronize do
|
|
101
|
+
@stopping = true
|
|
102
|
+
WAKE.broadcast
|
|
103
|
+
CHANGED.broadcast
|
|
104
|
+
if !@flush_thread&.alive? && @accepted > @delivered
|
|
105
|
+
@flush_thread = Thread.new { consume }
|
|
106
|
+
@flush_thread.name = 'legion-log-shipper'
|
|
107
|
+
@flush_thread.abort_on_exception = false
|
|
108
|
+
end
|
|
109
|
+
@flush_thread
|
|
110
|
+
end
|
|
111
|
+
return MUTEX.synchronize { @accepted == @delivered } unless thread
|
|
112
|
+
return false if Thread.current.equal?(thread)
|
|
113
|
+
|
|
114
|
+
remaining = [deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC), 0].max if deadline
|
|
115
|
+
thread.join(remaining)
|
|
116
|
+
MUTEX.synchronize { !thread.alive? && @accepted == @delivered }
|
|
72
117
|
end
|
|
73
118
|
|
|
74
119
|
def enabled?
|
|
75
120
|
return false unless defined?(Legion::Settings)
|
|
121
|
+
return false unless Legion::Settings.respond_to?(:loaded?) ? Legion::Settings.loaded? : true
|
|
76
122
|
|
|
77
123
|
Legion::Settings.dig(:logging, :shipper, :enabled) == true
|
|
78
124
|
end
|
|
79
125
|
|
|
80
126
|
private
|
|
81
127
|
|
|
82
|
-
def
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
128
|
+
def consume
|
|
129
|
+
loop do
|
|
130
|
+
batch = MUTEX.synchronize do
|
|
131
|
+
WAKE.wait(MUTEX, @interval) unless @flush_requested || @stopping
|
|
132
|
+
@flush_requested = false
|
|
133
|
+
@buffer.shift(@batch_size)
|
|
134
|
+
end
|
|
135
|
+
delivered = batch.empty? || deliver(@transport, batch)
|
|
136
|
+
stopping = MUTEX.synchronize do
|
|
137
|
+
@attempt += 1
|
|
138
|
+
@last_success = delivered
|
|
139
|
+
if delivered
|
|
140
|
+
@delivered += batch.size
|
|
141
|
+
@flush_requested = @buffer.size >= @batch_size || @delivered < @flush_target
|
|
142
|
+
else
|
|
143
|
+
@buffer.prepend(*batch)
|
|
144
|
+
end
|
|
145
|
+
CHANGED.broadcast
|
|
146
|
+
@stopping && (!delivered || @buffer.empty?)
|
|
147
|
+
end
|
|
148
|
+
break if stopping
|
|
149
|
+
end
|
|
150
|
+
ensure
|
|
151
|
+
MUTEX.synchronize do
|
|
152
|
+
@stopping = true
|
|
153
|
+
CHANGED.broadcast
|
|
90
154
|
end
|
|
91
|
-
|
|
92
|
-
flush if full
|
|
93
155
|
end
|
|
94
156
|
|
|
95
157
|
def deliver(transport, batch)
|
|
96
|
-
|
|
97
|
-
transport.ship_batch(batch)
|
|
98
|
-
else
|
|
99
|
-
batch.all? { |event| transport.ship(event) }
|
|
100
|
-
end
|
|
158
|
+
transport.ship_batch(batch)
|
|
101
159
|
rescue StandardError => e
|
|
102
|
-
|
|
160
|
+
Kernel.warn("Shipper deliver failed: #{e.message}")
|
|
103
161
|
false
|
|
104
162
|
end
|
|
105
163
|
|
|
106
164
|
def shippable_level?(level)
|
|
107
165
|
return true if level.nil?
|
|
108
166
|
|
|
109
|
-
|
|
110
|
-
LEVEL_ORDER.index(level.to_s.downcase).to_i >= LEVEL_ORDER.index(min).to_i
|
|
167
|
+
LEVEL_ORDER.index(level.to_s.downcase).to_i >= LEVEL_ORDER.index(minimum_level).to_i
|
|
111
168
|
end
|
|
112
169
|
|
|
113
170
|
def transport_type
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
key = Legion::Settings.dig(:logging, :shipper, :transport)
|
|
171
|
+
key = Legion::Settings.dig(:logging, :shipper, :transport) if defined?(Legion::Settings)
|
|
117
172
|
key ? key.to_sym : :file
|
|
118
173
|
end
|
|
119
174
|
|
|
120
175
|
def batch_size
|
|
121
|
-
return 100 unless defined?(Legion::Settings)
|
|
122
|
-
|
|
123
176
|
Legion::Settings.dig(:logging, :shipper, :batch_size) || 100
|
|
124
177
|
end
|
|
125
178
|
|
|
126
179
|
def flush_interval
|
|
127
|
-
return 5 unless defined?(Legion::Settings)
|
|
128
|
-
|
|
129
180
|
Legion::Settings.dig(:logging, :shipper, :flush_interval) || 5
|
|
130
181
|
end
|
|
131
182
|
|
|
132
183
|
def minimum_level
|
|
133
|
-
return 'warn' unless defined?(Legion::Settings)
|
|
134
|
-
|
|
135
184
|
levels = Legion::Settings.dig(:logging, :shipper, :levels)
|
|
136
185
|
return 'warn' unless levels.is_a?(Array) && !levels.empty?
|
|
137
186
|
|
|
138
|
-
levels.min_by { |
|
|
187
|
+
levels.min_by { |level| LEVEL_ORDER.index(level.to_s) || 99 }.to_s
|
|
139
188
|
end
|
|
140
189
|
end
|
|
141
190
|
end
|
|
@@ -29,7 +29,7 @@ module Legion
|
|
|
29
29
|
req.body = ::JSON.dump({ event: redact_phi(event.to_s), time: Time.now.to_f })
|
|
30
30
|
|
|
31
31
|
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https',
|
|
32
|
-
open_timeout: 5, read_timeout: 10) do |http|
|
|
32
|
+
open_timeout: 5, read_timeout: 10, write_timeout: 10) do |http|
|
|
33
33
|
http.request(req)
|
|
34
34
|
end
|
|
35
35
|
rescue StandardError => e
|