loggability 0.14.0 → 0.18.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.
Files changed (42) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/History.rdoc +55 -0
  5. data/Manifest.txt +15 -4
  6. data/{README.rdoc → README.md} +78 -71
  7. data/Rakefile +5 -77
  8. data/lib/loggability.rb +19 -27
  9. data/lib/loggability/constants.rb +2 -2
  10. data/lib/loggability/formatter.rb +13 -67
  11. data/lib/loggability/formatter/color.rb +2 -2
  12. data/lib/loggability/formatter/default.rb +69 -6
  13. data/lib/loggability/formatter/html.rb +5 -5
  14. data/lib/loggability/formatter/structured.rb +35 -0
  15. data/lib/loggability/log_device.rb +86 -0
  16. data/lib/loggability/log_device/appending.rb +34 -0
  17. data/lib/loggability/log_device/datadog.rb +90 -0
  18. data/lib/loggability/log_device/file.rb +37 -0
  19. data/lib/loggability/log_device/http.rb +310 -0
  20. data/lib/loggability/logclient.rb +2 -1
  21. data/lib/loggability/logger.rb +45 -42
  22. data/lib/loggability/loghost.rb +2 -1
  23. data/lib/loggability/override.rb +2 -1
  24. data/lib/loggability/spechelpers.rb +1 -1
  25. data/spec/helpers.rb +6 -12
  26. data/spec/loggability/formatter/color_spec.rb +3 -7
  27. data/spec/loggability/formatter/default_spec.rb +50 -0
  28. data/spec/loggability/formatter/html_spec.rb +7 -7
  29. data/spec/loggability/formatter/structured_spec.rb +61 -0
  30. data/spec/loggability/formatter_spec.rb +42 -29
  31. data/spec/loggability/log_device/appending_spec.rb +27 -0
  32. data/spec/loggability/log_device/datadog_spec.rb +67 -0
  33. data/spec/loggability/log_device/file_spec.rb +27 -0
  34. data/spec/loggability/log_device/http_spec.rb +217 -0
  35. data/spec/loggability/logger_spec.rb +46 -5
  36. data/spec/loggability/loghost_spec.rb +3 -1
  37. data/spec/loggability/override_spec.rb +18 -5
  38. data/spec/loggability/spechelpers_spec.rb +3 -4
  39. data/spec/loggability_spec.rb +16 -13
  40. metadata +77 -105
  41. metadata.gz.sig +0 -0
  42. data/ChangeLog +0 -621
@@ -1,5 +1,6 @@
1
1
  # -*- ruby -*-
2
- #encoding: utf-8
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # frozen_string_literal: true
3
4
 
4
5
  require 'loggability' unless defined?( Loggability )
5
6
 
@@ -1,6 +1,6 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
  # vim: set nosta noet ts=4 sw=4:
3
- # encoding: utf-8
3
+ # frozen_string_literal: true
4
4
 
5
5
  require 'logger'
6
6
  require 'loggability' unless defined?( Loggability )
@@ -24,35 +24,6 @@ class Loggability::Logger < ::Logger
24
24
  DEFAULT_SHIFT_SIZE = 1048576
25
25
 
26
26
 
27
- # A log device that appends to the object it's constructed with instead of writing
28
- # to a file descriptor or a file.
29
- class AppendingLogDevice
30
-
31
- ### Create a new AppendingLogDevice that will append content to +array+.
32
- def initialize( target )
33
- @target = target
34
- end
35
-
36
-
37
- ######
38
- public
39
- ######
40
-
41
- # The target of the log device
42
- attr_reader :target
43
-
44
-
45
- ### Append the specified +message+ to the target.
46
- def write( message )
47
- @target << message
48
- end
49
-
50
- ### No-op -- this is here just so Logger doesn't complain
51
- def close; end
52
-
53
- end # class AppendingLogDevice
54
-
55
-
56
27
  # Proxy for the Logger that injects the name of the object it wraps as the 'progname'
57
28
  # of each log message.
58
29
  class ObjectNameProxy
