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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/History.rdoc +55 -0
- data/Manifest.txt +15 -4
- data/{README.rdoc → README.md} +78 -71
- data/Rakefile +5 -77
- data/lib/loggability.rb +19 -27
- data/lib/loggability/constants.rb +2 -2
- data/lib/loggability/formatter.rb +13 -67
- data/lib/loggability/formatter/color.rb +2 -2
- data/lib/loggability/formatter/default.rb +69 -6
- data/lib/loggability/formatter/html.rb +5 -5
- data/lib/loggability/formatter/structured.rb +35 -0
- data/lib/loggability/log_device.rb +86 -0
- data/lib/loggability/log_device/appending.rb +34 -0
- data/lib/loggability/log_device/datadog.rb +90 -0
- data/lib/loggability/log_device/file.rb +37 -0
- data/lib/loggability/log_device/http.rb +310 -0
- data/lib/loggability/logclient.rb +2 -1
- data/lib/loggability/logger.rb +45 -42
- data/lib/loggability/loghost.rb +2 -1
- data/lib/loggability/override.rb +2 -1
- data/lib/loggability/spechelpers.rb +1 -1
- data/spec/helpers.rb +6 -12
- data/spec/loggability/formatter/color_spec.rb +3 -7
- data/spec/loggability/formatter/default_spec.rb +50 -0
- data/spec/loggability/formatter/html_spec.rb +7 -7
- data/spec/loggability/formatter/structured_spec.rb +61 -0
- data/spec/loggability/formatter_spec.rb +42 -29
- data/spec/loggability/log_device/appending_spec.rb +27 -0
- data/spec/loggability/log_device/datadog_spec.rb +67 -0
- data/spec/loggability/log_device/file_spec.rb +27 -0
- data/spec/loggability/log_device/http_spec.rb +217 -0
- data/spec/loggability/logger_spec.rb +46 -5
- data/spec/loggability/loghost_spec.rb +3 -1
- data/spec/loggability/override_spec.rb +18 -5
- data/spec/loggability/spechelpers_spec.rb +3 -4
- data/spec/loggability_spec.rb +16 -13
- metadata +77 -105
- metadata.gz.sig +0 -0
- data/ChangeLog +0 -621
data/lib/loggability.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
# vim: set nosta noet ts=4 sw=4:
|
3
|
-
#
|
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.
|
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))
|
29
|
+
(?<severity>(?i:debug|info|warn|error|fatal))
|
33
30
|
(?:
|
34
31
|
\s+
|
35
|
-
((?:[\w
|
32
|
+
(?<target>(?:[\w\-/:\.\[\]]|\\[ ])+)
|
36
33
|
)?
|
37
34
|
(?: \s+\(
|
38
|
-
(
|
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
|
-
|
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,
|
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,
|
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
|
-
|
1
|
+
# -*- ruby -*-
|
2
2
|
# vim: set nosta noet ts=4 sw=4:
|
3
|
-
#
|
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
|
-
|
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
|
-
|
25
|
-
|
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
|
-
|
78
|
-
|
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,20 +1,83 @@
|
|
1
|
-
|
1
|
+
# -*- ruby -*-
|
2
2
|
# vim: set nosta noet ts=4 sw=4:
|
3
|
-
#
|
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
|
-
###
|
16
|
-
|
17
|
-
|
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
|
-
|
1
|
+
# -*- ruby -*-
|
2
2
|
# vim: set nosta noet ts=4 sw=4:
|
3
|
-
#
|
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
|
-
#
|
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
|
+
|