semantic_logger 3.0.1 → 3.1.0

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