legion-logging 1.5.5 → 1.5.19

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.
@@ -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
- Legion::Logging.error("HttpTransport ship failed: #{e.message}") if defined?(Legion::Logging)
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,128 +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
- TRANSPORTS = {
13
- file: FileTransport,
14
- http: HttpTransport
15
- }.freeze
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)
28
+
29
+ redacted = Redactor.redact(event)
30
+ raise ClosedQueueError, 'log shipper is stopping' unless start
31
+
32
+ MUTEX.synchronize do
33
+ raise ThreadError, 'log shipper cannot enqueue from its delivery thread' if Thread.current.equal?(@flush_thread)
34
+
35
+ CHANGED.wait(MUTEX) while !@stopping && @accepted - @delivered >= @capacity
36
+ raise ClosedQueueError, 'log shipper is stopping' if @stopping
21
37
 
22
- redacted = Redactor.redact(event)
23
- transport = TRANSPORTS[transport_type]
24
- buffer_event(redacted) if transport
38
+ @buffer << redacted
39
+ @accepted += 1
40
+ @flush_requested = true if @buffer.size >= @batch_size
41
+ WAKE.signal if @flush_requested
42
+ end
43
+ true
25
44
  end
26
45
 
27
- def flush
28
- return if @buffer.nil? || @buffer.empty?
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)
29
53
 
30
- transport = TRANSPORTS[transport_type]
31
- return unless transport
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?
32
62
 
33
- @flush_mutex ||= Mutex.new
34
- @flush_mutex.synchronize do
35
- batch = nil
36
- @mutex.synchronize { batch = @buffer.dup }
37
- return if batch.empty?
63
+ remaining = deadline && (deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC))
64
+ return false if remaining && remaining <= 0
38
65
 
39
- delivered = deliver(transport, batch)
40
- @mutex.synchronize { @buffer.shift(batch.size) if delivered }
41
- delivered
66
+ CHANGED.wait(MUTEX, remaining)
67
+ end
68
+ true
42
69
  end
43
70
  end
44
71
 
45
72
  def start
46
- return unless enabled?
47
- return if @flush_thread&.alive?
48
-
49
- @buffer ||= []
50
- @mutex ||= Mutex.new
51
- @flush_mutex ||= Mutex.new
52
- interval = flush_interval
53
- @flush_thread = Thread.new do
54
- loop do
55
- sleep interval
56
- flush
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'
57
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 }
92
+ @flush_thread.name = 'legion-log-shipper'
93
+ @flush_thread.abort_on_exception = false
94
+ true
58
95
  end
59
- @flush_thread.abort_on_exception = false
60
96
  end
61
97
 
62
- def stop
63
- @flush_thread&.kill
64
- @flush_thread = nil
65
- flush
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 }
66
117
  end
67
118
 
68
119
  def enabled?
69
120
  return false unless defined?(Legion::Settings)
121
+ return false unless Legion::Settings.respond_to?(:loaded?) ? Legion::Settings.loaded? : true
70
122
 
71
123
  Legion::Settings.dig(:logging, :shipper, :enabled) == true
72
124
  end
73
125
 
74
126
  private
75
127
 
76
- def buffer_event(event)
77
- @buffer ||= []
78
- @mutex ||= Mutex.new
79
-
80
- full = false
81
- @mutex.synchronize do
82
- @buffer << event
83
- full = @buffer.size >= batch_size
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
84
154
  end
85
-
86
- flush if full
87
155
  end
88
156
 
89
157
  def deliver(transport, batch)
90
- if transport.respond_to?(:ship_batch)
91
- transport.ship_batch(batch)
92
- else
93
- batch.all? { |event| transport.ship(event) }
94
- end
158
+ transport.ship_batch(batch)
95
159
  rescue StandardError => e
96
- Legion::Logging.error("Shipper deliver failed: #{e.message}") if defined?(Legion::Logging)
160
+ Kernel.warn("Shipper deliver failed: #{e.message}")
97
161
  false
98
162
  end
99
163
 
100
164
  def shippable_level?(level)
101
165
  return true if level.nil?
