semantic_logger 2.16.0 → 2.17.0

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