semantic_logger 2.16.0 → 2.17.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6d4e0a0bfded65004b557ad08aa6729d70387841
4
- data.tar.gz: 9c8f8584584afb4d29c71467f09ab0d87c61fd4e
3
+ metadata.gz: 6d01095b46462b1fc9ea2b2d918a372bda406cd8
4
+ data.tar.gz: d6e2b86770aaaa606727efa5090028ffd0e6bcb5
5
5
  SHA512:
6
- metadata.gz: 5dea37b7135aba5a357aa012ff879345d521a5cc25d45f26b14c1292ab2716c644ebfd29b6ed7eaca6aba109c1b945501c43698a9a7c20c9919c85eb8cfeaa12
7
- data.tar.gz: a061d2c0989485fc5cba5dafc5d96d9d39376805c3d509046c05a46e0dc1c8be4d17d728be939d3cd94315971be1817b95da8baa0b2e223adc156d4de14bdbfc
6
+ metadata.gz: 0ec5d70d0147dba2cb7c63a1981de782a215036adbf469c48d2cb04a1c66a59b055fbf0627214464f0d12199679b69b2ddcd62fc93bf6017311a1cda730e5f90
7
+ data.tar.gz: 84d8f9e36d3ca6d04849e765a6cab837a281bdbcb625d4afebdecbe9d0d30bf86ac0f9153726bb9a726f702d8b22f4551ad782e2c25f285b89ff3bb17b9f0a0b
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # semantic_logger
2
- ![](https://img.shields.io/gem/v/semantic_logger.svg) ![](https://img.shields.io/travis/rocketjob/semantic_logger.svg) ![](https://img.shields.io/gem/dt/semantic_logger.svg) ![](https://img.shields.io/badge/status-production%20ready-blue.svg)
2
+ [![Gem Version](https://img.shields.io/gem/v/semantic_logger.svg)](https://rubygems.org/gems/semantic_logger) [![Build Status](https://travis-ci.org/rocketjob/semantic_logger.svg?branch=master)](https://travis-ci.org/rocketjob/semantic_logger) [![Downloads](https://img.shields.io/gem/dt/semantic_logger.svg)](https://rubygems.org/gems/semantic_logger) [![License](https://img.shields.io/badge/license-Apache%202.0-brightgreen.svg)](http://opensource.org/licenses/Apache-2.0) ![](https://img.shields.io/badge/status-Production%20Ready-blue.svg) [![Gitter chat](https://img.shields.io/badge/IRC%20(gitter)-Support-brightgreen.svg)](https://gitter.im/rocketjob/rocketjob)
3
3
 
4
4
  Low latency, high throughput, enterprise-scale logging system for Ruby
5
5
 
@@ -56,6 +56,20 @@ and are therefore not automatically included by this gem:
56
56
 
57
57
  gem install semantic_logger
58
58
 
59
+ To configure a stand-alone application for Semantic Logger:
60
+
61
+ ```ruby
62
+ require 'semantic_logger'
63
+
64
+ # Set the global default log level
65
+ SemanticLogger.default_level = :trace
66
+
67
+ # Log to a file, and use the colorized formatter
68
+ SemanticLogger.add_appender('development.log', &SemanticLogger::Appender::Base.colorized_formatter)
69
+ ```
70
+
71
+ If running rails, see: [Semantic Logger Rails](http://rocketjob.github.io/semantic_logger/rails.html)
72
+
59
73
  ## Author
60
74
 
61
75
  [Reid Morrison](https://github.com/reidmorrison)
@@ -35,12 +35,20 @@ module SemanticLogger
35
35
  tags = log.tags.collect { |tag| "[#{tag}]" }.join(' ') + ' ' if log.tags && (log.tags.size > 0)
36
36
 
37
37
  message = log.message.to_s.dup
38
- message << ' -- ' << log.payload.inspect unless log.payload.nil? || log.payload.empty?
38
+ message << ' -- ' << log.payload.inspect unless log.payload.nil? || (log.payload.respond_to?(:empty?) && log.payload.empty?)
39
39
  message << ' -- Exception: ' << "#{log.exception.class}: #{log.exception.message}\n#{(log.exception.backtrace || []).join("\n")}" if log.exception
40
40
 
41
41
  duration_str = log.duration ? "(#{'%.1f' % log.duration}ms) " : ''
42
42
 
43
- "#{SemanticLogger::Appender::Base.formatted_time(log.time)} #{log.level.to_s[0..0].upcase} [#{$$}:#{'%.50s' % log.thread_name}] #{tags}#{duration_str}#{log.name} -- #{message}"
43
+ file_name =
44
+ if log.backtrace || log.exception
45
+ backtrace = log.backtrace || log.exception.backtrace
46
+ location = backtrace[0].split('/').last
47
+ file, line = location.split(':')
48
+ " #{file}:#{line}"
49
+ end
50
+
51
+ "#{SemanticLogger::Appender::Base.formatted_time(log.time)} #{log.level.to_s[0..0].upcase} [#{$$}:#{'%.50s' % log.thread_name}#{file_name}] #{tags}#{duration_str}#{log.name} -- #{message}"
44
52
  end
45
53
  end
46
54
 
@@ -55,7 +63,7 @@ module SemanticLogger
55
63
  tags = log.tags.collect { |tag| "[#{colors::CYAN}#{tag}#{colors::CLEAR}]" }.join(' ') + ' ' if log.tags && (log.tags.size > 0)
56
64
 
57
65
  message = log.message.to_s.dup
58
- unless log.payload.nil? || log.payload.empty?
66
+ unless log.payload.nil? || (log.payload.respond_to?(:empty?) && log.payload.empty?)
59
67
  payload = log.payload
60
68
  payload = (defined?(AwesomePrint) && payload.respond_to?(:ai)) ? payload.ai(multiline: false) : payload.inspect
61
69
  message << ' -- ' << payload
@@ -78,7 +86,15 @@ module SemanticLogger
78
86
  colors::RED
79
87
  end
80
88
 
81
- "#{SemanticLogger::Appender::Base.formatted_time(log.time)} #{level_color}#{colors::BOLD}#{log.level.to_s[0..0].upcase}#{colors::CLEAR} [#{$$}:#{'%.30s' % log.thread_name}] #{tags}#{duration_str}#{level_color}#{log.name}#{colors::CLEAR} -- #{message}"
89
+ file_name =
90
+ if log.backtrace || log.exception
91
+ backtrace = log.backtrace || log.exception.backtrace
92
+ location = backtrace[0].split('/').last
93
+ file, line = location.split(':')
94
+ " #{level_color}#{file}:#{line}#{colors::CLEAR}"
95
+ end
96
+
97
+ "#{SemanticLogger::Appender::Base.formatted_time(log.time)} #{level_color}#{colors::BOLD}#{log.level.to_s[0..0].upcase}#{colors::CLEAR} [#{$$}:#{'%.30s' % log.thread_name}#{file_name}] #{tags}#{duration_str}#{level_color}#{log.name}#{colors::CLEAR} -- #{message}"
82
98
  end
83
99
  end
84
100
 
@@ -128,13 +144,13 @@ module SemanticLogger
128
144
  # Return the Time as a formatted string
129
145
  # JRuby only supports time in ms
130
146
  def self.formatted_time(time)
131
- "#{time.strftime("%Y-%m-%d %H:%M:%S")}.#{"%03d" % (time.usec/1000)}"
147
+ "#{time.strftime('%Y-%m-%d %H:%M:%S')}.#{'%03d' % (time.usec/1000)}"
132
148
  end
133
149
  else
134
150
  # Return the Time as a formatted string
135
151
  # Ruby MRI supports micro seconds
136
152
  def self.formatted_time(time)
137
- "#{time.strftime("%Y-%m-%d %H:%M:%S")}.#{"%06d" % (time.usec)}"
153
+ "#{time.strftime('%Y-%m-%d %H:%M:%S')}.#{'%06d' % (time.usec)}"
138
154
  end
139
155
  end
140
156
 
@@ -48,6 +48,8 @@ class SemanticLogger::Appender::Bugsnag < SemanticLogger::Appender::Base
48
48
  # Default: :error
49
49
  # Note: Not recommended to set the log level to :info, :debug, or :trace as that would flood Bugsnag with Error notices
50
50
  def initialize(level = :error, &block)
51
+ # Replace the Bugsnag logger so that we can identify its log messages and not forward them to Bugsnag
52
+ Bugsnag.configure { |config| config.logger = SemanticLogger[Bugsnag] }
51
53
  super(level, &block)
52
54
  end
53
55
 
@@ -55,6 +57,7 @@ class SemanticLogger::Appender::Bugsnag < SemanticLogger::Appender::Base
55
57
  def default_formatter
56
58
  proc do |log|
57
59
  h = {severity: log_level(log), tags: log.tags, class: log.name}
60
+ h[:message] = log.message if log.exception
58
61
  h.merge!(log.payload) if log.payload
59
62
  h
60
63
  end
@@ -63,22 +66,32 @@ class SemanticLogger::Appender::Bugsnag < SemanticLogger::Appender::Base
63
66
  # Send an error notification to Bugsnag
64
67
  def log(log)
65
68
  # Only log if level is warn, or error.
66
- # We don't want to send fatal as those are already captured by Bugsnag.
67
- return false if (level_index > (log.level_index || 0)) || !include_message?(log)
68
- return false if log.level == :fatal
69
- # Ignore logs coming from Bugsnag itself
70
- return false if log.message.to_s.include?(Bugsnag::LOG_PREFIX)
71
-
69
+ return false if (level_index > (log.level_index || 0)) ||
70
+ # We don't want to send fatal as those are already captured by Bugsnag.
71
+ (log.level == :fatal) ||
72
+ # Ignore logs coming from Bugsnag itself
73
+ (log.name == 'Bugsnag') ||
74
+ # Filtered out?
75
+ !include_message?(log)
76
+
77
+ # Send error messages as Runtime exceptions
78
+ exception =
79
+ if log.exception
80
+ log.exception
81
+ else
82
+ error = RuntimeError.new(log.message)
83
+ error.set_backtrace(log.backtrace) if log.backtrace
84
+ error
85
+ end
72
86
  # For more documentation on the Bugsnag.notify method see:
73
87
  # https://bugsnag.com/docs/notifiers/ruby#sending-handled-exceptions
74
- Bugsnag.notify(log.exception || RuntimeError.new(log.message), formatter.call(log))
88
+ Bugsnag.notify(exception, formatter.call(log))
75
89
  true
76
90
  end
77
91
 
78
92
  private
79
93
 
80
94
  def log_level(log)
81
- return 'warning' if log.level == :warn
82
- log.level.to_s
95
+ log.level == :warn ? 'warning' : log.level.to_s
83
96
  end
84
97
  end
@@ -28,6 +28,9 @@ module SemanticLogger
28
28
  # message: 'Invalid value',
29
29
  # stack_trace: []
30
30
  # }
31
+ # # When a backtrace is captured
32
+ # file_name: 'my_class.rb'
33
+ # line_number: 42
31
34
  #
32
35
  class MongoDB < SemanticLogger::Appender::Base
33
36
  attr_reader :db, :collection_name, :collection
@@ -155,11 +158,20 @@ module SemanticLogger
155
158
  document[:duration] = log.duration if log.duration
156
159
  document[:tags] = log.tags if log.tags && (log.tags.size > 0)
157
160
  document[:payload] = log.payload if log.payload
158
- document[:exception] = {
159
- name: log.exception.class.name,
160
- message: log.exception.message,
161
- stack_trace: log.exception.backtrace
162
- } if log.exception
161
+ if log.exception
162
+ document[:exception] = {
163
+ name: log.exception.class.name,
164
+ message: log.exception.message,
165
+ stack_trace: log.exception.backtrace
166
+ }
167
+ end
168
+ if log.backtrace || log.exception
169
+ backtrace = log.backtrace || log.exception.backtrace
170
+ location = backtrace[0].split('/').last
171
+ file, line = location.split(':')
172
+ document[:file_name] = file
173
+ document[:line_number] = line.to_i
174
+ end
163
175
  document
164
176
  end
165
177
  end
@@ -59,34 +59,23 @@ sleep 10
59
59
  require 'newrelic_rpm'
60
60
 
61
61
  class SemanticLogger::Appender::NewRelic < SemanticLogger::Appender::Base
62
-
63
62
  # Allow the level for this appender to be overwritten
64
63
  # Default: :error
65
64
  # Note: Not recommended to set the log level to :info, :debug, or :trace as that would flood NewRelic with Error notices
66
65
  def initialize(level=:error, &block)
67
- # Pass on the level and custom formatter if supplied
68
66
  super(level, &block)
69
67
  end
70
68
 
71
- # The application may send a multiline string to the appender... use the first non-blank line as a shorter message.
72
- def self.first_non_empty_line(string)
73
- string.strip.split("\n").first.to_s
74
- end
75
-
76
69
  # Returns [Hash] of parameters to send to New Relic.
77
70
  def default_formatter
78
71
  Proc.new do |log|
79
- short_message = self.class.first_non_empty_line(log.message)
80
- metric = log.metric || "#{log.name}/#{short_message}"
81
-
82
72
  custom_params = {thread_name: log.thread_name}
83
- # Only show the message under custom attributes if the error message uses an exception or shortened message (first non-empty line).
84
- custom_params[:message] = log.message if log.message && (log.exception || log.message != short_message)
85
73
  custom_params[:duration] = "#{log.duration} ms" if log.duration
86
74
  custom_params[:payload] = log.payload if log.payload
87
75
  custom_params[:tags] = log.tags if log.tags && (log.tags.size > 0)
76
+ custom_params[:message] = log.message if log.exception
88
77
 
89
- {metric: metric, custom_params: custom_params}
78
+ {metric: log.metric, custom_params: custom_params}
90
79
  end
91
80
  end
92
81
 
@@ -95,10 +84,19 @@ class SemanticLogger::Appender::NewRelic < SemanticLogger::Appender::Base
95
84
  # Ensure minimum log level is met, and check filter
96
85
  return false if (level_index > (log.level_index || 0)) || !include_message?(log)
97
86
 
87
+ # Send error messages as Runtime exceptions
88
+ exception =
89
+ if log.exception
90
+ log.exception
91
+ else
92
+ error = RuntimeError.new(log.message)
93
+ error.set_backtrace(log.backtrace) if log.backtrace
94
+ error
95
+ end
98
96
  # For more documentation on the NewRelic::Agent.notice_error method see:
99
97
  # http://rubydoc.info/github/newrelic/rpm/NewRelic/Agent#notice_error-instance_method
100
98
  # and https://docs.newrelic.com/docs/ruby/ruby-agent-api
101
- NewRelic::Agent.notice_error(log.exception || self.class.first_non_empty_line(log.message), formatter.call(log))
99
+ NewRelic::Agent.notice_error(exception, formatter.call(log))
102
100
  true
103
101
  end
104
102
 
@@ -314,7 +314,10 @@ module SemanticLogger
314
314
  #
315
315
  # metric [Object]
316
316
  # Object supplied when benchmark_x was called
317
- Log = Struct.new(:level, :thread_name, :name, :message, :payload, :time, :duration, :tags, :level_index, :exception, :metric)
317
+ #
318
+ # backtrace [Array<String>]
319
+ # The backtrace captured at source when the log level >= SemanticLogger.backtrace_level
320
+ Log = Struct.new(:level, :thread_name, :name, :message, :payload, :time, :duration, :tags, :level_index, :exception, :metric, :backtrace)
318
321
 
319
322
  # Whether to log the supplied message based on the current filter if any
320
323
  def include_message?(struct)
@@ -329,14 +332,18 @@ module SemanticLogger
329
332
 
330
333
  # Log message at the specified level
331
334
  def log_internal(level, index, message=nil, payload=nil, exception=nil, &block)
335
+ # Detect exception being logged
332
336
  if exception.nil? && payload.nil? && message.kind_of?(Exception)
333
337
  exception = message
334
338
  message = exception.inspect
335
- elsif exception.nil? && payload && payload.is_a?(Exception)
339
+ elsif exception.nil? && payload && payload.respond_to?(:backtrace) && payload.respond_to?(:message)
340
+ # Under JRuby a java exception is not a Ruby Exception
341
+ # Java::JavaLang::ClassCastException.new.is_a?(Exception) => false
336
342
  exception = payload
337
343
  payload = nil
338
344
  end
339
345
 
346
+ # Add result of block as message or payload if not nil
340
347
  if block && (result = block.call)
341
348
  if result.is_a?(String)
342
349
  message = message.nil? ? result : "#{message} -- #{result}"
@@ -351,13 +358,24 @@ module SemanticLogger
351
358
  if self.payload
352
359
  payload = payload.nil? ? self.payload : self.payload.merge(payload)
353
360
  end
354
- struct = Log.new(level, Thread.current.name, name, message, payload, Time.now, nil, tags, index, exception)
361
+
362
+ # Add caller stack trace
363
+ backtrace =
364
+ if !exception && (index >= SemanticLogger.backtrace_level_index)
365
+ trace = caller
366
+ # Remove call to this internal method
367
+ trace.shift(1)
368
+ trace
369
+ end
370
+
371
+ struct = Log.new(level, Thread.current.name, name, message, payload, Time.now, nil, tags, index, exception, nil, backtrace)
355
372
  log(struct) if include_message?(struct)
356
373
  end
357
374
 
358
375
  # Measure the supplied block and log the message
359
376
  def benchmark_internal(level, index, message, params, &block)
360
- start = Time.now
377
+ start = Time.now
378
+ exception = nil
361
379
  begin
362
380
  if block
363
381
  result =
@@ -394,6 +412,7 @@ module SemanticLogger
394
412
  end
395
413
  if exception
396
414
  logged_exception = exception
415
+ backtrace = nil
397
416
  case log_exception
398
417
  when :full
399
418
  # On exception change the log level
@@ -409,16 +428,29 @@ module SemanticLogger
409
428
  end
410
429
  message = "#{message} -- Exception: #{exception.class}: #{exception.message}"
411
430
  logged_exception = nil
431
+ backtrace = exception.backtrace
412
432
  else
413
433
  # Log the message with its duration but leave out the exception that was raised
414
434
  logged_exception = nil
435
+ backtrace = exception.backtrace
415
436
  end
416
- struct = Log.new(level, Thread.current.name, name, message, payload, end_time, duration, tags, index, logged_exception, metric)
437
+ struct = Log.new(level, Thread.current.name, name, message, payload, end_time, duration, tags, index, logged_exception, metric, backtrace)
417
438
  log(struct) if include_message?(struct)
418
439
  raise exception
419
440
  elsif duration >= min_duration
420
441
  # Only log if the block took longer than 'min_duration' to complete
421
- struct = Log.new(level, Thread.current.name, name, message, payload, end_time, duration, tags, index, nil, metric)
442
+ # Add caller stack trace
443
+ backtrace =
444
+ if index >= SemanticLogger.backtrace_level_index
445
+ trace = caller
446
+ # Remove call to this internal method
447
+ trace.shift
448
+ # Ruby 1.9 has additional stack entry for parent that calls this method
449
+ trace.shift if RUBY_VERSION.to_f <= 2.0
450
+ trace
451
+ end
452
+
453
+ struct = Log.new(level, Thread.current.name, name, message, payload, end_time, duration, tags, index, nil, metric, backtrace)
422
454
  log(struct) if include_message?(struct)
423
455
  end
424
456
  end
@@ -20,6 +20,33 @@ module SemanticLogger
20
20
  @@default_level
21
21
  end
22
22
 
23
+ # Sets the level at which backtraces should be captured
24
+ # for every log message.
25
+ #
26
+ # By enabling backtrace capture the filename and line number of where
27
+ # message was logged can be written to the log file. Additionally, the backtrace
28
+ # can be forwarded to error management services such as Bugsnag.
29
+ #
30
+ # Warning:
31
+ # Capturing backtraces is very expensive and should not be done all
32
+ # the time. It is recommended to run it at :error level in production.
33
+ def self.backtrace_level=(level)
34
+ @@backtrace_level = level
35
+ # For performance reasons pre-calculate the level index
36
+ @@backtrace_level_index = level.nil? ? 65535 : level_to_index(level)
37
+ end
38
+
39
+ # Returns the current backtrace level
40
+ def self.backtrace_level
41
+ @@backtrace_level
42
+ end
43
+
44
+ # Returns the current backtrace level index
45
+ # For internal use only
46
+ def self.backtrace_level_index #:nodoc
47
+ @@backtrace_level_index
48
+ end
49
+
23
50
  # Add a new logging appender as a new destination for all log messages
24
51
  # emitted from Semantic Logger
25
52
  #
@@ -263,6 +290,8 @@ module SemanticLogger
263
290
  end
264
291
 
265
292
  # Initial default Level for all new instances of SemanticLogger::Logger
266
- @@default_level = :info
267
- @@default_level_index = level_to_index(@@default_level)
293
+ @@default_level = :info
294
+ @@default_level_index = level_to_index(@@default_level)
295
+ @@backtrace_level = :error
296
+ @@backtrace_level_index = level_to_index(@@backtrace_level)
268
297
  end
@@ -1,3 +1,3 @@
1
1
  module SemanticLogger #:nodoc
2
- VERSION = '2.16.0'
2
+ VERSION = '2.17.0'
3
3
  end
@@ -0,0 +1,65 @@
1
+ require_relative '../test_helper'
2
+
3
+ # Unit Test for SemanticLogger::Appender::Bugsnag
4
+ module Appender
5
+ class BugsnagTest < Minitest::Test
6
+ describe SemanticLogger::Appender::Bugsnag do
7
+ before do
8
+ @appender = SemanticLogger::Appender::Bugsnag.new(:warn)
9
+ @message = 'AppenderBugsnagTest log message'
10
+ end
11
+
12
+ (SemanticLogger::LEVELS - [:warn, :error]).each do |level|
13
+ it "not send :#{level} notifications to Bugsnag" do
14
+ exception = hash = nil
15
+ Bugsnag.stub(:notify, -> exc, h { exception = exc; hash = h }) do
16
+ @appender.send(level, "AppenderBugsnagTest #{level.to_s} message")
17
+ end
18
+ assert_nil exception
19
+ assert_nil hash
20
+ end
21
+ end
22
+
23
+ it 'send error notifications to Bugsnag with severity' do
24
+ exception = hash = nil
25
+ Bugsnag.stub(:notify, -> exc, h { exception = exc; hash = h }) do
26
+ @appender.error @message
27
+ end
28
+ assert_equal 'RuntimeError', exception.class.to_s
29
+ assert_equal @message, exception.message
30
+ assert_equal 'error', hash[:severity]
31
+ end
32
+
33
+ it 'send warn notifications to Bugsnag replace warn severity with warning' do
34
+ exception = hash = nil
35
+ Bugsnag.stub(:notify, -> exc, h { exception = exc; hash = h }) do
36
+ @appender.warn @message
37
+ end
38
+ assert_equal 'RuntimeError', exception.class.to_s
39
+ assert_equal @message, exception.message
40
+ assert_equal 'warning', hash[:severity]
41
+ end
42
+
43
+ it 'send notification to Bugsnag with custom attributes' do
44
+ exception = hash = nil
45
+ Bugsnag.stub(:notify, -> exc, h { exception = exc; hash = h }) do
46
+ @appender.error @message, {key1: 1, key2: 'a'}
47
+ end
48
+ assert_equal 'RuntimeError', exception.class.to_s
49
+ assert_equal @message, exception.message
50
+ assert_equal(1, hash[:key1], hash)
51
+ assert_equal('a', hash[:key2], hash)
52
+ end
53
+
54
+ it 'send notification to Bugsnag with exception' do
55
+ error = RuntimeError.new('Hello World')
56
+ exception = hash = nil
57
+ Bugsnag.stub(:notify, -> exc, h { exception = exc; hash = h }) do
58
+ @appender.error error
59
+ end
60
+ assert_equal error.class.to_s, exception.class.to_s
61
+ assert_equal error.message, exception.message
62
+ end
63
+ end
64
+ end
65
+ end