semantic_logger 3.4.1 → 4.0.0.beta1

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -0
  3. data/Rakefile +4 -8
  4. data/lib/semantic_logger.rb +2 -31
  5. data/lib/semantic_logger/appender.rb +76 -0
  6. data/lib/semantic_logger/appender/bugsnag.rb +3 -8
  7. data/lib/semantic_logger/appender/file.rb +1 -1
  8. data/lib/semantic_logger/appender/honeybadger.rb +1 -1
  9. data/lib/semantic_logger/appender/http.rb +1 -1
  10. data/lib/semantic_logger/appender/mongodb.rb +30 -28
  11. data/lib/semantic_logger/appender/sentry.rb +2 -2
  12. data/lib/semantic_logger/appender/splunk_http.rb +4 -4
  13. data/lib/semantic_logger/appender/syslog.rb +2 -2
  14. data/lib/semantic_logger/appender/tcp.rb +9 -5
  15. data/lib/semantic_logger/appender/udp.rb +1 -0
  16. data/lib/semantic_logger/base.rb +73 -140
  17. data/lib/semantic_logger/core_ext/thread.rb +4 -1
  18. data/lib/semantic_logger/formatters/color.rb +7 -0
  19. data/lib/semantic_logger/formatters/default.rb +7 -0
  20. data/lib/semantic_logger/formatters/syslog.rb +1 -1
  21. data/lib/semantic_logger/log.rb +115 -12
  22. data/lib/semantic_logger/logger.rb +6 -215
  23. data/lib/semantic_logger/metrics/new_relic.rb +1 -1
  24. data/lib/semantic_logger/metrics/statsd.rb +5 -1
  25. data/lib/semantic_logger/metrics/udp.rb +80 -0
  26. data/lib/semantic_logger/processor.rb +235 -0
  27. data/lib/semantic_logger/semantic_logger.rb +36 -65
  28. data/lib/semantic_logger/subscriber.rb +2 -2
  29. data/lib/semantic_logger/version.rb +1 -1
  30. data/test/appender/bugsnag_test.rb +10 -9
  31. data/test/appender/elasticsearch_test.rb +3 -2
  32. data/test/appender/graylog_test.rb +4 -3
  33. data/test/appender/honeybadger_test.rb +2 -2
  34. data/test/appender/http_test.rb +3 -2
  35. data/test/appender/mongodb_test.rb +24 -23
  36. data/test/appender/new_relic_test.rb +15 -8
  37. data/test/appender/sentry_test.rb +2 -2
  38. data/test/appender/splunk_http_test.rb +8 -7
  39. data/test/appender/splunk_test.rb +6 -5
  40. data/test/appender/tcp_test.rb +3 -4
  41. data/test/appender/udp_test.rb +4 -5
  42. data/test/appender/wrapper_test.rb +37 -38
  43. data/test/concerns/compatibility_test.rb +2 -2
  44. data/test/loggable_test.rb +1 -1
  45. data/test/logger_test.rb +149 -528
  46. data/test/measure_test.rb +249 -0
  47. data/test/semantic_logger_test.rb +257 -0
  48. metadata +24 -16
@@ -1,5 +1,8 @@
1
1
  require 'thread'
2
2
  class Thread
3
+ undef :name if method_defined? :name
4
+ undef :name= if method_defined? :name=
5
+
3
6
  # Returns the name of the current thread
4
7
  # Default:
5
8
  # JRuby: The underlying Java thread name
@@ -25,4 +28,4 @@ class Thread
25
28
  @name = name.to_s
26
29
  end
27
30
  end
28
- end
31
+ end
@@ -35,6 +35,13 @@ module SemanticLogger
35
35
  # Tags
36
36
  message << ' ' << log.tags.collect { |tag| "[#{level_color}#{tag}#{colors::CLEAR}]" }.join(' ') if log.tags && (log.tags.size > 0)
37
37
 
38
+ # Named Tags
39
+ if (named_tags = log.named_tags) && !named_tags.empty?
40
+ list = {}
41
+ named_tags.each_pair { |name, value| list << "[#{level_color}#{name}: #{value}#{colors::CLEAR}]" }
42
+ message << ' ' << list.join(' ')
43
+ end
44
+
38
45
  # Duration
