semantic_logger 2.21.0 → 3.0.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.
@@ -328,18 +328,23 @@ module SemanticLogger
328
328
  end
329
329
 
330
330
  # Add caller stack trace
331
- backtrace =
332
- if index >= SemanticLogger.backtrace_level_index
333
- trace = caller
334
- # Remove call to this internal method
335
- trace.shift(1)
336
- trace
337
- end
331
+ backtrace = extract_backtrace if index >= SemanticLogger.backtrace_level_index
338
332
 
339
333
  struct = Log.new(level, Thread.current.name, name, message, payload, Time.now, nil, tags, index, exception, nil, backtrace)
340
334
  log(struct) if include_message?(struct)
341
335
  end
342
336
 
337
+ SELF_PATTERN = File.join('lib', 'semantic_logger')
338
+
339
+ # Extract the callers backtrace leaving out Semantic Logger
340
+ def extract_backtrace
341
+ stack = caller
342
+ while (first = stack.first) && first.include?(SELF_PATTERN)
343
+ stack.shift
344
+ end
345
+ stack
346
+ end
347
+
343
348
  # Measure the supplied block and log the message
344
349
  def benchmark_internal(level, index, message, params)
345
350
  start = Time.now
@@ -408,15 +413,7 @@ module SemanticLogger
408
413
  elsif duration >= min_duration
409
414
  # Only log if the block took longer than 'min_duration' to complete
410
415
  # Add caller stack trace
411
- backtrace =
412
- if index >= SemanticLogger.backtrace_level_index
413
- trace = caller
414
- # Remove call to this internal method
415
- trace.shift
416
- # Ruby 1.9 has additional stack entry for parent that calls this method
417
- trace.shift if RUBY_VERSION.to_f <= 2.0
418
- trace
419
- end
416
+ backtrace = extract_backtrace if index >= SemanticLogger.backtrace_level_index
420
417
 
421
418
  struct = Log.new(level, Thread.current.name, name, message, payload, end_time, duration, tags, index, nil, metric, backtrace)
422
419
  log(struct) if include_message?(struct)
@@ -64,6 +64,19 @@ module SemanticLogger
64
64
  end
65
65
  end
66
66
 
67
+ # Returns [String] the exception backtrace including all of the child / caused by exceptions
68
+ def backtrace_to_s
69
+ trace = ''
70
+ each_exception do |exception, i|
71
+ if i == 0
72
+ trace = (exception.backtrace || []).join("\n")
73
+ else
74
+ trace << "\nCause: #{exception.class.name}: #{exception.message}\n#{(exception.backtrace || []).join("\n")}"
75
+ end
76
+ end
77
+ trace
78
+ end
79
+
67
80
  # Returns [String] duration of the log entry as a string
68
81
  # Returns nil if their is no duration
69
82
  # Java time precision does not include microseconds
@@ -89,7 +102,7 @@ module SemanticLogger
89
102
  elsif seconds >= 60.0 # 1 minute
90
103
  Time.at(seconds).strftime('%-Mm %-Ss')
91
104
  elsif seconds >= 1.0 # 1 second
92
- Time.at(seconds).strftime('%-Ss %Lms')
105
+ "#{'%.3f' % seconds}s"
93
106
  else
94
107
  duration_to_s
95
108
  end
@@ -104,18 +117,26 @@ module SemanticLogger
104
117
  # Example:
105
118
  # 18934:thread 23 test_logging.rb:51
106
119
  def process_info(thread_name_length = 30)
107
- file, line = file_name_and_line
120
+ file, line = file_name_and_line(true)
108
121
  file_name = " #{file}:#{line}" if file
109
122
 
110
123
  "#{$$}:#{"%.#{thread_name_length}s" % thread_name}#{file_name}"
111
124
  end
112
125
 
126
+ CALLER_REGEXP = /^(.*):(\d+).*/
127
+
128
+ # Extract the filename and line number from the last entry in the supplied backtrace
129
+ def extract_file_and_line(stack, short_name = false)
130
+ match = CALLER_REGEXP.match(stack.first)
131
+ [short_name ? File.basename(match[1]) : match[1], match[2].to_i]
132
+ end
133
+
113
134
  # Returns [String, String] the file_name and line_number from the backtrace supplied