102
166
 
103
- min = minimum_level
104
- 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
105
168
  end
106
169
 
107
170
  def transport_type
108
- return :file unless defined?(Legion::Settings)
109
-
110
- key = Legion::Settings.dig(:logging, :shipper, :transport)
171
+ key = Legion::Settings.dig(:logging, :shipper, :transport) if defined?(Legion::Settings)
111
172
  key ? key.to_sym : :file
112
173
  end
113
174
 
114
175
  def batch_size
115
- return 100 unless defined?(Legion::Settings)
116
-
117
176
  Legion::Settings.dig(:logging, :shipper, :batch_size) || 100
118
177
  end
119
178
 
120
179
  def flush_interval
121
- return 5 unless defined?(Legion::Settings)
122
-
123
180
  Legion::Settings.dig(:logging, :shipper, :flush_interval) || 5
124
181
  end
125
182
 
126
183
  def minimum_level
127
- return 'warn' unless defined?(Legion::Settings)
128
-
129
184
  levels = Legion::Settings.dig(:logging, :shipper, :levels)
130
185
  return 'warn' unless levels.is_a?(Array) && !levels.empty?
131
186
 
132
- levels.min_by { |l| LEVEL_ORDER.index(l.to_s) || 99 }.to_s
187
+ levels.min_by { |level| LEVEL_ORDER.index(level.to_s) || 99 }.to_s
133
188
  end
134
189
  end
135
190
  end
@@ -28,7 +28,8 @@ module Legion
28
28
  req['Content-Type'] = 'application/json'
29
29
  req.body = ::JSON.dump({ event: redact_phi(event.to_s), time: Time.now.to_f })
30
30
 
31
- Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
31
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https',
32
+ open_timeout: 5, read_timeout: 10, write_timeout: 10) do |http|
32
33
  http.request(req)
33
34
  end
34
35
  rescue StandardError => e
@@ -32,44 +32,44 @@ module Legion
32
32
  @level_value
33
33
  end
34
34
 
35
- def debug(message = nil)
35
+ def debug(message = nil, **ctx)
36
36
  return unless @level_value < 1
37
37
 
38
38
  message = yield if message.nil? && block_given?
39
- with_segments { dispatch(:debug, message) }
39
+ with_segments { dispatch(:debug, message, **ctx) }
40
40
  end
41
41
 
42
- def info(message = nil)
42
+ def info(message = nil, **ctx)
43
43
  return unless @level_value < 2
44
44
 
45
45
  message = yield if message.nil? && block_given?
46
- with_segments { dispatch(:info, message) }
46
+ with_segments { dispatch(:info, message, **ctx) }
47
47
  end
48
48
 
49
- def warn(message = nil)
49
+ def warn(message = nil, **ctx)
50
50
  return unless @level_value < 3
51
51
 
52
52
  message = yield if message.nil? && block_given?
53
- with_segments { dispatch(:warn, message) }
53
+ with_segments { dispatch(:warn, message, **ctx) }
54
54
  end
55
55
 
56
- def error(message = nil)
56
+ def error(message = nil, **ctx)
57
57
  return unless @level_value < 4
58
58
 
59
59
  message = yield if message.nil? && block_given?
60
- with_segments { dispatch(:error, message) }
60
+ with_segments { dispatch(:error, message, **ctx) }
61
61
  end
62
62
 
63
- def fatal(message = nil)
63
+ def fatal(message = nil, **ctx)
64
64
  return unless @level_value < 5
65
65
 
66
66
  message = yield if message.nil? && block_given?
67
- with_segments { dispatch(:fatal, message) }
67
+ with_segments { dispatch(:fatal, message, **ctx) }
68
68
  end
69
69
 
70
- def unknown(message = nil)
70
+ def unknown(message = nil, **ctx)
71
71
  message = yield if message.nil? && block_given?
72
- with_segments { dispatch(:unknown, message) }
72
+ with_segments { dispatch(:unknown, message, **ctx) }
73
73
  end
74
74
 
75
75
  def trace(raw_message = nil, size: @trace_size, log_caller: true)
