semantic_logger 4.1.1 → 4.2.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/lib/semantic_logger.rb +6 -13
  3. data/lib/semantic_logger/ansi_colors.rb +10 -10
  4. data/lib/semantic_logger/appender.rb +42 -26
  5. data/lib/semantic_logger/appender/async.rb +179 -0
  6. data/lib/semantic_logger/appender/async_batch.rb +95 -0
  7. data/lib/semantic_logger/appender/bugsnag.rb +2 -1
  8. data/lib/semantic_logger/appender/elasticsearch.rb +113 -81
  9. data/lib/semantic_logger/appender/elasticsearch_http.rb +1 -3
  10. data/lib/semantic_logger/appender/file.rb +1 -3
  11. data/lib/semantic_logger/appender/graylog.rb +6 -5
  12. data/lib/semantic_logger/appender/honeybadger.rb +0 -2
  13. data/lib/semantic_logger/appender/http.rb +25 -10
  14. data/lib/semantic_logger/appender/kafka.rb +1 -3
  15. data/lib/semantic_logger/appender/mongodb.rb +1 -3
  16. data/lib/semantic_logger/appender/new_relic.rb +7 -3
  17. data/lib/semantic_logger/appender/sentry.rb +6 -7
  18. data/lib/semantic_logger/appender/splunk.rb +1 -2
  19. data/lib/semantic_logger/appender/splunk_http.rb +3 -4
  20. data/lib/semantic_logger/appender/syslog.rb +1 -3
  21. data/lib/semantic_logger/appender/tcp.rb +7 -9
  22. data/lib/semantic_logger/appender/udp.rb +0 -2
  23. data/lib/semantic_logger/appender/wrapper.rb +0 -2
  24. data/lib/semantic_logger/base.rb +76 -19
  25. data/lib/semantic_logger/formatters.rb +37 -0
  26. data/lib/semantic_logger/formatters/base.rb +10 -3
  27. data/lib/semantic_logger/formatters/json.rb +2 -6
  28. data/lib/semantic_logger/formatters/one_line.rb +18 -0
  29. data/lib/semantic_logger/formatters/raw.rb +8 -2
  30. data/lib/semantic_logger/formatters/signalfx.rb +169 -0
  31. data/lib/semantic_logger/log.rb +23 -14
  32. data/lib/semantic_logger/loggable.rb +88 -15
  33. data/lib/semantic_logger/logger.rb +0 -20
  34. data/lib/semantic_logger/metric/new_relic.rb +75 -0
  35. data/lib/semantic_logger/metric/signalfx.rb +123 -0
  36. data/lib/semantic_logger/{metrics → metric}/statsd.rb +20 -8
  37. data/lib/semantic_logger/processor.rb +67 -169
  38. data/lib/semantic_logger/semantic_logger.rb +7 -31
  39. data/lib/semantic_logger/subscriber.rb +32 -36
  40. data/lib/semantic_logger/utils.rb +47 -0
  41. data/lib/semantic_logger/version.rb +1 -1
  42. data/test/appender/async_batch_test.rb +61 -0
  43. data/test/appender/async_test.rb +45 -0
  44. data/test/appender/elasticsearch_http_test.rb +3 -3
  45. data/test/appender/elasticsearch_test.rb +211 -49
  46. data/test/appender/file_test.rb +9 -8
  47. data/test/appender/mongodb_test.rb +3 -3
  48. data/test/appender/newrelic_rpm.rb +6 -0
  49. data/test/appender/sentry_test.rb +3 -1
  50. data/test/appender/wrapper_test.rb +29 -0
  51. data/test/concerns/compatibility_test.rb +64 -60
  52. data/test/debug_as_trace_logger_test.rb +62 -77
  53. data/test/formatters/one_line_test.rb +61 -0
  54. data/test/formatters/signalfx_test.rb +200 -0
  55. data/test/formatters_test.rb +36 -0
  56. data/test/in_memory_appender.rb +9 -0
  57. data/test/in_memory_appender_helper.rb +43 -0
  58. data/test/in_memory_batch_appender.rb +9 -0
  59. data/test/in_memory_metrics_appender.rb +14 -0
  60. data/test/loggable_test.rb +15 -30
  61. data/test/logger_test.rb +181 -135
  62. data/test/measure_test.rb +212 -113
  63. data/test/metric/new_relic_test.rb +36 -0
  64. data/test/metric/signalfx_test.rb +78 -0
  65. data/test/semantic_logger_test.rb +58 -65
  66. data/test/test_helper.rb +19 -2
  67. metadata +33 -7
  68. data/lib/semantic_logger/metrics/new_relic.rb +0 -30
  69. data/lib/semantic_logger/metrics/udp.rb +0 -80
  70. data/test/mock_logger.rb +0 -29