@@ -182,6 +153,28 @@ class Loggability::Logger < ::Logger
182
153
  end
183
154
 
184
155
 
156
+ ### Log a message if the +severity+ is high enough. Overridden to account for
157
+ ### the overridden #level.
158
+ def add( severity, message=nil, progname=nil )
159
+ return true if severity < self.sev_threshold
160
+ progname ||= @progname
161
+
162
+ unless message
163
+ if block_given?
164
+ message = yield
165
+ else
166
+ message = progname
167
+ progname = @progname
168
+ end
169
+ end
170
+
171
+ msg = self.format_message( self.format_severity(severity), Time.now, progname, message )
172
+ self.logdev.write( msg )
173
+
174
+ return true
175
+ end
176
+
177
+
185
178
  ### Append operator -- Override Logger's append so log messages always have
186
179
  ### formatting, and are always appended at :debug level.
187
180
  def <<( message )
@@ -224,8 +217,7 @@ class Loggability::Logger < ::Logger
224
217
 
225
218
  ### Return the logger's level as a Symbol.
226
219
  def level
227
- numeric_level = super
228
- return LOG_LEVEL_NAMES[ numeric_level ]
220
+ return LOG_LEVEL_NAMES[ self.sev_threshold ]
229
221
  end
230
222
 
231
223
 
@@ -251,14 +243,17 @@ class Loggability::Logger < ::Logger
251
243
  ### logging to IO objects and files (given a filename in a String), this method can also
252
244
  ### set up logging to any object that responds to #<<.
253
245
  def output_to( target, *args )
254
- if target.is_a?( Logger::LogDevice ) ||
255
- target.is_a?( Loggability::Logger::AppendingLogDevice )
246
+ if target.is_a?( Logger::LogDevice ) || target.is_a?( Loggability::LogDevice )
256
247
  self.logdev = target
257
248
  elsif target.respond_to?( :write ) || target.is_a?( String )
258
249
  opts = { :shift_age => args.shift || 0, :shift_size => args.shift || 1048576 }
259
- self.logdev = Logger::LogDevice.new( target, opts )
250
+ self.logdev = Logger::LogDevice.new( target, **opts )
251
+ elsif target.respond_to?( :any? ) && target.any?( Loggability::LogDevice )
252
+ self.logdev = MultiDevice.new( target )
260
253
  elsif target.respond_to?( :<< )
261
- self.logdev = AppendingLogDevice.new( target )
254
+ self.logdev = Loggability::LogDevice.create( :appending, target )
255
+ elsif target.is_a?( Symbol )
256
+ self.logdev = Loggability::LogDevice.create( target, *args )
262
257
  else
263
258
  raise ArgumentError, "don't know how to output to %p (a %p)" % [ target, target.class ]
264
259
  end
@@ -278,14 +273,22 @@ class Loggability::Logger < ::Logger
278
273
 
279
274
  ### Format a log message using the current formatter and return it.
280
275
  def format_message( severity, datetime, progname, msg )
281
- self.formatter.call(severity, datetime, progname, msg)
276
+ self.formatter.call( severity, datetime, progname, msg )
277
+ end
278
+
279
+
280
+ ### Return the formatted name of the given +severity+.
281
+ def format_severity( severity )
282
+ name = LOG_LEVEL_NAMES[ severity ] || severity.to_s
283
+ return name.upcase
282
284
  end
283
285
 
284
286
 
285
- ### Set a new +formatter+ for the logger. If +formatter+ is +nil+ or +:default+, this causes the
286
- ### logger to fall back to its default formatter. If it's a Symbol other than +:default+, it looks
287
- ### for a similarly-named formatter under loggability/formatter/ and uses that. If +formatter+ is
288
- ### an object that responds to #call (e.g., a Proc or a Method object), that object is used directly.
287
+ ### Set a new +formatter+ for the logger. If +formatter+ is +nil+ or +:default+, this causes
288
+ ### the logger to fall back to its default formatter. If it's a Symbol other than +:default+,
289
+ ### it looks for a similarly-named formatter under loggability/formatter/ and uses that. If
290
+ ### +formatter+ is an object that responds to #call (e.g., a Proc or a Method object), that
291
+ ### object is used directly.
289
292
  ###