114
135
  # in either the backtrace or exception
115
- def file_name_and_line
136
+ def file_name_and_line(short_name = false)
116
137
  if backtrace || (exception && exception.backtrace)
117
- stacktrace = backtrace || exception.backtrace
118
- stacktrace[0].split('/').last.split(':')[0..1] if stacktrace && stacktrace.size > 0
138
+ stack = backtrace || exception.backtrace
139
+ extract_file_and_line(stack, short_name) if stack && stack.size > 0
119
140
  end
120
141
  end
121
142
 
@@ -148,6 +169,65 @@ module SemanticLogger
148
169
  end
149
170
  end
150
171
 
172
+ # Returns [Hash] representation of this log entry
173
+ def to_h
174
+ # Header
175
+ h = {
176
+ host: SemanticLogger.host,
177
+ application: SemanticLogger.application,
178
+ name: name,
179
+ pid: $$,
180
+ thread: thread_name,
181
+ time: time,
182
+ level: level,
183
+ level_index: level_index,
184
+ }
185
+ file, line = file_name_and_line
186
+ if file
187
+ h[:file] = file
188
+ h[:line] = line.to_i
189
+ end
190
+
191
+ # Tags
192
+ h[:tags] = tags if tags && (tags.size > 0)
193
+
194
+ # Duration
195
+ if duration
196
+ h[:duration_ms] = duration
197
+ h[:duration] = duration_human
198
+ end
199
+
200
+ # Log message
201
+ h[:message] = cleansed_message if message
202
+
203
+ # Payload
204
+ if payload
205
+ if payload.is_a?(Hash)
206
+ h.merge!(payload)
207
+ else
208
+ h[:payload] = payload
209
+ end
210
+ end
211
+
212
+ # Exceptions
213
+ if exception
214
+ root = h
215
+ each_exception do |exception, i|
216
+ name = i == 0 ? :exception : :cause
217
+ root[name] = {
218
+ name: exception.class.name,
219
+ message: exception.message,
220
+ stack_trace: exception.backtrace
221
+ }
222
+ root = root[name]
223
+ end
224
+ end
225
+
226
+ # Metric
227
+ h[:metric] = metric if metric
228
+ h
229
+ end
230
+
151
231
  end
152
232
 
153
233
  end
@@ -227,7 +227,7 @@ module SemanticLogger
227
227
  begin
228
228
  subscriber.call(log_struct)
229
229
  rescue Exception => exc
230
- logger.error 'Exception calling subscriber', exc
230
+ logger.error 'Exception calling metrics subscriber', exc
231
231
  end
232
232
  end
233
233
  end
@@ -1,4 +1,6 @@
1
1
  require 'concurrent'
2
+ require 'socket'
3
+
2
4
  module SemanticLogger
3
5
  # Logging levels in order of most detailed to most severe
4
6
  LEVELS = [:trace, :debug, :info, :warn, :error, :fatal]
@@ -47,6 +49,28 @@ module SemanticLogger
47
49
  @@backtrace_level_index
48
50
  end
49
51
 
52
+ # Returns [String] name of this host for logging purposes
53
+ # Note: Not all appenders use `host`
54
+ def self.host
55
+ @@host ||= Socket.gethostname
56
+ end
57
+
58
+ # Override the default host name
59
+ def self.host=(host)
60
+ @@host = host
61
+ end
62
+
63
+ # Returns [String] name of this application for logging purposes
64
+ # Note: Not all appenders use `application`
65
+ def self.application
66
+ @@application ||= 'Semantic Logger'
67
+ end
68
+
69
+ # Override the default application
70
+ def self.application=(application)
71
+ @@application = application
72
+ end
73
+
50
74
  # Add a new logging appender as a new destination for all log messages
51
75
  # emitted from Semantic Logger
52
76
  #
