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,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 'logger'
6
6
  require 'date'
@@ -9,10 +9,7 @@ require 'date'
9
9
  module Loggability
10
10
 
11
11
  # Package version constant
12
- VERSION = '0.14.0'
13
-
14
- # VCS revision
15
- REVISION = %q$Revision: 500260d36bfb $
12
+ VERSION = '0.18.0'
16
13
 
17
14
  # The key for the global logger (Loggability's own logger)
18
15
  GLOBAL_KEY = :__global__
@@ -29,22 +26,28 @@ module Loggability
29
26
  LOGSPEC_PATTERN = %r{
30
27
  ^
31
28
  \s*
32
- ((?i:debug|info|warn|error|fatal)) # severity
29
+ (?<severity>(?i:debug|info|warn|error|fatal))
33
30
  (?:
34
31
  \s+
35
- ((?:[\w\-/:\.]|\\[ ])+)
32
+ (?<target>(?:[\w\-/:\.\[\]]|\\[ ])+)
36
33
  )?
37
34
  (?: \s+\(
38
- (\w+)
35
+ (?<format>\w+)
39
36
  \) )?
40
37
  \s*
41
38
  $
42
39
  }x
43
40
 
44
- require 'loggability/constants'
45
- include Loggability::Constants
46
41
 
47
- require 'loggability/logger'
42
+ # Automatically load subordinate classes/modules
43
+ autoload :Constants, 'loggability/constants'
44
+ autoload :LogDevice, 'loggability/log_device'
45
+ autoload :Logger, 'loggability/logger'
46
+ autoload :LogHost, 'loggability/loghost'
47
+ autoload :LogClient, 'loggability/logclient'
48
+ autoload :Override, 'loggability/override'
49
+
50
+ include Loggability::Constants
48
51
 
49
52
 
50
53
  ##
@@ -58,18 +61,6 @@ module Loggability
58
61
  @config = CONFIG_DEFAULTS.dup.freeze
59
62
 
60
63
 
61
- # Automatically log the log host and log client mixins when they're referenced
62
- autoload :LogHost, 'loggability/loghost'
63
- autoload :LogClient, 'loggability/logclient'
64
- autoload :Override, 'loggability/override'
65
-
66
-
67
- ### Return the library's version string
68
- def self::version_string( include_buildnum=false )
69
- vstring = "%s %s" % [ self.name, VERSION ]
70
- vstring << " (build %s)" % [ REVISION[/: ([[:xdigit:]]+)/, 1] || '0' ] if include_buildnum
71
- return vstring
72
- end
73
64
 
74
65
 
75
66
  ### Cast the given +device+ to a Loggability::Logger, if possible, and return it. If
@@ -155,12 +146,12 @@ module Loggability
155
146
 
156
147
  ### Call the method with the given +methodname+ across the loggers of all loghosts with
157
148
  ### the given +arg+ and/or +block+.
158
- def self::aggregate( methodname, arg, &block )
149
+ def self::aggregate( methodname, *args, &block )
159
150
  # self.log.debug "Aggregating a call to %p with %p to %d log hosts" %
160
151
  # [ methodname, arg, Loggability.log_hosts.length ]
161
152
  Loggability.log_hosts.values.each do |loghost|
162
153
  # self.log.debug " %p.logger.%s( %p )" % [ loghost, methodname, arg ]
163
- loghost.logger.send( methodname, arg, &block )
154
+ loghost.logger.send( methodname, *args, &block )
164
155
  end
165
156
  end
166
157
 
@@ -200,8 +191,8 @@ module Loggability
200
191
  #
201
192
  # Aggregate method: set all loggers to log to +destination+. See Loggability::Logger#output_to
202
193
  # for more info.
203
- def self::output_to( newdevice )
204
- self.aggregate( :output_to, newdevice )
194
+ def self::output_to( newdevice, *args )
195
+ self.aggregate( :output_to, newdevice, *args )
205
196
  end
206
197
  class << self
207
198
  alias_method :write_to, :output_to
@@ -332,6 +323,7 @@ module Loggability
332
323
  target = case target
333
324
  when 'STDOUT' then $stdout
334
325
  when 'STDERR' then $stderr
