semantic_logger 3.0.1 → 3.1.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/semantic_logger.rb +29 -13
  4. data/lib/semantic_logger/ansi_colors.rb +27 -0
  5. data/lib/semantic_logger/appender/base.rb +54 -128
  6. data/lib/semantic_logger/appender/bugsnag.rb +29 -19
  7. data/lib/semantic_logger/appender/elasticsearch.rb +9 -9
  8. data/lib/semantic_logger/appender/file.rb +40 -18
  9. data/lib/semantic_logger/appender/graylog.rb +30 -26
  10. data/lib/semantic_logger/appender/http.rb +14 -19
  11. data/lib/semantic_logger/appender/mongodb.rb +20 -20
  12. data/lib/semantic_logger/appender/new_relic.rb +15 -15
  13. data/lib/semantic_logger/appender/splunk.rb +1 -1
  14. data/lib/semantic_logger/appender/splunk_http.rb +28 -25
  15. data/lib/semantic_logger/appender/syslog.rb +41 -42
  16. data/lib/semantic_logger/appender/wrapper.rb +19 -17
  17. data/lib/semantic_logger/base.rb +57 -32
  18. data/lib/semantic_logger/concerns/compatibility.rb +51 -0
  19. data/lib/semantic_logger/debug_as_trace_logger.rb +6 -2
  20. data/lib/semantic_logger/formatters/color.rb +66 -0
  21. data/lib/semantic_logger/formatters/default.rb +39 -0
  22. data/lib/semantic_logger/formatters/json.rb +16 -0
  23. data/lib/semantic_logger/jruby/garbage_collection_logger.rb +1 -1
  24. data/lib/semantic_logger/log.rb +13 -8
  25. data/lib/semantic_logger/loggable.rb +2 -2
  26. data/lib/semantic_logger/logger.rb +18 -13
  27. data/lib/semantic_logger/metrics/new_relic.rb +18 -0
  28. data/lib/semantic_logger/metrics/statsd.rb +48 -0
  29. data/lib/semantic_logger/semantic_logger.rb +122 -55
  30. data/lib/semantic_logger/version.rb +1 -1
  31. data/test/appender/bugsnag_test.rb +12 -3
  32. data/test/appender/mongodb_test.rb +6 -5
  33. data/test/appender/new_relic_test.rb +1 -1
  34. data/test/appender/splunk_http_test.rb +1 -0
  35. data/test/concerns/compatibility_test.rb +106 -0
  36. data/test/debug_as_trace_logger_test.rb +1 -1
  37. data/test/loggable_test.rb +1 -1
  38. data/test/logger_test.rb +183 -24
  39. metadata +12 -3
@@ -5,27 +5,19 @@ require 'socket'
5
5
  # Send log messages to local syslog, or remote syslog servers over TCP or UDP.
6
6
  #
7
7
  # Example: Log to a local Syslog daemon
8
- # SemanticLogger.add_appender(SemanticLogger::Appender::Syslog.new)
8
+ # SemanticLogger.add_appender(appender: :syslog)
9
9
  #
10
10
  # Example: Log to a remote Syslog server using TCP:
11
- # appender = SemanticLogger::Appender::Syslog.new(
12
- # url: 'tcp://myloghost:514'
11
+ # SemanticLogger.add_appender(
12
+ # appender: :syslog,
13
+ # url: 'tcp://myloghost:514'
13
14
  # )
14
15
  #
15
- # # Optional: Add filter to exclude health_check, or other log entries
16
- # appender.filter = Proc.new { |log| log.message !~ /(health_check|Not logged in)/ }
17
- #
18
- # SemanticLogger.add_appender(appender)
19
- #
20
16
  # Example: Log to a remote Syslog server using UDP:
21
- # appender = SemanticLogger::Appender::Syslog.new(
22
- # url: 'udp://myloghost:514'
17
+ # SemanticLogger.add_appender(
18
+ # appender: :syslog,
19
+ # url: 'udp://myloghost:514'
23
20
  # )
24
- #
25
- # # Optional: Add filter to exclude health_check, or other log entries
26
- # appender.filter = Proc.new { |log| log.message !~ /(health_check|Not logged in)/ }
27
- #
28
- # SemanticLogger.add_appender(appender)
29
21
  module SemanticLogger
30
22
  module Appender
31
23
  class Syslog < SemanticLogger::Appender::Base
@@ -81,6 +73,11 @@ module SemanticLogger
81
73
  # Override the log level for this appender.
82
74
  # Default: SemanticLogger.default_level
83
75
  #