@@ -94,21 +94,23 @@ module Legion
94
94
 
95
95
  private
96
96
 
97
- def dispatch(level, message)
97
+ def dispatch(level, message, **ctx)
98
98
  return unless defined?(Legion::Logging)
99
99
 
100
100
  if Legion::Logging.respond_to?(:emit_tagged)
101
- Legion::Logging.emit_tagged(level, message, segments: @segments)
101
+ Legion::Logging.emit_tagged(level, message, segments: @segments, **ctx)
102
102
  return
103
103
  end
104
104
 
105
105
  if Legion::Logging.respond_to?(level)
106
- Legion::Logging.public_send(level, message)
106
+ Legion::Logging.public_send(level, message, **ctx)
107
107
  return
108
108
  end
109
109
 
110
110
  fallback = fallback_level(level)
111
- Legion::Logging.public_send(fallback, message) if fallback && Legion::Logging.respond_to?(fallback)
111
+ return unless fallback && Legion::Logging.respond_to?(fallback)
112
+
113
+ Legion::Logging.public_send(fallback, message, **ctx)
112
114
  end
113
115
 
114
116
  def fallback_level(level)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Logging
5
- VERSION = '1.5.5'
5
+ VERSION = '1.5.19'
6
6
  end
7
7
  end
@@ -76,36 +76,37 @@ module Legion
76
76
  Hooks.clear_hooks!
77
77
  end
78
78
 
79
- def setup(level: 'debug', format: :text, async: true, **options)
80
- output(**options)
81
- log_level(level)
82
- log_format(format: format, **options)
83
- @color = options[:color]
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)
99
- if async
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)
104
- end
105
- buffer ||= 10_000
106
- start_async_writer(buffer_size: buffer)
107
- else
108
- stop_async_writer
79
+ def setup(level: 'debug', format: :text, async: true, shutdown_timeout: 2, **options)
80
+ with_lifecycle_lock do
81
+ return false if @async_writer && stop_async_writer(timeout: shutdown_timeout) == false
82
+ return false if output(**options) == false
83
+
84
+ log_level(level)
85
+ log_format(format: format, **options)
86
+ @color = options[:color]
87
+ @color = format != :json && (options[:color] || (options[:color].nil? && options[:log_file].nil?))
88
+ @current_settings = {
89
+ level: level,
90
+ format: format.to_sym,
91
+ async: async,
92
+ trace: options.fetch(:trace, true),
93
+ trace_size: options.fetch(:trace_size, 4),
94
+ extended: options.fetch(:extended, true),
95
+ log_file: options[:log_file],
96
+ log_stdout: options[:log_stdout],
97
+ include_pid: options.fetch(:include_pid, false),
98
+ color: @color
99
+ }.freeze
100
+ @configuration_generation = configuration_generation + 1
101
+ Legion::Logging::Redactor.refresh_patterns! if defined?(Legion::Logging::Redactor)
102
+ if async
103
+ start_async_writer(buffer_size: Legion::Logging::Settings.async_buffer_size)
104
+ else
105
+ @async_writer = nil
106
+ @async = false
107
+ log.async_writer = nil
108
+ true
109
+ end
109
110
  end
110
111
  end
111
112
  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.5
4
+ version: 1.5.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -63,12 +63,14 @@ files:
63
63
  - lib/legion/logging/builder.rb
64
64
  - lib/legion/logging/category_registry.rb
65
65
  - lib/legion/logging/event_builder.rb
66
+ - lib/legion/logging/header_builder.rb
66
67
  - lib/legion/logging/helper.rb
67
68
  - lib/legion/logging/hooks.rb
68
69
  - lib/legion/logging/logger.rb
69
70
  - lib/legion/logging/method_tracer.rb
70
71
  - lib/legion/logging/methods.rb
71
72
  - lib/legion/logging/multi_io.rb
73
+ - lib/legion/logging/output_logger.rb
72
74
  - lib/legion/logging/redactor.rb
73
75
  - lib/legion/logging/settings.rb
74
76
  - lib/legion/logging/shipper.rb