326
+ when /:/ then Loggability::LogDevice.parse_device_spec( target )
335
327
  else
336
328
  target
337
329
  end
@@ -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 )
@@ -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 'loggability' unless defined?( Loggability )
6
6
 
@@ -8,21 +8,23 @@ require 'loggability' unless defined?( Loggability )
8
8
  ### An abstract base class for Loggability log formatters.
9
9
  class Loggability::Formatter
10
10
 
11
- # The default sprintf pattern
12
- DEFAULT_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
13
-
14
-
15
11
  ##
16
12
  # Derivative classes, keyed by name
17
- class << self; attr_reader :derivatives; end
13
+ singleton_class.attr_reader( :derivatives )
18
14
  @derivatives = {}
19
15
 
20
16
 
21
17
  ### Inherited hook -- add subclasses to the ::derivatives Array.
22
18
  def self::inherited( subclass )
23
19
  super
24
- classname = subclass.name.sub( /.*::/, '' ).downcase.to_sym
25
- Loggability::Formatter.derivatives[ classname ] = subclass
20
+
21
+ if ( name = subclass.name )
22
+ classname = name.sub( /.*::/, '' ).downcase
23
+ else
24
+ classname = "anonymous_%d" % [ subclass.object_id ]
25
+ end
26
+
27
+ Loggability::Formatter.derivatives[ classname.to_sym ] = subclass
26
28
  end
27
29
 
28
30
 
@@ -42,69 +44,13 @@ class Loggability::Formatter
42
44
  end
43
45
 
44
46
 
45
-
46
- ### Initialize a new Loggability::Formatter. The specified +logformat+ should
47
- ### be a sprintf pattern with positional placeholders:
48
- ###
49
- ### [<tt>%1$s</tt>] Time (pre-formatted using strftime with the +datetime_format+)
50
- ### [<tt>%2$d</tt>] Time microseconds
51
- ### [<tt>%3$d</tt>] PID
52
- ### [<tt>%4$s</tt>] Thread ID
53
- ### [<tt>%5$s</tt>] Severity
54
- ### [<tt>%6$s</tt>] Object/Program Name
55
- ### [<tt>%7$s</tt>] Message
56
- ###
57
- def initialize( logformat, datetime_format=DEFAULT_DATETIME_FORMAT )
58
- @format = logformat.dup
59
- @datetime_format = datetime_format.dup
60
- end
61
-
62
-
63
- ######
64
- public
65
- ######
66
-
67
- # Main log sprintf format
68
- attr_accessor :format
69
-
70
- # Strftime format for log messages
71
- attr_accessor :datetime_format
72
-
73
-
74
47
  ### Create a log message from the given +severity+, +time+, +progname+, and +message+
75
48
  ### and return it.
76
49
  def call( severity, time, progname, message )
77
- timeformat = self.datetime_format
78
- args = [
79
- time.strftime( timeformat ), # %1$s
80
- time.usec, # %2$d
81
- Process.pid, # %3$d
82
- Thread.current == Thread.main ? 'main' : Thread.current.object_id, # %4$s
83
- severity.downcase, # %5$s
84
- progname, # %6$s
85
- self.msg2str(message, severity) # %7$s
86
- ]
87
-
88
- return self.format % args
50
+ raise NotImplementedError, "%p doesn't implement required method %s" %
51
+ [ self.class, __method__ ]
89
52
  end
90
53
 
91
54
 
92
- #########
93
- protected
94
- #########
95
-
96
- ### Format the specified +msg+ for output to the log.
97
- def msg2str( msg, severity )
98
- case msg
99
- when String
100
- return msg
101
- when Exception
102
- bt = severity == 'DEBUG' ? msg.backtrace.join("\n") : msg.backtrace.first
103
- return "%p: %s from %s" % [ msg.class, msg.message, bt ]
104
- else
105
- return msg.inspect
106
- end
107
- end
108
-
109
55
  end # class Loggability::Formatter
110
56
 
@@ -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 'loggability' unless defined?( Loggability )
6
6
  require 'loggability/formatter' unless defined?( Loggability::Formatter )