76
+ # formatter: [Object|Proc]
77
+ # An instance of a class that implements #call, or a Proc to be used to format
78
+ # the output from this appender
79
+ # Default: Use the built-in formatter (See: #call)
80
+ #
84
81
  # filter: [Regexp|Proc]
85
82
  # RegExp: Only include log messages where the class name matches the supplied.
86
83
  # regular expression. All other messages will be ignored.
@@ -143,10 +140,15 @@ module SemanticLogger
143
140
  # debug: ::Syslog::LOG_INFO,
144
141
  # trace: ::Syslog::LOG_DEBUG
145
142
  # }
143
+ #
144
+ # format: [Symbol]
145
+ # Format for the Syslog message
146
+ # :syslog uses the default syslog format
147
+ # :json uses the CEE JSON Syslog format
148
+ # Example: "@cee: #{JSON.dump(data)}"
149
+ # Default: :syslog
146
150
  def initialize(options = {}, &block)
147
151
  options = options.dup
148
- level = options.delete(:level)
149
- filter = options.delete(:filter)
150
152
  @application = options.delete(:application) || options.delete(:ident) || 'ruby'
151
153
  @options = options.delete(:options) || (::Syslog::LOG_PID | ::Syslog::LOG_CONS)
152
154
  @facility = options.delete(:facility) || ::Syslog::LOG_USER
@@ -161,7 +163,6 @@ module SemanticLogger
161
163
  @tcp_client_options = options.delete(:tcp_client)
162
164
 
163
165
  raise "Unknown protocol #{@protocol}!" unless [:syslog, :tcp, :udp].include?(@protocol)
164
- raise(ArgumentError, "Unknown options: #{options.inspect}") if options.size > 0
165
166
 
166
167
  @level_map = DEFAULT_LEVEL_MAP.dup
167
168
  @level_map.update(level_map) if level_map
@@ -188,7 +189,7 @@ module SemanticLogger
188
189
 
189
190
  reopen
190
191
 
191
- super(level, filter, &block)
192
+ super(options, &block)
192
193
  end
193
194
 
194
195
  # After forking an active process call #reopen to re-open
@@ -235,35 +236,33 @@ module SemanticLogger
235
236
 
236
237
  # Custom log formatter for syslog.
237
238
  # Only difference is the removal of the timestamp string since it is in the syslog packet.
238
- def default_formatter
239
- Proc.new do |log|
240
- # Header with date, time, log level and process info
241
- entry = "#{log.level_to_s} [#{log.process_info}]"
239
+ def call(log, logger)
240
+ # Header with date, time, log level and process info
241
+ message = "#{log.level_to_s} [#{log.process_info}]"
242
242
 
243
- # Tags
244
- entry << ' ' << log.tags.collect { |tag| "[#{tag}]" }.join(' ') if log.tags && (log.tags.size > 0)
243
+ # Tags
244
+ message << ' ' << log.tags.collect { |tag| "[#{tag}]" }.join(' ') if log.tags && (log.tags.size > 0)
245
245
 
246
- # Duration
247
- entry << " (#{log.duration_human})" if log.duration
246
+ # Duration
247
+ message << " (#{log.duration_human})" if log.duration
248
248
 
249
- # Class / app name
250
- entry << " #{log.name}"
249
+ # Class / app name
250
+ message << " #{log.name}"
251
251
 
252
- # Log message
253
- entry << " -- #{log.message}" if log.message
252
+ # Log message
253
+ message << " -- #{log.message}" if log.message
254
254
 
255
- # Payload
256
- if payload = log.payload_to_s(false)
257
- entry << ' -- ' << payload
258
- end
255
+ # Payload
256
+ if payload = log.payload_to_s
257
+ message << ' -- ' << payload
258
+ end
259
259
 
260
- # Exceptions
261
- if log.exception
262
- entry << " -- Exception: #{log.exception.class}: #{log.exception.message}\n"
263
- entry << log.backtrace_to_s
264
- end
265
- entry
260
+ # Exceptions
261
+ if log.exception
262
+ message << " -- Exception: #{log.exception.class}: #{log.exception.message}\n"
263
+ message << log.backtrace_to_s
266
264
  end
265
+ message
267
266
  end
268
267
 
269
268
  # Format the syslog packet so it can be sent over TCP or UDP
@@ -273,7 +272,7 @@ module SemanticLogger
273
272
  packet.facility = @facility
274
273
  packet.severity = @level_map[log.level]
275
274
  packet.tag = @application
276
- packet.content = default_formatter.call(log, self)
275
+ packet.content = formatter.call(log, self)
277
276
  packet.time = log.time
278
277
  packet.to_s