290
293
  ### Procs and methods should have the method signature: (severity, datetime, progname, msg).
291
294
  ###
@@ -1,5 +1,6 @@
1
1
  # -*- ruby -*-
2
- #encoding: utf-8
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # frozen_string_literal: true
3
4
 
4
5
  require 'loggability' unless defined?( Loggability )
5
6
 
@@ -1,5 +1,6 @@
1
1
  # -*- ruby -*-
2
- #encoding: utf-8
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # frozen_string_literal: true
3
4
 
4
5
  require 'monitor'
5
6
  require 'loggability' unless defined?( Loggability )
@@ -1,6 +1,6 @@
1
1
  # -*- ruby -*-
2
2
  # vim: set nosta noet ts=4 sw=4:
3
- # encoding: utf-8
3
+ # frozen_string_literal: true
4
4
 
5
5
  require 'loggability' unless defined?( Loggability )
6
6
 
@@ -1,15 +1,6 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
  # vim: set nosta noet ts=4 sw=4:
3
- # encoding: utf-8
4
-
5
- BEGIN {
6
- require 'pathname'
7
- basedir = Pathname.new( __FILE__ ).dirname.parent
8
-
9
- libdir = basedir + "lib"
10
-
11
- $LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
12
- }
3
+ # frozen_string_literal: true
13
4
 
14
5
  # SimpleCov test coverage reporting; enable this using the :coverage rake task
15
6
  if ENV['COVERAGE']
@@ -25,10 +16,10 @@ begin
25
16
  rescue LoadError
26
17
  end
27
18
 
19
+ require 'timecop'
28
20
  require 'loggability'
29
21
  require 'loggability/spechelpers'
30
22
 
31
-
32
23
  # Helpers specific to Loggability specs
33
24
  module SpecHelpers
34
25
 
@@ -107,10 +98,13 @@ RSpec.configure do |c|
107
98
  c.mock_with( :rspec ) do |mock|
108
99
  mock.syntax = :expect
109
100
  end
101
+ c.warnings = true
102
+ c.profile_examples = 5
110
103
 
111
104
  c.include( SpecHelpers )
112
105
  c.include( Loggability::SpecHelpers )
113
106
  c.filter_run_excluding( :configurability ) unless defined?( Configurability )
114
107
 
108
+
115
109
  end
116
110
 
@@ -1,13 +1,9 @@
1
- # -*- rspec -*-
2
- #encoding: utf-8
1
+ # -*- ruby -*-
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # frozen_string_literal: true
3
4
 
4
5
  require_relative '../../helpers'
5
6
 
6
- require 'tempfile'
7
- require 'rspec'
8
-
9
- require 'loggability/logger'
10
- require 'loggability/formatter'
11
7
  require 'loggability/formatter/color'
12
8
 
13
9
 
