semantic_logger 4.1.1 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
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