@@ -1,17 +1,24 @@
1
1
  module SemanticLogger
2
2
  # Thread that submits and processes log requests
3
3
  class Processor
4
+ # Returns [Appender::Async => SemanticLogger::Processor] the global instance of this processor
5
+ # wrapped in the Async proxy so that all logging is asynchronous in a thread of its own.
6
+ #
7
+ # More than one instance can be created if needed.
8
+ def self.instance
9
+ @processor
10
+ end
11
+
4
12
  # Start the appender thread
5
13
  def self.start
6
- return false if active?
7
- @thread = Thread.new { process_requests }
8
- raise 'Failed to start Appender Thread' unless @thread
14
+ return false if instance.active?
15
+ instance.thread
9
16
  true
10
17
  end
11
18
 
12
19
  # Returns true if the appender_thread is active
13
20
  def self.active?
14
- @thread && @thread.alive?
21
+ instance.alive?
15
22
  end
16
23
 
17
24
  # Returns [Integer] the number of log entries waiting to be written to the appenders.
@@ -21,105 +28,40 @@ module SemanticLogger
21
28
  # logging, increase the log level, reduce the number of appenders, or
22
29
  # look into speeding up the appenders themselves
23
30
  def self.queue_size
24
- queue.size
25
- end
26
-
27
- # Flush all queued log entries disk, database, etc.
28
- # All queued log messages are written and then each appender is flushed in turn.
29
- def self.flush
30
- submit_request(:flush)
31
- end
32
-
33
- # Close all appenders and flush any outstanding messages.
34
- def self.close
35
- submit_request(:close)
31
+ instance.queue.size
36
32
  end
37
33
 
38
34
  # Add log request to the queue for processing.
35
+ # Log subscribers are called inline before handing off to the queue.
39
36
  def self.<<(log)
40
- return unless active?
41
-
42
- call_log_subscribers(log)
43
- queue << log
44
- end
45
-
46
- # Submit command and wait for reply
47
- def self.submit_request(command)
48
- return false unless active?
49
-
50
- msg = "Too many queued log messages: #{queue_size}, running command: #{command}"
51
- if queue_size > 1_000
52
- logger.warn msg
53
- elsif queue_size > 100
54
- logger.info msg
55
- elsif queue_size > 0
56
- logger.trace msg
57
- end
58
-
59
- reply_queue = Queue.new
60
- queue << {command: command, reply_queue: reply_queue}
61
- reply_queue.pop
62
- end
63
-
64
- # Allow the internal logger to be overridden from its default of STDERR
65
- # Can be replaced with another Ruby logger or Rails logger, but never to
66
- # SemanticLogger::Logger itself since it is for reporting problems
67
- # while trying to log to the various appenders
68
- def self.logger=(logger)
69
- @logger = logger
37
+ instance.appender.send(:call_log_subscribers, log)
38
+ instance.log(log)
70
39
  end
71
40
 
72
41
  # Returns the check_interval which is the number of messages between checks
73
- # to determine if the appender thread is falling behind
42
+ # to determine if the appender thread is falling behind.
74
43
  def self.lag_check_interval
75
- @lag_check_interval
44
+ instance.lag_check_interval
76
45
  end
77
46
 
78
47
  # Set the check_interval which is the number of messages between checks