279
278
  end
@@ -17,6 +17,11 @@ module SemanticLogger
17
17
  # Override the log level for this appender.
18
18
  # Default: SemanticLogger.default_level
19
19
  #
20
+ # formatter: [Object|Proc]
21
+ # An instance of a class that implements #call, or a Proc to be used to format
22
+ # the output from this appender
23
+ # Default: Use the built-in formatter (See: #call)
24
+ #
20
25
  # filter: [Regexp|Proc]
21
26
  # RegExp: Only include log messages where the class name matches the supplied.
22
27
  # regular expression. All other messages will be ignored.
@@ -28,27 +33,25 @@ module SemanticLogger
28
33
  # require 'semantic_logger'
29
34
  #
30
35
  # ruby_logger = Logger.new(STDOUT)
31
- # SemanticLogger.add_appender(ruby_logger)
36
+ # SemanticLogger.add_appender(logger: ruby_logger)
32
37
  #
33
38
  # logger = SemanticLogger['test']
34
39
  # logger.info('Hello World', some: :payload)
35
40
  #
36
- # Enhance the Rails Logger
37
- # # Add the Rails logger to the list of appenders
38
- # SemanticLogger.add_appender(Rails.logger)
39
- # Rails.logger = SemanticLogger['Rails']
40
- #
41
- # # Make ActiveRecord logging include its class name in every log entry
42
- # ActiveRecord::Base.logger = SemanticLogger['ActiveRecord']
43
- #
44
41
  # Install the `rails_semantic_logger` gem to replace the Rails logger with Semantic Logger.
45
- def initialize(logger, level = nil, filter = nil, &block)
46
- raise 'logger cannot be null when initializing the SemanticLogging::Appender::Wrapper' unless logger
47
- @logger = logger
42
+ def initialize(options, &block)
43
+ # Backward compatibility
44
+ options = {logger: options} unless options.is_a?(Hash)
45
+ options = options.dup
46
+ @logger = options.delete(:logger)
47
+
48
+ # Check if the custom appender responds to all the log levels. For example Ruby ::Logger
49
+ if does_not_implement = LEVELS[1..-1].find { |i| !@logger.respond_to?(i) }
50
+ raise(ArgumentError, "Supplied logger does not implement:#{does_not_implement}. It must implement all of #{LEVELS[1..-1].inspect}")
51
+ end
48
52
 
49
- # Set the formatter to the supplied block
50
- @formatter = block || self.default_formatter
51
- super(level, filter, &block)
53
+ raise 'SemanticLogging::Appender::Wrapper missing mandatory parameter :logger' unless @logger
54
+ super(options, &block)
52
55
  end
53
56
 
54
57
  # Pass log calls to the underlying Rails, log4j or Ruby logger
@@ -58,8 +61,7 @@ module SemanticLogger
58
61
  # Ensure minimum log level is met, and check filter
59
62
  return false if (level_index > (log.level_index || 0)) || !include_message?(log)
60
63
 
61
- # Underlying wrapper logger implements log level, so don't check here
62
- @logger.send(log.level == :trace ? :debug : log.level, @formatter.call(log, self))
64
+ @logger.send(log.level == :trace ? :debug : log.level, formatter.call(log, self))
63
65
  true
64
66
  end
65
67
 
@@ -21,7 +21,7 @@ module SemanticLogger
21
21
  # nil if this logger instance should use the global default level
22
22
  def level=(level)
23
23
  @level_index = SemanticLogger.level_to_index(level)
24
- @level = level
24
+ @level = SemanticLogger.send(:index_to_level, @level_index)
25
25
  end
26
26
 
27
27
  # Returns the current log level if set, otherwise it returns the global
@@ -59,10 +59,10 @@ module SemanticLogger
59
59
  # SemanticLogger.default_level = :info
60
60
  #
61
61
  # # Log to screen
62
- # SemanticLogger.add_appender(STDOUT)
62
+ # SemanticLogger.add_appender(io: STDOUT, formatter: :color)
63
63
  #
64
64
  # # And log to a file at the same time
65
- # SemanticLogger.add_appender('application.log')
65
+ # SemanticLogger.add_appender(file: 'application.log', formatter: :color)
66
66
  #
67
67
  # logger = SemanticLogger['MyApplication']
68
68
  # logger.debug("Only display this if log level is set to Debug or lower")
@@ -88,9 +88,17 @@ module SemanticLogger
88
88
  level_index <= #{index}
89
89
  end
90
90
 