39
46
  message << " (#{colors::BOLD}#{log.duration_human}#{colors::CLEAR})" if log.duration
40
47
 
@@ -14,6 +14,13 @@ module SemanticLogger
14
14
  # Tags
15
15
  message << ' ' << log.tags.collect { |tag| "[#{tag}]" }.join(' ') if log.tags && (log.tags.size > 0)
16
16
 
17
+ # Named Tags
18
+ if (named_tags = log.named_tags) && !named_tags.empty?
19
+ list = []
20
+ named_tags.each_pair { |name, value| list << "[#{name}: #{value}]" }
21
+ message << ' ' << list.join(' ')
22
+ end
23
+
17
24
  # Duration
18
25
  message << " (#{log.duration_human})" if log.duration
19
26
 
@@ -88,7 +88,7 @@ module SemanticLogger
88
88
  @options = options.delete(:options) || (::Syslog::LOG_PID | ::Syslog::LOG_CONS)
89
89
  @facility = options.delete(:facility) || ::Syslog::LOG_USER
90
90
  @level_map = DEFAULT_LEVEL_MAP.dup
91
- if level_map = options.delete(:level_map)
91
+ if level_map = options.delete(:level_map)
92
92
  @level_map.update(level_map)
93
93
  end
94
94
  # Time is already part of Syslog packet
@@ -1,7 +1,7 @@
1
1
  module SemanticLogger
2
- # Log Struct
2
+ # Log
3
3
  #
4
- # Structure for holding all log entries
4
+ # Class to hold all log entry information
5
5
  #
6
6
  # level
7
7
  # Log level of the supplied log call
@@ -43,7 +43,103 @@ module SemanticLogger
43
43
  # metric_amount [Numeric]
44
44
  # Used for numeric or counter metrics.
45
45
  # For example, the number of inquiries or, the amount purchased etc.
46
- Log = Struct.new(:level, :thread_name, :name, :message, :payload, :time, :duration, :tags, :level_index, :exception, :metric, :backtrace, :metric_amount) do
46
+ class Log
47
+ attr_accessor :level, :thread_name, :name, :message, :payload, :time, :duration, :tags, :level_index, :exception, :metric, :backtrace, :metric_amount, :named_tags
48
+
49
+ def initialize(name, level, index = nil)
50
+ @level = level
51
+ @thread_name = Thread.current.name
52
+ @name = name
53
+ @time = Time.now
54
+ @tags = SemanticLogger.tags
55
+ @named_tags = SemanticLogger.named_tags
56
+ @level_index = index.nil? ? SemanticLogger.level_to_index(level) : index
57
+ end
58
+
59
+ # Assign named arguments to this log entry, supplying defaults where applicable
60
+ #
61
+ # Returns [true|false] whether this log entry should be logged
62
+ #
63
+ # Example:
64
+ # logger.info(name: 'value')
65
+ def assign(message: nil, payload: nil, min_duration: 0.0, exception: nil, metric: nil, metric_amount: 1, duration: nil, backtrace: nil, log_exception: :full, on_exception_level: nil)
66
+ # Elastic logging: Log when :duration exceeds :min_duration
67
+ # Except if there is an exception when it will always be logged
68
+ if duration
69
+ self.duration = duration
70
+ return false if (duration <= min_duration) && exception.nil?
71
+ end
72
+
73
+ self.message = message
74
+ self.payload = payload
75
+
76
+ if exception
77
+ case log_exception
78
+ when :full
79
+ self.exception = exception
80
+ when :partial
81
+ self.message = "#{message} -- Exception: #{exception.class}: #{exception.message}"
82
+ when nil, :none
83
+ # Log the message without the exception that was raised
84
+ else
85
+ raise(ArgumentError, "Invalid value:#{log_exception.inspect} for argument :log_exception")
86
+ end
87
+ # On exception change the log level
88
+ if on_exception_level
89
+ self.level = on_exception_level
90
+ self.level_index = SemanticLogger.level_to_index(level)
91
+ end
92
+ end
93
+
94
+ if backtrace
95
+ self.backtrace = self.class.cleanse_backtrace(backtrace)
96
+ elsif level_index >= SemanticLogger.backtrace_level_index
97
+ self.backtrace = self.class.cleanse_backtrace
98
+ end
99
+
100
+ if metric
101
+ self.metric = metric
102
+ self.metric_amount = metric_amount
103
+ end
104
+
105
+ self.payload = payload if payload && (payload.size > 0)
106
+ true
107
+ end
108
+
109
+ # Assign positional arguments to this log entry, supplying defaults where applicable
110
+ #
111
+ # Returns [true|false] whether this log entry should be logged
112
+ #
113
+ # Example:
114
+ # logger.info('value', :debug, 0, "hello world")
115
+ def assign_positional(message = nil, payload = nil, exception = nil)
116
+ # Exception being logged?
117
+ # Under JRuby a java exception is not a Ruby Exception
118
+ # Java::JavaLang::ClassCastException.new.is_a?(Exception) => false
119
+ if exception.nil? && payload.nil? && message.respond_to?(:backtrace) && message.respond_to?(:message)
120
+ exception = message
121
+ message = nil
122
+ elsif exception.nil? && payload && payload.respond_to?(:backtrace) && payload.respond_to?(:message)
123
+ exception = payload
124
+ payload = nil
125
+ end
126
+
127
+ # Add result of block as message or payload if not nil
128
+ if block_given? && (result = yield)
129
+ if result.is_a?(String)
130
+ message = message.nil? ? result : "#{message} -- #{result}"
131
+ assign(message: message, payload: payload, exception: exception)
132
+ elsif message.nil? && result.is_a?(Hash)
133
+ assign(result)
134
+ elsif payload && payload.respond_to?(:merge)
135
+ assign(message: message, payload: payload.merge(result), exception: exception)
136
+ else
137
+ assign(message: message, payload: result, exception: exception)
138
+ end
139
+ else
140
+ assign(message: message, payload: payload, exception: exception)
141
+ end
142
+ end
47
143
 
