loggability 0.14.0 → 0.18.0

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