91
+ def measure_#{level}(message, params = {}, &block)
92
+ if level_index <= #{index}
93
+ measure_internal(:#{level}, #{index}, message, params, &block)
94
+ else
95
+ block.call(params) if block
96
+ end
97
+ end
98
+
91
99
  def benchmark_#{level}(message, params = {}, &block)
92
100
  if level_index <= #{index}
93
- benchmark_internal(:#{level}, #{index}, message, params, &block)
101
+ measure_internal(:#{level}, #{index}, message, params, &block)
94
102
  else
95
103
  block.call(params) if block
96
104
  end
@@ -98,16 +106,18 @@ module SemanticLogger
98
106
  EOT
99
107
  end
100
108
 
101
- # Dynamically supply the log level with every benchmark call
102
- def benchmark(level, message, params = {}, &block)
109
+ # Dynamically supply the log level with every measurement call
110
+ def measure(level, message, params = {}, &block)
103
111
  index = SemanticLogger.level_to_index(level)
104
112
  if level_index <= index
105
- benchmark_internal(level, index, message, params, &block)
113
+ measure_internal(level, index, message, params, &block)
106
114
  else
107
115
  block.call(params) if block
108
116
  end
109
117
  end
110
118
 
119
+ alias_method :benchmark, :measure
120
+
111
121
  # If the tag being supplied is definitely a string then this fast
112
122
  # tag api can be used for short lived tags
113
123
  def fast_tag(tag)
@@ -184,12 +194,6 @@ module SemanticLogger
184
194
  Thread.current[:semantic_logger_payload]
185
195
  end
186
196
 
187
- # Semantic Logging does not support :unknown level since these
188
- # are not understood by the majority of the logging providers
189
- # Map it to :error
190
- alias :unknown :error
191
- alias :unknown? :error?
192
-
193
197
  # Silence noisy log levels by changing the default_level within the block
194
198
  #
195
199
  # This setting is thread-safe and only applies to the current thread
@@ -246,6 +250,13 @@ module SemanticLogger
246
250
  SemanticLogger.default_level
247
251
  end
248
252
 
253
+ protected
254
+
255
+ # Write log data to underlying data storage
256
+ def log(log_)
257
+ raise NotImplementedError.new('Logging Appender must implement #log(log)')
258
+ end
259
+
249
260
  private
250
261
 
251
262
  # Initializer for Abstract Class SemanticLogger::Base
@@ -275,11 +286,6 @@ module SemanticLogger
275
286
  self.level = level unless level.nil?
276
287
  end
277
288
 
278
- # Write log data to underlying data storage
279
- def log(log_)
280
- raise NotImplementedError.new('Logging Appender must implement #log(log)')
281
- end
282
-
283
289
  # Return the level index for fast comparisons
284
290
  # Returns the global default level index if the level has not been explicitly
285
291
  # set for this instance
@@ -288,25 +294,25 @@ module SemanticLogger
288
294
  end
289
295
 
290
296
  # Whether to log the supplied message based on the current filter if any
291
- def include_message?(struct)
297
+ def include_message?(log)
292
298
  return true if @filter.nil?
293
299
 
294
300
  if @filter.is_a?(Regexp)
295
- (@filter =~ struct.name) != nil
301
+ (@filter =~ log.name) != nil
296
302
  elsif @filter.is_a?(Proc)
297
- @filter.call(struct) == true
303
+ @filter.call(log) == true
298
304
  end
299
305
  end
300
306
 
301
307
  # Log message at the specified level
302
308
  def log_internal(level, index, message=nil, payload=nil, exception=nil)
303
- # Detect exception being logged
304
- if exception.nil? && payload.nil? && message.kind_of?(Exception)
309
+ # Exception being logged?
310
+ # Under JRuby a java exception is not a Ruby Exception
311
+ # Java::JavaLang::ClassCastException.new.is_a?(Exception) => false
312
+ if exception.nil? && payload.nil? && message.respond_to?(:backtrace) && message.respond_to?(:message)
305
313
  exception = message
306
314
  message = nil
307
315
  elsif exception.nil? && payload && payload.respond_to?(:backtrace) && payload.respond_to?(:message)
308
- # Under JRuby a java exception is not a Ruby Exception
309
- # Java::JavaLang::ClassCastException.new.is_a?(Exception) => false
310
316
  exception = payload
311
317
  payload = nil
312
318
  end
@@ -315,6 +321,8 @@ module SemanticLogger
315
321
  if block_given? && (result = yield)
316
322
  if result.is_a?(String)
317
323
  message = message.nil? ? result : "#{message} -- #{result}"
324
+ elsif message.nil? && result.is_a?(Hash)
325
+ message = result
318
326
  elsif payload && payload.respond_to?(:merge)
319
327
  payload.merge(result)
320
328
  else
@@ -330,8 +338,25 @@ module SemanticLogger
330
338
  # Add caller stack trace
331
339
  backtrace = extract_backtrace if index >= SemanticLogger.backtrace_level_index
332
340
 
333
- struct = Log.new(level, Thread.current.name, name, message, payload, Time.now, nil, tags, index, exception, nil, backtrace)
334
- log(struct) if include_message?(struct)
341
+ log = Log.new(level, Thread.current.name, name, message, payload, Time.now, nil, tags, index, exception, nil, backtrace)
342
+
343
+ # Logging Hash only?
344
+ # logger.info(name: 'value')
345
+ if payload.nil? && exception.nil? && message.is_a?(Hash)
346
+ payload = message.dup
347
+ min_duration = payload.delete(:min_duration) || 0.0
348
+ log.exception = payload.delete(:exception)
349
+ log.message = payload.delete(:message)
350
+ log.metric = payload.delete(:metric)
351
+ log.metric_amount = payload.delete(:metric_amount) || 1
352
+ if duration = payload.delete(:duration)
353
+ return false if duration <= min_duration
354
+ log.duration = duration
355
+ end
356
+ log.payload = payload if payload.size > 0
357
+ end
358
+
359
+ self.log(log) if include_message?(log)
335
360
  end
336
361
 
337
362
  SELF_PATTERN = File.join('lib', 'semantic_logger')
@@ -346,7 +371,7 @@ module SemanticLogger
346
371
  end
347
372
 
348
373
  # Measure the supplied block and log the message
349
- def benchmark_internal(level, index, message, params)
374
+ def measure_internal(level, index, message, params)
350
375
  start = Time.now
351
376
  exception = nil
352
377
  begin
@@ -407,16 +432,16 @@ module SemanticLogger
407
432
  logged_exception = nil
408
433
  backtrace = exception.backtrace
409
434
  end
410
- struct = Log.new(level, Thread.current.name, name, message, payload, end_time, duration, tags, index, logged_exception, metric, backtrace)
411
- log(struct) if include_message?(struct)
435
+ log = Log.new(level, Thread.current.name, name, message, payload, end_time, duration, tags, index, logged_exception, metric, backtrace)
436
+ self.log(log) if include_message?(log)
412
437
  raise exception
413
438
  elsif duration >= min_duration
414
439
  # Only log if the block took longer than 'min_duration' to complete
415
440
  # Add caller stack trace
416
441
  backtrace = extract_backtrace if index >= SemanticLogger.backtrace_level_index
417
442
 
418
- struct = Log.new(level, Thread.current.name, name, message, payload, end_time, duration, tags, index, nil, metric, backtrace)
419
- log(struct) if include_message?(struct)
443
+ log = Log.new(level, Thread.current.name, name, message, payload, end_time, duration, tags, index, nil, metric, backtrace)
444
+ self.log(log) if include_message?(log)
420
445
  end
421
446
  end
422
447
  end
@@ -0,0 +1,51 @@
1
+ # :nodoc:
2
+ module SemanticLogger
3
+ # :nodoc:
4
+ module Concerns
5
+ # :nodoc:
6
+ module Compatibility
7
+ #
8
+ # For compatibility with Ruby Logger only.
9
+ #
10
+ def self.included(base)
11
+ base.class_eval do
12
+ # Map :unknown to :error
13
+ alias_method :unknown, :error # :nodoc:
14
+ alias_method :unknown?, :error? # :nodoc:
15
+
16
+ alias_method :<<, :info # :nodoc:
17
+
18
+ alias_method :progname, :name # :nodoc:
19
+ alias_method :progname=, :name= # :nodoc:
20
+
21
+ alias_method :sev_threshold, :level # :nodoc:
22
+ alias_method :sev_threshold=, :level= # :nodoc:
23
+
24
+ attr_accessor :formatter # :nodoc:
25
+ attr_accessor :datetime_format # :nodoc:
26
+ end
27
+ end
28
+
29
+ # :nodoc:
30
+ def close
31
+ end
32
+
33
+ # :nodoc:
34
+ def reopen(logdev = nil)
35
+ end
36
+
37
+ # :nodoc:
38
+ def add(severity, message = nil, progname = nil, &block)
39
+ index = SemanticLogger.send(:level_to_index, severity)
40
+ if level_index <= index
41
+ level = SemanticLogger.send(:index_to_level, index)
42
+ log_internal(level, index, message, progname, &block)
43
+ true
44
+ else
45
+ false
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end