48
144
  MAX_EXCEPTIONS_TO_UNWRAP = 5
49
145
  # Call the block for exception and any nested exception
@@ -196,7 +292,8 @@ module SemanticLogger
196
292
  end
197
293
 
198
294
  # Tags
199
- h[:tags] = tags if tags && (tags.size > 0)
295
+ h[:tags] = tags if tags && !tags.empty?
296
+ h[:named_tags] = named_tags if named_tags && !named_tags.empty?
200
297
 
201
298
  # Duration
202
299
  if duration
@@ -208,13 +305,7 @@ module SemanticLogger
208
305
  h[:message] = cleansed_message if message
209
306
 
210
307
  # Payload
211
- if payload
212
- if payload.is_a?(Hash)
213
- h.merge!(payload)
214
- else
215
- h[:payload] = payload
216
- end
217
- end
308
+ h[:payload] = payload if payload && payload.respond_to?(:empty?) && !payload.empty?
218
309
 
219
310
  # Exceptions
220
311
  if exception
@@ -231,11 +322,23 @@ module SemanticLogger
231
322
  end
232
323
 
233
324
  # Metric
234
- h[:metric] = metric if metric
325
+ h[:metric] = metric if metric
235
326
  h[:metric_amount] = metric_amount if metric_amount
236
327
  h
237
328
  end
238
329
 
330
+ private
331
+
332
+ SELF_PATTERN = File.join('lib', 'semantic_logger')
333
+
334
+ # Extract the backtrace leaving out Semantic Logger
335
+ def self.cleanse_backtrace(stack = caller)
336
+ while (first = stack.first) && first.include?(SELF_PATTERN)
337
+ stack.shift
338
+ end
339
+ stack
340
+ end
341
+
239
342
  end
240
343
 
241
344
  end
@@ -38,95 +38,26 @@ module SemanticLogger
38
38
  # logging, increase the log level, reduce the number of appenders, or
39
39
  # look into speeding up the appenders themselves
40
40
  def self.queue_size
41
- queue.size
41
+ Processor.queue_size
42
42
  end
43
43
 
44
44
  # Flush all queued log entries disk, database, etc.
45
45
  # All queued log messages are written and then each appender is flushed in turn
46
46
  def self.flush