79
- # to determine if the appender thread is falling behind
48
+ # to determine if the appender thread is falling behind.
80
49
  def self.lag_check_interval=(lag_check_interval)
81
- @lag_check_interval = lag_check_interval
50
+ instance.lag_check_interval = lag_check_interval
82
51
  end
83
52
 
84
53
  # Returns the amount of time in seconds
85
- # to determine if the appender thread is falling behind
54
+ # to determine if the appender thread is falling behind.
86
55
  def self.lag_threshold_s
87
- @lag_threshold_s
56
+ instance.lag_threshold_s
88
57
  end
89
58
 
90
- def self.on_metric(options = {}, &block)
91
- # Backward compatibility
92
- options = options.is_a?(Hash) ? options.dup : {appender: options}
93
- subscriber = block || options.delete(:appender)
94
-
95
- # Convert symbolized metrics appender to an actual object
96
- subscriber = SemanticLogger::Appender.constantize_symbol(subscriber, 'SemanticLogger::Metrics').new(options) if subscriber.is_a?(Symbol)
97
-
98
- raise('When supplying a metrics subscriber, it must support the #call method') unless subscriber.is_a?(Proc) || subscriber.respond_to?(:call)
99
- subscribers = (@metric_subscribers ||= Concurrent::Array.new)
100
- subscribers << subscriber unless subscribers.include?(subscriber)
101
- end
102
-
103
- def self.on_log(object = nil, &block)
104
- subscriber = block || object
105
-
106
- raise('When supplying an on_log subscriber, it must support the #call method') unless subscriber.is_a?(Proc) || subscriber.respond_to?(:call)
107
- subscribers = (@log_subscribers ||= Concurrent::Array.new)
108
- subscribers << subscriber unless subscribers.include?(subscriber)
109
- end
110
-
111
- private
112
-
113
- @thread = nil
114
- @queue = Queue.new
115
- @lag_check_interval = 5000
116
- @lag_threshold_s = 30
117
- @metric_subscribers = nil
118
- @log_subscribers = nil
119
-
120
- # Queue to hold messages that need to be logged to the various appenders
121
- def self.queue
122
- @queue
59
+ # Allow the internal logger to be overridden from its default of STDERR
60
+ # Can be replaced with another Ruby logger or Rails logger, but never to
61
+ # SemanticLogger::Logger itself since it is for reporting problems
62
+ # while trying to log to the various appenders
63
+ def self.logger=(logger)
64
+ @logger = logger
123
65
  end
124
66
 
125
67
  # Internal logger for SemanticLogger
@@ -133,103 +75,33 @@ module SemanticLogger
133
75
  end
134
76
  end
135
77
 
136
- # Separate appender thread responsible for reading log messages and
137
- # calling the appenders in it's thread
138
- def self.process_requests
139
- # This thread is designed to never go down unless the main thread terminates
140
- # Before terminating at_exit is used to flush all the appenders
141
- #
142
- # Should any appender fail to log or flush, the exception is logged and
143
- # other appenders will still be called
144
- Thread.current.name = 'SemanticLogger::Processor'
145
- logger.trace "V#{VERSION} Processor thread active"
146
- begin
147
- count = 0
148
- while message = queue.pop
149
- if message.is_a?(Log)
150
- call_appenders(message)
151
- call_metric_subscribers(message) if message.metric
152
- count += 1
153
- # Check every few log messages whether this appender thread is falling behind
154
- if count > lag_check_interval
155
- if (diff = Time.now - message.time) > lag_threshold_s
156
- logger.warn "Appender thread has fallen behind by #{diff} seconds with #{queue_size} messages queued up. Consider reducing the log level or changing the appenders"
157
- end
158
- count = 0
159
- end
160
- else
161
- case message[:command]
162
- when :flush
163
- flush_appenders
164
- message[:reply_queue] << true if message[:reply_queue]
165
- when :close
166
- close_appenders
167
- message[:reply_queue] << true if message[:reply_queue]
168
- break
169
- else
170
- logger.warn "Appender thread: Ignoring unknown command: #{message[:command]}"
171
- end
172
- end
173
- end
174
- rescue Exception => exception
175
- # This block may be called after the file handles have been released by Ruby
176
- begin
177
- logger.error 'Appender thread restarting due to exception', exception
178
- rescue Exception
179
- nil
180
- end
181
- retry
182
- ensure
183
- @thread = nil
184
- # This block may be called after the file handles have been released by Ruby
185
- begin
186
- logger.trace 'Appender thread has stopped'
187
- rescue Exception
188
- nil
189
- end
190
- end
191
- end
192
-
193
- # Call Metric subscribers
194
- def self.call_metric_subscribers(log)
195
- # If no subscribers registered, then return immediately
196
- return unless @metric_subscribers
78
+ attr_accessor :logger, :log_subscribers
197
79
 