@@ -1,20 +1,83 @@
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 'loggability' unless defined?( Loggability )
6
6
  require 'loggability/formatter' unless defined?( Loggability::Formatter )
7
7
 
8
8
 
9
- # The default log formatter class.
9
+ # The default sprintf-based log formatter class.
10
10
  class Loggability::Formatter::Default < Loggability::Formatter
11
11
 
12
+ # The default sprintf pattern
13
+ DEFAULT_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
14
+
12
15
  # The format to output unless debugging is turned on
13
16
  FORMAT = "[%1$s.%2$06d %3$d/%4$s] %5$5s {%6$s} -- %7$s\n"
14
17
 
15
- ### Specify the format for the default formatter.
16
- def initialize( format=FORMAT )
17
- super
18
+ ### Initialize a new Loggability::Formatter. The specified +logformat+ should
19
+ ### be a sprintf pattern with positional placeholders:
20
+ ###
21
+ ### [<tt>%1$s</tt>] Time (pre-formatted using strftime with the +datetime_format+)
22
+ ### [<tt>%2$d</tt>] Time microseconds
23
+ ### [<tt>%3$d</tt>] PID
24
+ ### [<tt>%4$s</tt>] Thread ID
25
+ ### [<tt>%5$s</tt>] Severity
26
+ ### [<tt>%6$s</tt>] Object/Program Name
27
+ ### [<tt>%7$s</tt>] Message
28
+ ###
29
+ def initialize( logformat=FORMAT, datetime_format=DEFAULT_DATETIME_FORMAT )
30
+ super()
31
+
32
+ @format = logformat.dup
33
+ @datetime_format = datetime_format.dup
34
+ end
35
+
36
+
37
+ ######
38
+ public
39
+ ######
40
+
41
+ # Main log sprintf format
42
+ attr_accessor :format
43
+
44
+ # Strftime format for log messages
45
+ attr_accessor :datetime_format
46
+
47
+
48
+ ### Create a log message from the given +severity+, +time+, +progname+, and +message+
49
+ ### and return it.
50
+ def call( severity, time, progname, message )
51
+ timeformat = self.datetime_format
52
+ args = [
53
+ time.strftime( timeformat ), # %1$s
54
+ time.usec, # %2$d
55
+ Process.pid, # %3$d
56
+ Thread.current == Thread.main ? 'main' : Thread.current.object_id, # %4$s
57
+ severity.downcase, # %5$s
58
+ progname, # %6$s
59
+ self.msg2str(message, severity) # %7$s
60
+ ]
61
+
62
+ return self.format % args
63
+ end
64
+
65
+
66
+ #########
67
+ protected
68
+ #########
69
+
70
+ ### Format the specified +msg+ for output to the log.
71
+ def msg2str( msg, severity )
72
+ case msg
73
+ when String
74
+ return msg
75
+ when Exception
76
+ bt = severity == 'DEBUG' ? msg.backtrace.join("\n") : msg.backtrace.first
77
+ return "%p: %s from %s" % [ msg.class, msg.message, bt ]
78
+ else
79
+ return msg.inspect
80
+ end
18
81
  end
19
82
 
20
83
  end # class Loggability::Formatter::Default
@@ -1,13 +1,13 @@
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
- require 'loggability' unless defined?( Loggability )
6
5
  require 'loggability/formatter' unless defined?( Loggability::Formatter )
6
+ require 'loggability/formatter/default'
7
7
 
8
8
 
9
- # The default log formatter class.
10
- class Loggability::Formatter::HTML < Loggability::Formatter
9
+ # Output logs as HTML
10
+ class Loggability::Formatter::HTML < Loggability::Formatter::Default
11
11
 
12
12
  # The default HTML fragment that'll be used as the template for each log message.
