semantic_logger 3.4.1 → 4.0.0.beta1

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