198
- @metric_subscribers.each do |subscriber|
199
- begin
200
- subscriber.call(log)
201
- rescue Exception => exc
202
- logger.error 'Exception calling metrics subscriber', exc
203
- end
204
- end
80
+ def initialize
81
+ @log_subscribers = nil
82
+ @logger = self.class.logger.dup
83
+ @logger.name = self.class.name
205
84
  end
206
85
 
207
- # Call on_log subscribers
208
- def self.call_log_subscribers(log)
209
- # If no subscribers registered, then return immediately
210
- return unless @log_subscribers
86
+ def on_log(object = nil, &block)
87
+ subscriber = block || object
211
88
 
212
- @log_subscribers.each do |subscriber|
213
- begin
214
- subscriber.call(log)
215
- rescue Exception => exc
216
- logger.error 'Exception calling :on_log subscriber', exc
217
- end
218
- end
89
+ raise('When supplying an on_log subscriber, it must support the #call method') unless subscriber.is_a?(Proc) || subscriber.respond_to?(:call)
90
+ subscribers = (@log_subscribers ||= Concurrent::Array.new)
91
+ subscribers << subscriber unless subscribers.include?(subscriber)
219
92
  end
220
93
 
221
- # Call Appenders
222
- def self.call_appenders(log)
94
+ def log(log)
223
95
  SemanticLogger.appenders.each do |appender|
224
96
  begin
225
- appender.log(log)
97
+ appender.log(log) if appender.should_log?(log)
226
98
  rescue Exception => exc
227
99
  logger.error "Appender thread: Failed to log to appender: #{appender.inspect}", exc
228
100
  end
229
101
  end
230
102
  end
231
103
 
232
- def self.flush_appenders
104
+ def flush
233
105
  SemanticLogger.appenders.each do |appender|
234
106
  begin
235
107
  logger.trace "Appender thread: Flushing appender: #{appender.name}"
@@ -241,7 +113,7 @@ module SemanticLogger
241
113
  logger.trace 'Appender thread: All appenders flushed'
242
114
  end
243
115
 
244
- def self.close_appenders
116
+ def close
245
117
  SemanticLogger.appenders.each do |appender|
246
118
  begin
247
119
  logger.trace "Appender thread: Closing appender: #{appender.name}"
@@ -255,5 +127,31 @@ module SemanticLogger
255
127
  logger.trace 'Appender thread: All appenders closed and removed from appender list'
256
128
  end
257
129
 
130
+ private
131
+
132
+ def self.create_instance
133
+ SemanticLogger::Appender::Async.new(
134
+ name: 'SemanticLogger::Processor',
135
+ appender: new,
136
+ max_queue_size: -1
137
+ )
138
+ end
139
+
140
+ @processor = create_instance
141
+
142
+ # Call on_log subscribers
143
+ def call_log_subscribers(log)
144
+ # If no subscribers registered, then return immediately
145
+ return unless log_subscribers
146
+
147
+ log_subscribers.each do |subscriber|
148
+ begin
149
+ subscriber.call(log)
150
+ rescue Exception => exc
151
+ logger.error 'Exception calling :on_log subscriber', exc
152
+ end
153
+ end
154
+ end
155
+
258
156
  end