13
13
  HTML_LOG_FORMAT = %q{
@@ -0,0 +1,35 @@
1
+ # -*- ruby -*-
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # frozen_string_literal: true
4
+
5
+ require 'time'
6
+ require 'json'
7
+
8
+ require 'loggability/formatter' unless defined?( Loggability::Formatter )
9
+
10
+
11
+ # Output logs as JSON.
12
+ class Loggability::Formatter::Structured < Loggability::Formatter
13
+
14
+ # The version of the format output
15
+ LOG_FORMAT_VERSION = 1
16
+
17
+
18
+ ### Format a message of the specified +severity+ using the given +time+,
19
+ ### +progname+, and +message+.
20
+ def call( severity, time, progname, message )
21
+ severity ||= 'DEBUG'
22
+ time ||= Time.now
23
+ entry = {
24
+ '@version' => LOG_FORMAT_VERSION,
25
+ '@timestamp' => time.iso8601( 3 ),
26
+ 'level' => severity,
27
+ 'progname' => progname,
28
+ 'message' => message,
29
+ }
30
+
31
+ return JSON.generate( entry )
32
+ end
33
+
34
+ end # class Loggability::Formatter::Default
35
+
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # encoding: utf-8
4
+
5
+ require 'loggability' unless defined?( Loggability )
6
+
7
+
8
+ # An abstract base class for logging devices. A device manages the actual writing of messages
9
+ # to whatever destination logs are supposed to be shipped to, along with any buffering,
10
+ # encoding, or serialization that needs to be done.
11
+ #
12
+ # Log devices are loadable by name via the ::create method if they are declared in a
13
+ # directory named `loggability/log_device/` in the gem path.
14
+ #
15
+ # Concrete log devices are required to implement two methods: #write and #close.
16
+ #
17
+ # [write]
18
+ # Takes one argument, which is the message that needs to be written.
19
+ #
20
+ # [close]
21
+ # Close any open filehandles or connections established by the device.
22
+ class Loggability::LogDevice
23
+
24
+
25
+ # Regexp used to split up logging devices in config lines
26
+ DEVICE_TARGET_REGEX = /^([\s*a-z]\w*)(?:\[(.*)\])?/
27
+
28
+
29
+ ### Parses out the target class name and its arguments from the +target_spec+
30
+ ### then requires the subclass and instantiates it by passing the arguments.
31
+ ### The +target_spec+ comes from a config file in the format of:
32
+ ###
33
+ ### logging:
34
+ ### datadog[data_dog_api_key]
35
+ ###
36
+ ### In the above example:
37
+ ### * "datadog" is the log device to send logs to
38
+ ### * "data_dog_api_key" is the argument that will be passed onto the datadog
39
+ ### log device's constructor
40
+ def self::parse_device_spec( target_spec )
41
+ targets = target_spec.split( ';' ).compact
42
+ return targets.map do |t|
43
+ target_subclass = t[ DEVICE_TARGET_REGEX, 1 ]&.strip.to_sym
44
+ target_subclass_args = t[ DEVICE_TARGET_REGEX, 2 ]
45
+
46
+ self.create( target_subclass, target_subclass_args )
47
+ end
48
+ end
49
+
50
+
51
+ ### Requires the subclass and instantiates it with the passed-in arguments and
52
+ ### then returns an instance of it.
53
+ def self::create( target, *target_args )
54
+ modname = target.to_s.capitalize
55
+
56
+ self.load_device_type( target ) unless self.const_defined?( modname, false )
57
+ subclass = self.const_get( modname, false )
58
+
59
+ return subclass.new( *target_args )
60
+ rescue NameError => err
61
+ raise LoadError, "failed to load %s LogDevice: %s" % [ target, err.message ]
62
+ end
63
+
64
+
65
+ ### Attempt to load a LogDevice of the given +type+.
66
+ def self::load_device_type( type )
67
+ require_path = "loggability/log_device/%s" % [ type.to_s.downcase ]
68
+ require( require_path )
69
+ end
70
+
71
+
72
+ ### Write a +message+ to the device. This needs to be overridden by concrete
73
+ ### subclasses; calling this implementation will raise an NotImplementedError.
74
+ def write( message )
75
+ raise NotImplementedError, "%s is not implemented by %s" % [ __callee__, self.class.name ]
76
+ end
77
+
78
+
79
+ ### Close the device. This needs to be overridden by concrete subclasses;
80
+ ### calling this implementation will raise an NotImplementedError.
81
+ def close
82
+ raise NotImplementedError, "%s is not implemented by %s" % [ __callee__, self.class.name ]
83
+ end
84
+
85
+ end # class Loggability::LogDevice
86
+