loggability 0.0.1
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.
- data.tar.gz.sig +4 -0
- data/.gemtest +0 -0
- data/ChangeLog +9353 -0
- data/History.rdoc +4 -0
- data/Manifest.txt +19 -0
- data/README.rdoc +200 -0
- data/Rakefile +49 -0
- data/bin/loggability +3 -0
- data/lib/loggability.rb +223 -0
- data/lib/loggability/constants.rb +26 -0
- data/lib/loggability/formatter.rb +89 -0
- data/lib/loggability/formatter/color.rb +52 -0
- data/lib/loggability/formatter/default.rb +21 -0
- data/lib/loggability/formatter/html.rb +76 -0
- data/lib/loggability/logger.rb +244 -0
- data/spec/lib/helpers.rb +42 -0
- data/spec/loggability/formatter/color_spec.rb +38 -0
- data/spec/loggability/formatter/html_spec.rb +45 -0
- data/spec/loggability/formatter_spec.rb +50 -0
- data/spec/loggability/logger_spec.rb +149 -0
- data/spec/loggability_spec.rb +84 -0
- metadata +220 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim: set nosta noet ts=4 sw=4:
|
3
|
+
# encoding: utf-8
|
4
|
+
|
5
|
+
require 'logger'
|
6
|
+
require 'loggability' unless defined?( Loggability )
|
7
|
+
|
8
|
+
|
9
|
+
module Loggability::Constants
|
10
|
+
|
11
|
+
# Log level names and their Logger constant equivalents
|
12
|
+
LOG_LEVELS = {
|
13
|
+
debug: Logger::DEBUG,
|
14
|
+
info: Logger::INFO,
|
15
|
+
warn: Logger::WARN,
|
16
|
+
error: Logger::ERROR,
|
17
|
+
fatal: Logger::FATAL,
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
# Logger levels -> names
|
21
|
+
LOG_LEVEL_NAMES = LOG_LEVELS.invert.freeze
|
22
|
+
|
23
|
+
|
24
|
+
end # module Loggability::Constants
|
25
|
+
|
26
|
+
|
@@ -0,0 +1,89 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim: set nosta noet ts=4 sw=4:
|
3
|
+
# encoding: utf-8
|
4
|
+
|
5
|
+
require 'pluginfactory'
|
6
|
+
|
7
|
+
require 'loggability' unless defined?( Loggability )
|
8
|
+
|
9
|
+
|
10
|
+
### An abstract base class for Loggability log formatters.
|
11
|
+
class Loggability::Formatter
|
12
|
+
extend PluginFactory
|
13
|
+
|
14
|
+
# The default sprintf pattern
|
15
|
+
DEFAULT_DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
|
16
|
+
|
17
|
+
|
18
|
+
### PluginFactory API -- return the Array of paths prefixes to use when searching
|
19
|
+
### formatter plugins.
|
20
|
+
def self::derivative_dirs
|
21
|
+
return ['loggability/formatter']
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
### Initialize a new Loggability::Formatter. The specified +logformat+ should
|
26
|
+
### be a sprintf pattern with positional placeholders:
|
27
|
+
###
|
28
|
+
### [<tt>%1$s</tt>] Time (pre-formatted using strftime with the +datetime_format+)
|
29
|
+
### [<tt>%2$d</tt>] Time microseconds
|
30
|
+
### [<tt>%3$d</tt>] PID
|
31
|
+
### [<tt>%4$s</tt>] Thread ID
|
32
|
+
### [<tt>%5$s</tt>] Severity
|
33
|
+
### [<tt>%6$s</tt>] Object/Program Name
|
34
|
+
### [<tt>%7$s</tt>] Message
|
35
|
+
###
|
36
|
+
def initialize( logformat, datetime_format=DEFAULT_DATETIME_FORMAT )
|
37
|
+
@format = logformat.dup
|
38
|
+
@datetime_format = datetime_format.dup
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
######
|
43
|
+
public
|
44
|
+
######
|
45
|
+
|
46
|
+
# Main log sprintf format
|
47
|
+
attr_accessor :format
|
48
|
+
|
49
|
+
# Strftime format for log messages
|
50
|
+
attr_accessor :datetime_format
|
51
|
+
|
52
|
+
|
53
|
+
### Create a log message from the given +severity+, +time+, +progname+, and +message+
|
54
|
+
### and return it.
|
55
|
+
def call( severity, time, progname, message )
|
56
|
+
timeformat = self.datetime_format
|
57
|
+
args = [
|
58
|
+
time.strftime( timeformat ), # %1$s
|
59
|
+
time.usec, # %2$d
|
60
|
+
Process.pid, # %3$d
|
61
|
+
Thread.current == Thread.main ? 'main' : Thread.object_id, # %4$s
|
62
|
+
severity, # %5$s
|
63
|
+
progname, # %6$s
|
64
|
+
self.msg2str(message, severity) # %7$s
|
65
|
+
]
|
66
|
+
|
67
|
+
return self.format % args
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
#########
|
72
|
+
protected
|
73
|
+
#########
|
74
|
+
|
75
|
+
### Format the specified +msg+ for output to the log.
|
76
|
+
def msg2str( msg, severity )
|
77
|
+
case msg
|
78
|
+
when String
|
79
|
+
return msg
|
80
|
+
when Exception
|
81
|
+
bt = severity == 'DEBUG' ? msg.backtrace.join("\n") : msg.backtrace.first
|
82
|
+
return "%p: %s from %s" % [ msg.class, msg.message, bt ]
|
83
|
+
else
|
84
|
+
return msg.inspect
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end # class Loggability::Formatter
|
89
|
+
|
@@ -0,0 +1,52 @@
|
|
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
|
+
require 'loggability/formatter' unless defined?( Loggability::Formatter )
|
7
|
+
require 'loggability/formatter/default'
|
8
|
+
|
9
|
+
|
10
|
+
# The default log formatter class.
|
11
|
+
class Loggability::Formatter::Color < Loggability::Formatter::Default
|
12
|
+
|
13
|
+
# ANSI reset
|
14
|
+
RESET = "\e[0m"
|
15
|
+
|
16
|
+
# ANSI color escapes keyed by severity
|
17
|
+
LEVEL_CODES = {
|
18
|
+
debug: "\e[1;30m", # bold black
|
19
|
+
info: "\e[37m", # white
|
20
|
+
warn: "\e[1;33m", # bold yellow
|
21
|
+
error: "\e[31m", # red
|
22
|
+
fatal: "\e[1;31m", # bold red
|
23
|
+
}
|
24
|
+
|
25
|
+
# Pattern for matching color terminals
|
26
|
+
COLOR_TERMINAL_NAMES = /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i
|
27
|
+
|
28
|
+
|
29
|
+
### Create a new formatter.
|
30
|
+
def initialize( * )
|
31
|
+
super
|
32
|
+
@color_enabled = COLOR_TERMINAL_NAMES.match( ENV['TERM'] ) ? true : false
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
######
|
37
|
+
public
|
38
|
+
######
|
39
|
+
|
40
|
+
### Format the specified +msg+ for output to the log.
|
41
|
+
def msg2str( msg, severity )
|
42
|
+
msg = super
|
43
|
+
if @color_enabled
|
44
|
+
color = severity.downcase.to_sym
|
45
|
+
msg = [ LEVEL_CODES[color], msg, RESET ].join( '' )
|
46
|
+
end
|
47
|
+
|
48
|
+
return msg
|
49
|
+
end
|
50
|
+
|
51
|
+
end # class Loggability::Formatter::Color
|
52
|
+
|
@@ -0,0 +1,21 @@
|
|
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
|
+
require 'loggability/formatter' unless defined?( Loggability::Formatter )
|
7
|
+
|
8
|
+
|
9
|
+
# The default log formatter class.
|
10
|
+
class Loggability::Formatter::Default < Loggability::Formatter
|
11
|
+
|
12
|
+
# The format to output unless debugging is turned on
|
13
|
+
FORMAT = "[%1$s.%2$06d %3$d/%4$s] %5$5s {%6$s} -- %7$s\n"
|
14
|
+
|
15
|
+
### Specify the format for the default formatter.
|
16
|
+
def initialize( format=FORMAT )
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
end # class Loggability::Formatter::Default
|
21
|
+
|
@@ -0,0 +1,76 @@
|
|
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
|
+
require 'loggability/formatter' unless defined?( Loggability::Formatter )
|
7
|
+
|
8
|
+
|
9
|
+
# The default log formatter class.
|
10
|
+
class Loggability::Formatter::HTML < Loggability::Formatter
|
11
|
+
|
12
|
+
# The default HTML fragment that'll be used as the template for each log message.
|
13
|
+
HTML_LOG_FORMAT = %q{
|
14
|
+
<div class="log-message %5$s-log-message">
|
15
|
+
<span class="log-time">%1$s.%2$06d</span>
|
16
|
+
[
|
17
|
+
<span class="log-pid">%3$d</span>
|
18
|
+
/
|
19
|
+
<span class="log-tid">%4$s</span>
|
20
|
+
]
|
21
|
+
<span class="log-level">%5$s</span>
|
22
|
+
:
|
23
|
+
<span class="log-name">%6$s</span>
|
24
|
+
<span class="log-message-text">%7$s</span>
|
25
|
+
</div>
|
26
|
+
}.gsub( /[\t\n]/, ' ' )
|
27
|
+
|
28
|
+
# The format for dumping exceptions
|
29
|
+
HTML_EXCEPTION_FORMAT = %Q{
|
30
|
+
<span class="log-exc">%1$p</span>:
|
31
|
+
<span class="log-exc-message">%2$s</span>
|
32
|
+
from <span class="log-exc-firstframe">%3$s</span>
|
33
|
+
}.gsub( /[\t\n]/, ' ' )
|
34
|
+
|
35
|
+
|
36
|
+
### Override the logging formats with ones that generate HTML fragments
|
37
|
+
def initialize( format=HTML_LOG_FORMAT ) # :notnew:
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
#########
|
43
|
+
protected
|
44
|
+
#########
|
45
|
+
|
46
|
+
### Format the specified +object+ for output to the log.
|
47
|
+
def msg2str( object, severity )
|
48
|
+
case object
|
49
|
+
when String
|
50
|
+
return escape_html( object )
|
51
|
+
when Exception
|
52
|
+
return HTML_EXCEPTION_FORMAT % [
|
53
|
+
object.class,
|
54
|
+
escape_html(object.message),
|
55
|
+
escape_html(object.backtrace.first)
|
56
|
+
]
|
57
|
+
else
|
58
|
+
return escape_html(object.inspect)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
#######
|
64
|
+
private
|
65
|
+
#######
|
66
|
+
|
67
|
+
### Escape any HTML special characters in +string+.
|
68
|
+
def escape_html( string )
|
69
|
+
return string.
|
70
|
+
gsub( '&', '&' ).
|
71
|
+
gsub( '<', '<' ).
|
72
|
+
gsub( '>', '>' )
|
73
|
+
end
|
74
|
+
|
75
|
+
end # class Loggability::Formatter::Default
|
76
|
+
|
@@ -0,0 +1,244 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim: set nosta noet ts=4 sw=4:
|
3
|
+
# encoding: utf-8
|
4
|
+
|
5
|
+
require 'logger'
|
6
|
+
require 'loggability' unless defined?( Loggability )
|
7
|
+
require 'loggability/constants'
|
8
|
+
require 'loggability/formatter'
|
9
|
+
|
10
|
+
|
11
|
+
# A subclass of Logger that provides additional API for configuring outputters,
|
12
|
+
# formatters, etc.
|
13
|
+
class Loggability::Logger < ::Logger
|
14
|
+
include Loggability::Constants,
|
15
|
+
Logger::Severity
|
16
|
+
|
17
|
+
# Default log 'device'
|
18
|
+
DEFAULT_DEVICE = $stderr
|
19
|
+
|
20
|
+
# Default 'shift age'
|
21
|
+
DEFAULT_SHIFT_AGE = 0
|
22
|
+
|
23
|
+
# Default 'shift size'
|
24
|
+
DEFAULT_SHIFT_SIZE = 1048576
|
25
|
+
|
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
|
+
### Append the specified +message+ to the target.
|
37
|
+
def write( message )
|
38
|
+
@target << message
|
39
|
+
end
|
40
|
+
|
41
|
+
### No-op -- this is here just so Logger doesn't complain
|
42
|
+
def close; end
|
43
|
+
|
44
|
+
end # class AppendingLogDevice
|
45
|
+
|
46
|
+
|
47
|
+
# Proxy for the Logger that injects the name of the object it wraps as the 'progname'
|
48
|
+
# of each log message.
|
49
|
+
class ObjectNameProxy
|
50
|
+
|
51
|
+
### Create a proxy for the given +logger+ that will inject the name of the
|
52
|
+
### specified +object+ into the 'progname' of each log message.
|
53
|
+
def initialize( logger, object )
|
54
|
+
@logger = logger
|
55
|
+
@progname = make_progname( object )
|
56
|
+
end
|
57
|
+
|
58
|
+
######
|
59
|
+
public
|
60
|
+
######
|
61
|
+
|
62
|
+
# The Loggability::Logger this proxy is for.
|
63
|
+
attr_reader :logger
|
64
|
+
|
65
|
+
# The progname of the object the proxy is for.
|
66
|
+
attr_reader :progname
|
67
|
+
|
68
|
+
|
69
|
+
### Delegate debug messages
|
70
|
+
def debug( msg=nil, &block )
|
71
|
+
@logger.add( Logger::DEBUG, msg, @progname, &block )
|
72
|
+
end
|
73
|
+
|
74
|
+
### Delegate info messages
|
75
|
+
def info( msg=nil, &block )
|
76
|
+
@logger.add( Logger::INFO, msg, @progname, &block )
|
77
|
+
end
|
78
|
+
|
79
|
+
### Delegate warn messages
|
80
|
+
def warn( msg=nil, &block )
|
81
|
+
@logger.add( Logger::WARN, msg, @progname, &block )
|
82
|
+
end
|
83
|
+
|
84
|
+
### Delegate error messages
|
85
|
+
def error( msg=nil, &block )
|
86
|
+
@logger.add( Logger::ERROR, msg, @progname, &block )
|
87
|
+
end
|
88
|
+
|
89
|
+
### Delegate fatal messages
|
90
|
+
def fatal( msg=nil, &block )
|
91
|
+
@logger.add( Logger::FATAL, msg, @progname, &block )
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
### Return a human-readable string representation of the object, suitable for debugging.
|
96
|
+
def inspect
|
97
|
+
return "#<%p:%#016x for %s>" % [ self.class, self.object_id * 2, @progname ]
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
#######
|
102
|
+
private
|
103
|
+
#######
|
104
|
+
|
105
|
+
### Make a progname for the specified object.
|
106
|
+
def make_progname( object )
|
107
|
+
case object
|
108
|
+
when Class
|
109
|
+
object.inspect
|
110
|
+
else
|
111
|
+
"%p:%#x" % [ object.class, object.object_id * 2 ]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
end # class ObjectNameProxy
|
117
|
+
|
118
|
+
|
119
|
+
### Create a new Logger wrapper that will output to the specified +logdev+.
|
120
|
+
def initialize( logdev=DEFAULT_DEVICE, *args )
|
121
|
+
super
|
122
|
+
self.level = ($DEBUG ? DEBUG : WARN)
|
123
|
+
@default_formatter = Loggability::Formatter.create( :default )
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
######
|
128
|
+
public
|
129
|
+
######
|
130
|
+
|
131
|
+
#
|
132
|
+
# :section: Severity Level
|
133
|
+
#
|
134
|
+
|
135
|
+
### Return the logger's level as a Symbol.
|
136
|
+
def level
|
137
|
+
numeric_level = super
|
138
|
+
return LOG_LEVEL_NAMES[ numeric_level ]
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
### Set the logger level to +newlevel+, which can be a numeric level (e.g., Logger::DEBUG, etc.),
|
143
|
+
### or a symbolic level (e.g., :debug, :info, etc.)
|
144
|
+
def level=( newlevel )
|
145
|
+
newlevel = LOG_LEVELS[ newlevel.to_sym ] if newlevel.respond_to?( :to_sym )
|
146
|
+
super( newlevel )
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
#
|
151
|
+
# :section: Output Device
|
152
|
+
#
|
153
|
+
|
154
|
+
# The raw log device
|
155
|
+
attr_accessor :logdev
|
156
|
+
|
157
|
+
|
158
|
+
### Change the log device to log to +target+ instead of what it was before. Any additional
|
159
|
+
### +args+ are passed to the LogDevice's constructor. In addition to Logger's support for
|
160
|
+
### logging to IO objects and files (given a filename in a String), this method can also
|
161
|
+
### set up logging to any object that responds to #<<.
|
162
|
+
def output_to( target, *args )
|
163
|
+
if target.respond_to?( :write ) || target.is_a?( String )
|
164
|
+
opts = { shift_age: args.shift, shift_size: args.shift }
|
165
|
+
self.logdev = Logger::LogDevice.new( target, opts )
|
166
|
+
elsif target.respond_to?( :<< )
|
167
|
+
self.logdev = AppendingLogDevice.new( target )
|
168
|
+
else
|
169
|
+
raise ArgumentError, "don't know how to output to %p (a %p)" % [ target, target.class ]
|
170
|
+
end
|
171
|
+
end
|
172
|
+
alias_method :write_to, :output_to
|
173
|
+
|
174
|
+
|
175
|
+
#
|
176
|
+
# :section: Output Formatting API
|
177
|
+
#
|
178
|
+
|
179
|
+
### Return the current formatter used to format log messages.
|
180
|
+
def formatter
|
181
|
+
return ( @formatter || @default_formatter )
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
### Format a log message using the current formatter and return it.
|
186
|
+
def format_message( severity, datetime, progname, msg )
|
187
|
+
self.formatter.call(severity, datetime, progname, msg)
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
### Set a new +formatter+ for the logger. If +formatter+ is +nil+ or +:default+, this causes the
|
192
|
+
### logger to fall back to its default formatter. If it's a Symbol other than +:default+, it looks
|
193
|
+
### for a similarly-named formatter under loggability/formatter/ and uses that. If +formatter+ is
|
194
|
+
### an object that responds to #call (e.g., a Proc or a Method object), that object is used directly.
|
195
|
+
###
|
196
|
+
### Procs and methods should have the method signature: (severity, datetime, progname, msg).
|
197
|
+
###
|
198
|
+
### # Load and use the HTML formatter
|
199
|
+
### MyProject.logger.format_with( :html )
|
200
|
+
###
|
201
|
+
### # Call self.format_logmsg(...) to format messages
|
202
|
+
### MyProject.logger.format_with( self.method(:format_logmsg) )
|
203
|
+
###
|
204
|
+
### # Reset to the default
|
205
|
+
### MyProject.logger.format_with( :default )
|
206
|
+
###
|
207
|
+
def format_with( formatter=nil, &block ) # :yield: severity, datetime, progname, msg
|
208
|
+
formatter ||= block
|
209
|
+
|
210
|
+
if formatter.nil? || formatter == :default
|
211
|
+
@formatter = nil
|
212
|
+
|
213
|
+
elsif formatter.respond_to?( :call )
|
214
|
+
@formatter = formatter
|
215
|
+
|
216
|
+
elsif formatter.respond_to?( :to_sym )
|
217
|
+
@formatter = Loggability::Formatter.create( formatter )
|
218
|
+
|
219
|
+
else
|
220
|
+
raise ArgumentError, "don't know what to do with a %p formatter (%p)" %
|
221
|
+
[ formatter.class, formatter ]
|
222
|
+
end
|
223
|
+
end
|
224
|
+
alias_method :format_as, :format_with
|
225
|
+
alias_method :formatter=, :format_with
|
226
|
+
|
227
|
+
|
228
|
+
#
|
229
|
+
# :section: Progname Proxy
|
230
|
+
# Rather than require that the caller provide the 'progname' part of the log message
|
231
|
+
# on every call, you can grab a Proxy object for a particular object and Logger combination
|
232
|
+
# that will include the object's name with every log message.
|
233
|
+
#
|
234
|
+
|
235
|
+
|
236
|
+
### Create a logging proxy for +object+ that will include its name as the 'progname' of
|
237
|
+
### each message.
|
238
|
+
def proxy_for( object )
|
239
|
+
return ObjectNameProxy.new( self, object )
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
end # class Loggability::Logger
|
244
|
+
|