259
157
  end
@@ -12,7 +12,7 @@ module SemanticLogger
12
12
 
13
13
  # Sets the global default log level
14
14
  def self.default_level=(level)
15
- @default_level = level
15
+ @default_level = level
16
16
  # For performance reasons pre-calculate the level index
17
17
  @default_level_index = level_to_index(level)
18
18
  end
@@ -33,7 +33,7 @@ module SemanticLogger
33
33
  # Capturing backtraces is very expensive and should not be done all
34
34
  # the time. It is recommended to run it at :error level in production.
35
35
  def self.backtrace_level=(level)
36
- @backtrace_level = level
36
+ @backtrace_level = level
37
37
  # For performance reasons pre-calculate the level index
38
38
  @backtrace_level_index = level.nil? ? 65535 : level_to_index(level)
39
39
  end
@@ -153,7 +153,7 @@ module SemanticLogger
153
153
  # logger.debug("Login time", user: 'Joe', duration: 100, ip_address: '127.0.0.1')
154
154
  def self.add_appender(options, deprecated_level = nil, &block)
155
155
  options = options.is_a?(Hash) ? options.dup : convert_old_appender_args(options, deprecated_level)
156
- appender = SemanticLogger::Appender.create(options, &block)
156
+ appender = SemanticLogger::Appender.factory(options, &block)
157
157
  @appenders << appender
158
158
 
159
159
  # Start appender thread if it is not already running
@@ -178,12 +178,12 @@ module SemanticLogger
178
178
  # Flush all queued log entries disk, database, etc.
179
179
  # All queued log messages are written and then each appender is flushed in turn.
180
180
  def self.flush
181
- SemanticLogger::Processor.flush
181
+ SemanticLogger::Processor.instance.flush
182
182
  end
183
183
 
184
184
  # Close all appenders and flush any outstanding messages.
185
185
  def self.close
186
- SemanticLogger::Processor.close
186
+ SemanticLogger::Processor.instance.close
187
187
  end
188
188
 
189
189
  # After forking an active process call SemanticLogger.reopen to re-open
@@ -198,30 +198,6 @@ module SemanticLogger
198
198
  SemanticLogger::Processor.start
199
199
  end
200
200
 
201
- # Supply a metrics appender to be called whenever a logging metric is encountered
202
- #
203
- # Parameters
204
- # appender: [Symbol | Object | Proc]
205
- # [Proc] the block to call.
206
- # [Object] the block on which to call #call.
207
- # [Symbol] :new_relic, or :statsd to forward metrics to
208
- #
209
- # block
210
- # The block to be called
211
- #
212
- # Example:
213
- # SemanticLogger.on_metric do |log|
214
- # puts "#{log.metric} was received. Log: #{log.inspect}"
215
- # end
216
- #
217
- # Note:
218
- # * This callback is called in the separate logging thread.
219
- # * Does not slow down the application.
220
- # * Only context is what is passed in the log struct, the original thread context is not available.
221
- def self.on_metric(options = {}, &block)
222
- SemanticLogger::Processor.on_metric(options, &block)
223
- end
224
-
225
201
  # Supply a callback to be called whenever a log entry is created.
226
202
  # Useful for capturing appender specific context information.
227
203
  #
@@ -247,7 +223,7 @@ module SemanticLogger
247
223
  # * This callback is called within the thread of the application making the logging call.
248
224
  # * If these callbacks are slow they will slow down the application.
249
225
  def self.on_log(object = nil, &block)
250
- Processor.on_log(object, &block)
226
+ Processor.instance.on_log(object, &block)
251
227
  end
252
228
 
253
229
  # Add signal handlers for Semantic Logger
@@ -513,7 +489,7 @@ module SemanticLogger
513
489
  else
514
490
  options[:logger] = appender
515
491
  end
516
- warn "[DEPRECATED] SemanticLogger.add_appender parameters have changed. Please use: #{options.inspect}" if $VERBOSE
492
+ warn "[DEPRECATED] SemanticLogger.add_appender parameters have changed. Please use: #{options.inspect}"
517
493
  options