47
- msg = "Flushing appenders with #{queue_size} log messages on the queue"
48
- if queue_size > 1_000
49
- logger.warn msg
50
- elsif queue_size > 100
51
- logger.info msg
52
- elsif queue_size > 0
53
- logger.trace msg
54
- end
55
- process_request(:flush)
47
+ Processor.submit_request(:flush)
56
48
  end
57
49
 
58
50
  # Close all appenders and flush any outstanding messages
59
51
  def self.close
60
- msg = "Closing appenders with #{queue_size} log messages on the queue"
61
- if queue_size > 1_000
62
- logger.warn msg
63
- elsif queue_size > 100
64
- logger.info msg
65
- elsif queue_size > 0
66
- logger.trace msg
67
- end
68
- process_request(:close)
52
+ Processor.submit_request(:close)
69
53
  end
70
54
 
71
- @@lag_check_interval = 5000
72
- @@lag_threshold_s = 30
73
-
74
- # Returns the check_interval which is the number of messages between checks
75
- # to determine if the appender thread is falling behind
76
- def self.lag_check_interval
77
- @@lag_check_interval
78
- end
79
-
80
- # Set the check_interval which is the number of messages between checks
81
- # to determine if the appender thread is falling behind
82
- def self.lag_check_interval=(lag_check_interval)
83
- @@lag_check_interval = lag_check_interval
84
- end
85
-
86
- # Returns the amount of time in seconds
87
- # to determine if the appender thread is falling behind
88
- def self.lag_threshold_s
89
- @@lag_threshold_s
90
- end
91
-
92
- # Allow the internal logger to be overridden from its default to STDERR
55
+ # Allow the internal logger to be overridden from its default of STDERR
93
56
  # Can be replaced with another Ruby logger or Rails logger, but never to
94
57
  # SemanticLogger::Logger itself since it is for reporting problems
95
58
  # while trying to log to the various appenders
96
59
  def self.logger=(logger)
97
- @@logger = logger
98
- end
99
-
100
- # Supply a metrics appender to be called whenever a logging metric is encountered
101
- #
102
- # Parameters
103
- # appender: [Symbol | Object | Proc]
104
- # [Proc] the block to call.
105
- # [Object] the block on which to call #call.
106
- # [Symbol] :new_relic, or :statsd to forward metrics to
107
- #
108
- # block
109
- # The block to be called
110
- #
111
- # Example:
112
- # SemanticLogger.on_metric do |log|
113
- # puts "#{log.metric} was received. Log Struct: #{log.inspect}"
114
- # end
115
- #
116
- # Note:
117
- # * This callback is called in the logging thread.
118
- # * Does not slow down the application.
119
- # * Only context is what is passed in the log struct, the original thread context is not available.
120
- def self.on_metric(options = {}, &block)
121
- # Backward compatibility
122
- options = options.is_a?(Hash) ? options.dup : {appender: options}
123
- appender = block || options.delete(:appender)
124
-
125
- # Convert symbolized metrics appender to an actual object
126
- appender = SemanticLogger.constantize_symbol(appender, 'SemanticLogger::Metrics').new(options) if appender.is_a?(Symbol)
127
-
128
- raise('When supplying a metrics appender, it must support the #call method') unless appender.is_a?(Proc) || appender.respond_to?(:call)
129
- (@@metric_subscribers ||= Concurrent::Array.new) << appender
60
+ Processor.logger = logger
130
61
  end
131
62
 
132
63
  # Place log request on the queue for the Appender thread to write to each
@@ -134,147 +65,7 @@ module SemanticLogger
134
65
  def log(log, message = nil, progname = nil, &block)
135
66
  # Compatibility with ::Logger
136
67
  return add(log, message, progname, &block) unless log.is_a?(SemanticLogger::Log)