@@ -0,0 +1,50 @@
1
+ # -*- ruby -*-
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # frozen_string_literal: true
4
+
5
+ require_relative '../../helpers'
6
+
7
+ require 'loggability/formatter/default'
8
+
9
+
10
+ describe Loggability::Formatter::Default do
11
+
12
+ it "formats messages with the pattern it's constructed with" do
13
+ formatter = described_class.new( '[%5$s] %7$s' )
14
+ result = formatter.call( 'INFO', Time.at(1336286481), nil, 'Foom.' )
15
+ expect( result ).to match(/\[INFO\] Foom./i)
16
+ end
17
+
18
+
19
+ it "formats exceptions into useful messages" do
20
+ formatter = described_class.new( '[%5$s] %7$s' )
21
+ msg = nil
22
+
23
+ begin
24
+ raise ArgumentError, "invalid argument"
25
+ rescue => err
26
+ msg = formatter.call( 'INFO', Time.at(1336286481), nil, err )
27
+ end
28
+
29
+ expect( msg ).to match(/\[INFO\] ArgumentError: invalid argument/i)
30
+ end
31
+
32
+
33
+ it "formats regular objects into useful messages" do
34
+ formatter = described_class.new( '[%5$s] %7$s' )
35
+ result = formatter.call( 'INFO', Time.at(1336286481), nil, Object.new )
36
+
37
+ expect( result ).to match(/\[INFO\] #<Object:0x[[:xdigit:]]+>/i)
38
+ end
39
+
40
+
41
+ it "includes the thread ID if logging from a thread other than the main thread" do
42
+ formatter = described_class.new( '%4$d' )
43
+ thr = Thread.new do
44
+ formatter.call( 'INFO', Time.now, nil, 'Foom.' )
45
+ end
46
+ expect( thr.value ).to eq( thr.object_id.to_s )
47
+ end
48
+
49
+ end
50
+
@@ -1,13 +1,9 @@
1
- # -*- rspec -*-
2
- #encoding: utf-8
1
+ # -*- ruby -*-
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # frozen_string_literal: true
3
4
 
4
5
  require_relative '../../helpers'
5
6
 
6
- require 'tempfile'
7
- require 'rspec'
8
-
9
- require 'loggability/logger'
10
- require 'loggability/formatter'
11
7
  require 'loggability/formatter/html'
12
8
 
13
9
 
@@ -15,12 +11,14 @@ describe Loggability::Formatter::HTML do
15
11
 
16
12
  subject { described_class.new }
17
13
 
14
+
18
15
  it "formats messages as HTML" do
19
16
  expect(
20
17
  subject.call( 'INFO', Time.at(1336286481), nil, "Foom." )
21
18
  ).to match( %r{<span class="log-message-text">Foom.</span>}i )
22
19
  end
23
20
 
21
+
24
22
  it "formats exceptions into useful messages" do
25
23
  msg = nil
26
24
 
@@ -35,12 +33,14 @@ describe Loggability::Formatter::HTML do
35
33
  expect( msg ).to match( %r{ from <span class=\"log-exc-firstframe\">}i )
36
34
  end
37
35
 
36
+
38
37
  it "formats regular objects into useful messages" do
39
38
  expect(
40
39
  subject.call( 'INFO', Time.at(1336286481), nil, Object.new )
41
40
  ).to match( %r{<span class=\"log-message-text\">#&lt;Object:0x[[:xdigit:]]+&gt;</span>} )
42
41
  end
43
42
 
43
+
44
44
  it "escapes the 'progname' part of log messages" do
45
45
  progname = "#<Class:0x007f9efa153d08>:0x7f9efa153c18"
46
46
  expect(
@@ -0,0 +1,61 @@
1
+ # -*- ruby -*-
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # frozen_string_literal: true
4
+
5
+ require_relative '../../helpers'
6
+
7
+ require 'loggability/formatter/structured'
8
+
9
+
10
+ describe Loggability::Formatter::Structured do
11
+
12
+ before( :each ) do
13
+ ENV['TZ'] = 'UTC'
14
+ end
15
+
16
+
17
+ it "outputs a stream of JSON objects" do
18
+ expect(
19
+ subject.call('INFO', Time.at(1336286481), nil, "Foom.")
20
+ ).to eq(
21
+ %q|{"@version":1,"@timestamp":"2012-05-06T06:41:21.000+00:00"| +
22
+ %q|,"level":"INFO","progname":null,"message":"Foom."}|
23
+ )
24
+ end
25
+
26
+
27
+ it "includes a time even if called without one" do
28
+ Timecop.freeze( Time.at(1563114765.123) ) do
29
+ expect(
30
+ subject.call('WARN', nil, nil, "Right.")
31
+ ).to match( %r(
32
+ \{
33
+ "@version":1,
34
+ "@timestamp":"2019-07-14T14:32:45\.\d{3}\+00:00",
35
+ "level":"WARN",
36
+ "progname":null,
37
+ "message":"Right\."
38
+ \}
39
+ )x )
40
+ end
41
+ end
42
+
43
+
44
+ it "defaults to DEBUG severity" do
45
+ Timecop.freeze( Time.at(1563114765.123) ) do
46
+ expect(
47
+ subject.call(nil, nil, nil, "Crane.")
48
+ ).to match( %r(
49
+ \{
50
+ "@version":1,
51
+ "@timestamp":"2019-07-14T14:32:45\.\d{3}\+00:00",
52
+ "level":"DEBUG",
53
+ "progname":null,
54
+ "message":"Crane\."
55
+ \}
56
+ )x )
57
+ end
58
+ end
59
+
60
+ end
61
+
@@ -1,52 +1,65 @@
1
- # -*- rspec -*-
1
+ # -*- ruby -*-
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # frozen_string_literal: true
2
4
 
3
5
  require_relative '../helpers'
4
6
 
5
- require 'tempfile'
6
- require 'rspec'
7
-
8
- require 'loggability/logger'
9
7
  require 'loggability/formatter'
10
- require 'loggability/formatter/default'
11
8
 
12
9
 
13
10
  describe Loggability::Formatter do
14
11
 
15
- it "formats messages with the pattern it's constructed with" do
16
- formatter = described_class.new( '[%5$s] %7$s' )
17
- result = formatter.call( 'INFO', Time.at(1336286481), nil, 'Foom.' )
18
- expect( result ).to match(/\[INFO\] Foom./i)
12
+ before( :all ) do
13
+ @actual_derivatives = described_class.derivatives.dup
14
+ end
15
+
16
+ after( :all ) do
17
+ described_class.derivatives.replace( @actual_derivatives )
19
18
  end
20
19
 
21
20
 
22
- it "formats exceptions into useful messages" do
23
- formatter = described_class.new( '[%5$s] %7$s' )
24
- msg = nil
21
+ describe "concrete subclasses" do
25
22
 
26
- begin
27
- raise ArgumentError, "invalid argument"
28
- rescue => err
29
- msg = formatter.call( 'INFO', Time.at(1336286481), nil, err )
23
+ class Test < described_class
30
24
  end
31
25
 
32
- expect( msg ).to match(/\[INFO\] ArgumentError: invalid argument/i)
33
- end
34
26
 
27
+ it "must implement #call" do
28
+ expect {
29
+ Test.new.call( 'INFO', Time.now, nil, "A message." )
30
+ }.to raise_error( /doesn't implement required method/i )
31
+ end
35
32
 
36
- it "formats regular objects into useful messages" do
37
- formatter = described_class.new( '[%5$s] %7$s' )
38
- result = formatter.call( 'INFO', Time.at(1336286481), nil, Object.new )
39
33
 
40
- expect( result ).to match(/\[INFO\] #<Object:0x[[:xdigit:]]+>/i)
41
- end
34
+ it "is tracked by the base class" do
35
+ expect( described_class.derivatives ).to include( test: Test )
36
+ end
42
37
 
43
38
 
44
- it "includes the thread ID if logging from a thread other than the main thread" do
45
- formatter = described_class.new( '%4$d' )
46
- thr = Thread.new do
47
- formatter.call( 'INFO', Time.now, nil, 'Foom.' )
39
+ it "is tracked if its anonymous" do
40
+ subclass = Class.new( described_class )
41
+ expect( described_class.derivatives.values ).to include( subclass )
48
42
  end
49
- expect( thr.value ).to eq( thr.object_id.to_s )
43
+
44
+
45
+ it "can be loaded by name" do
46
+ expect( described_class ).to receive( :require ).
47
+ with( "loggability/formatter/test" )
48
+
49
+ expect( described_class.create(:test) ).to be_an_instance_of( Test )
50
+ end
51
+
52
+
53
+ it "raises a LoadError if loading doesn't work" do
54
+ expect( described_class ).to receive( :require ).
55
+ with( "loggability/formatter/circus" )
56
+
57
+ expect {
58
+ described_class.create( :circus )
59
+ }.to raise_error( LoadError, /didn't load a class/i )
60
+ end
61
+
62
+
50
63
  end
51
64
 
52
65
  end