518
494
  end
519
495
 
@@ -1,11 +1,11 @@
1
1
  # Abstract Subscriber
2
2
  #
3
- # Abstract base class for appender and metrics subscribers.
3
+ # Abstract base class for all appenders.
4
4
  module SemanticLogger
5
5
  class Subscriber < SemanticLogger::Base
6
- # Every logger has its own formatter
7
- attr_accessor :formatter
8
- attr_writer :application, :host
6
+ # Every appender has its own formatter
7
+ attr_reader :formatter
8
+ attr_writer :application, :host, :logger
9
9
 
10
10
  # Returns the current log level if set, otherwise it logs everything it receives.
11
11
  def level
@@ -37,6 +37,31 @@ module SemanticLogger
37
37
  @host || SemanticLogger.host
38
38
  end
39
39
 
40
+ # Give each appender its own logger for logging.
41
+ # For example trace messages sent to services or errors when something fails.
42
+ def logger
43
+ @logger ||= begin
44
+ logger = SemanticLogger::Processor.logger.clone
45
+ logger.name = self.class.name
46
+ logger
47
+ end
48
+ end
49
+
50
+ # Set the formatter from Symbol|Hash|Block
51
+ def formatter=(formatter)
52
+ @formatter =
53
+ if formatter.nil?
54
+ respond_to?(:call) ? self : default_formatter
55
+ else
56
+ Formatters.factory(formatter)
57
+ end
58
+ end
59
+
60
+ # Whether this log entry meets the criteria to be logged by this appender.
61
+ def should_log?(log)
62
+ super(log) && !log.metric_only?
63
+ end
64
+
40
65
  private
41
66
 
42
67
  # Initializer for Abstract Class SemanticLogger::Subscriber
@@ -44,7 +69,6 @@ module SemanticLogger
44
69
  # Parameters
45
70
  # level: [:trace | :debug | :info | :warn | :error | :fatal]
46
71
  # Override the log level for this subscriber.
47
- # Default: :error
48
72
  #
49
73
  # formatter: [Object|Proc]
50
74
  # An instance of a class that implements #call, or a Proc to be used to format
@@ -65,9 +89,9 @@ module SemanticLogger
65
89
  # Name of this host to appear in log messages.
66
90
  # Default: SemanticLogger.host
67
91
  def initialize(level: nil, formatter: nil, filter: nil, application: nil, host: nil, &block)
68
- @formatter = extract_formatter(formatter, &block)
69
- @application = application
70
- @host = host
92
+ self.formatter = block || formatter
93
+ @application = application
94
+ @host = host
71
95
 
72
96
  # Subscribers don't take a class name, so use this class name if a subscriber
73
97
  # is logged to directly.
@@ -81,33 +105,5 @@ module SemanticLogger
81
105
  @level_index || 0
82
106
  end
83
107
 
84
- # Return formatter that responds to call.
85
- #
86
- # Supports formatter supplied as:
87
- # - Symbol
88
- # - Hash ( Symbol => { options })
89
- # - Instance of any of SemanticLogger::Formatters
90
- # - Proc
91
- # - Any object that responds to :call
92
- # - If none of the above apply, then the supplied block is returned as the formatter.
93
- # - Otherwise an instance of the default formatter is returned.
94
- def extract_formatter(formatter, &block)
95
- case
96
- when formatter.is_a?(Symbol)
97
- SemanticLogger::Appender.constantize_symbol(formatter, 'SemanticLogger::Formatters').new
98
- when formatter.is_a?(Hash) && formatter.size > 0
99
- fmt, options = formatter.first
100
- SemanticLogger::Appender.constantize_symbol(fmt.to_sym, 'SemanticLogger::Formatters').new(options)
101
- when formatter.respond_to?(:call)
102
- formatter
103
- when block
104
- block
105
- when respond_to?(:call)
106
- self
107
- else
108
- default_formatter
109
- end
110
- end
111
-
112
108
  end
113
109
  end