semantic_logger 2.21.0 → 3.0.0

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