137
- self.class.queue << log if @@appender_thread
138
- end
139
-
140
- private
141
-
142
- @@appender_thread = nil
143
- @@queue = Queue.new
144
- @@metric_subscribers = nil
145
-
146
- # Queue to hold messages that need to be logged to the various appenders
147
- def self.queue
148
- @@queue
149
- end
150
-
151
- # Internal logger for SemanticLogger
152
- # For example when an appender is not working etc..
153
- # By default logs to STDERR
154
- def self.logger
155
- @@logger ||= begin
156
- l = SemanticLogger::Appender::File.new(STDERR, :warn)
157
- l.name = name
158
- l
159
- end
160
- end
161
-
162
- # Start the appender thread
163
- def self.start_appender_thread
164
- return false if appender_thread_active?
165
- @@appender_thread = Thread.new { appender_thread }
166
- raise 'Failed to start Appender Thread' unless @@appender_thread
167
- true
168
- end
169
-
170
- # Returns true if the appender_thread is active
171
- def self.appender_thread_active?
172
- @@appender_thread && @@appender_thread.alive?
173
- end
174
-
175
- # Separate appender thread responsible for reading log messages and
176
- # calling the appenders in it's thread
177
- def self.appender_thread
178
- # This thread is designed to never go down unless the main thread terminates
179
- # Before terminating at_exit is used to flush all the appenders
180
- #
181
- # Should any appender fail to log or flush, the exception is logged and
182
- # other appenders will still be called
183
- Thread.current.name = 'SemanticLogger::AppenderThread'
184
- logger.trace "V#{VERSION} Appender thread active"
185
- begin
186
- count = 0
187
- while message = queue.pop
188
- if message.is_a?(Log)
189
- SemanticLogger.appenders.each do |appender|
190
- begin
191
- appender.log(message)
192
- rescue Exception => exc
193
- logger.error "Appender thread: Failed to log to appender: #{appender.inspect}", exc
194
- end
195
- end
196
- call_metric_subscribers(message) if message.metric
197
- count += 1
198
- # Check every few log messages whether this appender thread is falling behind
199
- if count > lag_check_interval
200
- if (diff = Time.now - message.time) > lag_threshold_s
201
- 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"
202
- end
203
- count = 0
204
- end
205
- else
206
- case message[:command]
207
- when :flush
208
- SemanticLogger.appenders.each do |appender|
209
- begin
210
- logger.trace "Appender thread: Flushing appender: #{appender.name}"
211
- appender.flush
212
- rescue Exception => exc
213
- logger.error "Appender thread: Failed to flush appender: #{appender.inspect}", exc
214
- end
215
- end
216
-
217
- message[:reply_queue] << true if message[:reply_queue]
218
- logger.trace 'Appender thread: All appenders flushed'
219
- when :close
220
- SemanticLogger.appenders.each do |appender|
221
- begin
222
- logger.trace "Appender thread: Closing appender: #{appender.name}"
223
- appender.flush
224
- appender.close
225
- SemanticLogger.remove_appender(appender)
226
- rescue Exception => exc
227
- logger.error "Appender thread: Failed to close appender: #{appender.inspect}", exc
228
- end
229
- end
230
-
231
- message[:reply_queue] << true if message[:reply_queue]
232
- logger.trace 'Appender thread: All appenders flushed'
233
- else
234
- logger.warn "Appender thread: Ignoring unknown command: #{message[:command]}"
235
- end
236
- end
237
- end
238
- rescue Exception => exception
239
- # This block may be called after the file handles have been released by Ruby
240
- begin
241
- logger.error 'Appender thread restarting due to exception', exception
242
- rescue Exception
243
- nil
244
- end
245
- retry
246
- ensure
247
- @@appender_thread = nil
248
- # This block may be called after the file handles have been released by Ruby
249
- begin
250
- logger.trace 'Appender thread has stopped'
251
- rescue Exception
252
- nil
253
- end
254
- end
255
- end
256
-
257
- # Call Metric subscribers
258
- def self.call_metric_subscribers(log)
259
- # If no subscribers registered, then return immediately
260
- return unless @@metric_subscribers
261
-
262
- @@metric_subscribers.each do |subscriber|
263
- begin
264
- subscriber.call(log)
265
- rescue Exception => exc
266
- logger.error 'Exception calling metrics subscriber', exc
267
- end
268
- end
269
- end
270
-
271
- # Close all appenders and flush any outstanding messages
272
- def self.process_request(command)
273
- return false unless appender_thread_active?
274
-
275
- reply_queue = Queue.new
276
- queue << {command: command, reply_queue: reply_queue}
277
- reply_queue.pop
68
+ Processor << log
278
69
  end
279
70
 
280
71
  end