@@ -1,3 +1,3 @@
1
1
  module SemanticLogger #:nodoc
2
- VERSION = '2.21.0'
2
+ VERSION = '3.0.0'
3
3
  end
@@ -5,61 +5,59 @@ module Appender
5
5
  class BugsnagTest < Minitest::Test
6
6
  describe SemanticLogger::Appender::Bugsnag do
7
7
  before do
8
- @appender = SemanticLogger::Appender::Bugsnag.new(:warn)
8
+ @appender = SemanticLogger::Appender::Bugsnag.new(:info)
9
9
  @message = 'AppenderBugsnagTest log message'
10
10
  end
11
11
 
12
12
  (SemanticLogger::LEVELS - [:warn, :error]).each do |level|
13
- it "not send :#{level} notifications to Bugsnag" do
13
+ it "sends #{level} message" do
14
14
  exception = hash = nil
15
15
  Bugsnag.stub(:notify, -> exc, h { exception = exc; hash = h }) do
16
- @appender.send(level, "AppenderBugsnagTest #{level.to_s} message")
16
+ @appender.send(level, @message)
17
+ end
18
+ if [:trace, :debug].include?(level)
19
+ assert_equal nil, exception
20
+ assert_equal nil, hash
21
+ else
22
+ assert_equal 'RuntimeError', exception.class.to_s
23
+ assert_equal @message, exception.message
24
+ assert_equal level.to_s, hash[:severity]
17
25
  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
26
  end
28
- assert_equal 'RuntimeError', exception.class.to_s
29
- assert_equal @message, exception.message
30
- assert_equal 'error', hash[:severity]
31
- end
32
27
 
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
28
+ it "sends #{level} custom attributes" do
29
+ exception = hash = nil
30
+ Bugsnag.stub(:notify, -> exc, h { exception = exc; hash = h }) do
31
+ @appender.send(level, @message, {key1: 1, key2: 'a'})
32
+ end
33
+ if [:trace, :debug].include?(level)
34
+ assert_equal nil, exception
35
+ assert_equal nil, hash
36
+ else
37
+ assert_equal 'RuntimeError', exception.class.to_s
38
+ assert_equal @message, exception.message
39
+ assert_equal 1, hash[:key1], hash
40
+ assert_equal 'a', hash[:key2], hash
41
+ end
37
42
  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
 
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'}
44
+ it "sends #{level} exceptions" do
45
+ error = RuntimeError.new('Hello World')
46
+ exception = hash = nil
47
+ Bugsnag.stub(:notify, -> exc, h { exception = exc; hash = h }) do
48
+ @appender.send(level, @message, error)
49
+ end
50
+ if [:trace, :debug].include?(level)
51
+ assert_equal nil, exception
52
+ assert_equal nil, hash
53
+ else
54
+ assert_equal error.class.to_s, exception.class.to_s
55
+ assert_equal error.message, exception.message
56
+ assert_equal @message, hash[:message], hash
57
+ end
47
58
  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
59
  end
53
60
 
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
61
  end
64
62
  end
65
63
  end
@@ -0,0 +1,63 @@
1
+ require_relative '../test_helper'
2
+
3
+ # Unit Test for SemanticLogger::Appender::Graylog
4
+ module Appender
5
+ class GraylogTest < Minitest::Test
6
+ describe SemanticLogger::Appender::Graylog do
7
+ before do
8
+ @appender = SemanticLogger::Appender::Graylog.new(level: :info)
9
+ @message = 'AppenderGraylogTest log message'
10
+ end
11
+
12
+ (SemanticLogger::LEVELS - [:info, :warn, :error, :fatal]).each do |level|
13
+ it "not send :#{level} notifications to Graylog" do
14
+ hash = nil
15
+ @appender.notifier.stub(:notify!, -> h { hash = h }) do
16
+ @appender.send(level, "AppenderGraylogTest #{level.to_s} message")
17
+ end
18
+ assert_nil hash
19
+ end
20
+ end
21
+
22
+ it 'send exception notifications to Graylog with severity' do
23
+ hash = nil
24
+ exc = nil
25
+ begin
26
+ Uh oh
27
+ rescue Exception => e
28
+ exc = e
29
+ end
30
+ @appender.notifier.stub(:notify!, -> h { hash = h }) do
31
+ @appender.error 'Reading File', exc
32
+ end
33
+ assert 'Reading File', hash[:short_message]
34
+ assert 'NameError', hash[:exception][:name]
35
+ assert 'undefined local variable or method', hash[:exception][:message]
36
+ assert_equal 3, hash[:level], 'Should be error level (3)'
37
+ assert hash[:exception][:stack_trace].first.include?(__FILE__), hash[:exception]
38
+ end
39
+
40
+ it 'send error notifications to Graylog with severity' do
41
+ hash = nil
42
+ @appender.notifier.stub(:notify!, -> h { hash = h }) do
43
+ @appender.error @message
44
+ end
45
+ assert_equal @message, hash[:short_message]
46
+ assert_equal 3, hash[:level]
47
+ refute hash[:stack_trace]
48
+ end
49
+
50
+ it 'send notification to Graylog with custom attributes' do
51
+ hash = nil
52
+ @appender.notifier.stub(:notify!, -> h { hash = h }) do
53
+ @appender.error @message, {key1: 1, key2: 'a'}
54
+ end
55
+ assert_equal @message, hash[:short_message]
56
+ assert_equal 3, hash[:level]
57
+ refute hash[:stack_trace]
58
+ assert_equal(1, hash[:key1], hash)
59
+ assert_equal('a', hash[:key2], hash)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,61 @@
1
+ require_relative '../test_helper'
2
+
3
+ # Unit Test for SemanticLogger::Appender::Http
4
+ module Appender
5
+ class HttpTest < Minitest::Test
6
+ response_mock = Struct.new(:code, :body)
7
+
8
+ describe SemanticLogger::Appender::Http do
9
+ before do
10
+ @appender = SemanticLogger::Appender::Http.new(url: 'http://localhost:8088/path')
11
+ @message = 'AppenderHttpTest log message'
12
+ end
13
+
14
+ SemanticLogger::LEVELS.each do |level|
15
+ it "send #{level}" do
16
+ request = nil
17
+ @appender.http.stub(:request, -> r { request = r; response_mock.new('200', 'ok') }) do
18
+ @appender.send(level, @message)
19
+ end
20
+ hash = JSON.parse(request.body)
21
+ assert_equal @message, hash['message']
22
+ assert_equal level.to_s, hash['level']
23
+ refute hash['stack_trace']
24
+ end
25
+
26
+ it "send #{level} exceptions" do
27
+ exc = nil
28
+ begin
29
+ Uh oh
30
+ rescue Exception => e
31
+ exc = e
32
+ end
33
+ request = nil
34
+ @appender.http.stub(:request, -> r { request = r; response_mock.new('200', 'ok') }) do
35
+ @appender.send(level, 'Reading File', exc)
36
+ end
37
+ hash = JSON.parse(request.body)
38
+ assert 'Reading File', hash['message']
39
+ assert 'NameError', hash['exception']['name']
40
+ assert 'undefined local variable or method', hash['exception']['message']
41
+ assert_equal level.to_s, hash['level'], 'Should be error level (3)'
42
+ assert hash['exception']['stack_trace'].first.include?(__FILE__), hash['exception']
43
+ end
44
+
45
+ it "send #{level} custom attributes" do
46
+ request = nil
47
+ @appender.http.stub(:request, -> r { request = r; response_mock.new('200', 'ok') }) do
48
+ @appender.send(level, @message, {key1: 1, key2: 'a'})
49
+ end
50
+ hash = JSON.parse(request.body)
51
+ assert_equal @message, hash['message']
52
+ assert_equal level.to_s, hash['level']
53
+ refute hash['stack_trace']
54
+ assert_equal 1, hash['key1'], hash
55
+ assert_equal 'a', hash['key2'], hash
56
+ end
57
+
58
+ end
59
+ end
60
+ end
61
+ end